summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2019-06-12 10:28:58 +0100
committerFilipa Lacerda <filipa@gitlab.com>2019-06-12 10:28:58 +0100
commit0b080f8c8a81c6883582c417be199abc56086208 (patch)
tree52657388129959073b52bbb8f72ddfc26a042641
parent093b2f42c4efc1f679ec742e0a28145171127782 (diff)
parente206e66bbba9e36a75eb485f65c26f1d5a05e90f (diff)
downloadgitlab-ce-0b080f8c8a81c6883582c417be199abc56086208.tar.gz
Merge branch 'master' into generate-spans-for-sections
* master: (784 commits) Update Serverless.gitlab-ci-yml to fix lint warning Document when milestones and labels links are missing Update git object deduplication overview Apply tooltip on truncated commit title Add link to import video Update merge options for auto merge strategies Update note style in various docs Add link to Auto DevOps demo video Add link to Groups video Remove glossary content and make necessary doc edits to clarify terms Docs for Gitlab Runner docker use of private registries Metrics reports documentation is incomplete Update issues/index.md with note on label priority sorting API issues - minor cleanup of permission check Fix gl_dropdown scrolling to top on assignee click Final doc review Remove docs for a removed ES rake task Fixed markdown link in Docker compose note Documented the Admin Area's *Groups* page Docs: Updating issue data and actions doc ...
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml3
-rw-r--r--.gitlab/CODEOWNERS (renamed from .gitlab/CODEOWNERS.disabled)7
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml25
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/pages.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml90
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml7
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/yaml.gitlab-ci.yml9
-rw-r--r--.gitlab/issue_templates/Feature Flag Roll Out.md43
-rw-r--r--.gitlab/route-map.yml4
-rw-r--r--.yamllint7
-rw-r--r--CHANGELOG.md63
-rw-r--r--Dangerfile1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile13
-rw-r--r--Gemfile.lock49
-rw-r--r--PROCESS.md4
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/favicon-yellow.pngbin1667 -> 1481 bytes
-rw-r--r--app/assets/javascripts/api.js2
-rw-r--r--app/assets/javascripts/batch_comments/mixins/resolved_status.js6
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js2
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue6
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue4
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue19
-rw-r--r--app/assets/javascripts/boards/components/modal/index.vue2
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js3
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.vue2
-rw-r--r--app/assets/javascripts/boards/index.js34
-rw-r--r--app/assets/javascripts/boards/models/assignee.js (renamed from app/assets/javascripts/vue_shared/models/assignee.js)0
-rw-r--r--app/assets/javascripts/boards/models/issue.js2
-rw-r--r--app/assets/javascripts/boards/models/label.js11
-rw-r--r--app/assets/javascripts/boards/models/list.js8
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js40
-rw-r--r--app/assets/javascripts/branches/branches_delete_modal.js2
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js11
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js6
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js70
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue121
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue178
-rw-r--r--app/assets/javascripts/clusters/components/knative_domain_editor.vue150
-rw-r--r--app/assets/javascripts/clusters/services/application_state_machine.js1
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js13
-rw-r--r--app/assets/javascripts/commons/polyfills.js32
-rw-r--r--app/assets/javascripts/compare_autocomplete.js2
-rw-r--r--app/assets/javascripts/create_item_dropdown.js2
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue4
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue15
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue23
-rw-r--r--app/assets/javascripts/diffs/components/diff_gutter_avatars.vue1
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue21
-rw-r--r--app/assets/javascripts/diffs/components/edit_button.vue2
-rw-r--r--app/assets/javascripts/diffs/store/actions.js3
-rw-r--r--app/assets/javascripts/emoji/no_emoji_validator.js44
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/getters.js4
-rw-r--r--app/assets/javascripts/frequent_items/store/actions.js2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js1
-rw-r--r--app/assets/javascripts/gl_dropdown.js17
-rw-r--r--app/assets/javascripts/gl_form.js2
-rw-r--r--app/assets/javascripts/ide/components/activity_bar.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue34
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue43
-rw-r--r--app/assets/javascripts/ide/components/ide.vue40
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue19
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue1
-rw-r--r--app/assets/javascripts/ide/components/preview/clientside.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue2
-rw-r--r--app/assets/javascripts/ide/lib/editor_options.js2
-rw-r--r--app/assets/javascripts/ide/lib/keymap.json8
-rw-r--r--app/assets/javascripts/ide/stores/actions.js57
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js5
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js118
-rw-r--r--app/assets/javascripts/ide/stores/actions/tree.js18
-rw-r--r--app/assets/javascripts/ide/stores/getters.js15
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js36
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/getters.js7
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/mutation_types.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/mutations.js18
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/state.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/getters.js2
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js2
-rw-r--r--app/assets/javascripts/ide/stores/mutations/branch.js6
-rw-r--r--app/assets/javascripts/ide/stores/mutations/project.js5
-rw-r--r--app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js2
-rw-r--r--app/assets/javascripts/image_diff/image_diff.js4
-rw-r--r--app/assets/javascripts/image_diff/view_types.js2
-rw-r--r--app/assets/javascripts/issuable_index.js2
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue2
-rw-r--r--app/assets/javascripts/issue_show/components/title.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue12
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue3
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue11
-rw-r--r--app/assets/javascripts/jobs/store/actions.js26
-rw-r--r--app/assets/javascripts/jobs/store/mutation_types.js4
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js19
-rw-r--r--app/assets/javascripts/jobs/store/state.js1
-rw-r--r--app/assets/javascripts/label_manager.js2
-rw-r--r--app/assets/javascripts/lib/graphql.js28
-rw-r--r--app/assets/javascripts/lib/utils/accessor.js2
-rw-r--r--app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js9
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js10
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js37
-rw-r--r--app/assets/javascripts/lib/utils/number_utils.js15
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js6
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js48
-rw-r--r--app/assets/javascripts/monitoring/components/charts/single_stat.vue37
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue87
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js3
-rw-r--r--app/assets/javascripts/monitoring/services/monitoring_service.js75
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js117
-rw-r--r--app/assets/javascripts/monitoring/stores/index.js21
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js12
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js45
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js12
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js (renamed from app/assets/javascripts/monitoring/stores/monitoring_store.js)44
-rw-r--r--app/assets/javascripts/mr_notes/stores/getters.js2
-rw-r--r--app/assets/javascripts/notes.js29
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue14
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue4
-rw-r--r--app/assets/javascripts/notes/components/discussion_locked_widget.vue20
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue14
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue6
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue2
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue45
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue11
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue4
-rw-r--r--app/assets/javascripts/notes/mixins/issuable_state.js15
-rw-r--r--app/assets/javascripts/notes/stores/getters.js2
-rw-r--r--app/assets/javascripts/operation_settings/components/external_dashboard.vue34
-rw-r--r--app/assets/javascripts/operation_settings/index.js9
-rw-r--r--app/assets/javascripts/operation_settings/store/actions.js38
-rw-r--r--app/assets/javascripts/operation_settings/store/index.js16
-rw-r--r--app/assets/javascripts/operation_settings/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/operation_settings/store/mutations.js7
-rw-r--r--app/assets/javascripts/operation_settings/store/state.js5
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/pages_domains/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pages_domains/form.js43
-rw-r--r--app/assets/javascripts/pages/projects/pages_domains/new/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue2
-rw-r--r--app/assets/javascripts/pages/projects/project.js12
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js1
-rw-r--r--app/assets/javascripts/pages/projects/settings/operations/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue5
-rw-r--r--app/assets/javascripts/pages/sessions/new/index.js2
-rw-r--r--app/assets/javascripts/pages/sessions/new/length_validator.js32
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_name_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue13
-rw-r--r--app/assets/javascripts/profile/account/index.js2
-rw-r--r--app/assets/javascripts/profile/profile.js1
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js6
-rw-r--r--app/assets/javascripts/registry/stores/mutations.js4
-rw-r--r--app/assets/javascripts/reports/components/issues_list.vue20
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue42
-rw-r--r--app/assets/javascripts/reports/constants.js6
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue61
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue111
-rw-r--r--app/assets/javascripts/repository/components/table/parent_row.vue37
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue37
-rw-r--r--app/assets/javascripts/repository/fragmentTypes.json1
-rw-r--r--app/assets/javascripts/repository/graphql.js65
-rw-r--r--app/assets/javascripts/repository/index.js38
-rw-r--r--app/assets/javascripts/repository/queries/getFiles.graphql61
-rw-r--r--app/assets/javascripts/repository/queries/getProjectPath.graphql3
-rw-r--r--app/assets/javascripts/repository/queries/getProjectShortPath.graphql3
-rw-r--r--app/assets/javascripts/repository/router.js19
-rw-r--r--app/assets/javascripts/repository/utils/icon.js2
-rw-r--r--app/assets/javascripts/repository/utils/title.js9
-rw-r--r--app/assets/javascripts/right_sidebar.js4
-rw-r--r--app/assets/javascripts/search_autocomplete.js6
-rw-r--r--app/assets/javascripts/serverless/components/functions.vue36
-rw-r--r--app/assets/javascripts/serverless/constants.js4
-rw-r--r--app/assets/javascripts/serverless/serverless_bundle.js3
-rw-r--r--app/assets/javascripts/serverless/store/actions.js29
-rw-r--r--app/assets/javascripts/serverless/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/serverless/store/mutations.js15
-rw-r--r--app/assets/javascripts/serverless/store/state.js1
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue6
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue6
-rw-r--r--app/assets/javascripts/test_utils/index.js4
-rw-r--r--app/assets/javascripts/usage_ping_consent.js3
-rw-r--r--app/assets/javascripts/users_select.js38
-rw-r--r--app/assets/javascripts/validators/input_validator.js34
-rw-r--r--app/assets/javascripts/visual_review_toolbar/index.js2
-rw-r--r--app/assets/javascripts/visual_review_toolbar/styles/toolbar.css149
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue13
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue79
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js13
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue32
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue89
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue13
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_warning.vue45
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue27
-rw-r--r--app/assets/javascripts/vue_shared/components/modal_copy_button.vue121
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/pagination/constants.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/pagination/graphql_pagination.vue47
-rw-r--r--app/assets/javascripts/vue_shared/components/pikaday.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue20
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue10
-rw-r--r--app/assets/javascripts/vue_shared/models/label.js13
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss4
-rw-r--r--app/assets/stylesheets/components/avatar.scss195
-rw-r--r--app/assets/stylesheets/components/popover.scss19
-rw-r--r--app/assets/stylesheets/errors.scss2
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/animations.scss24
-rw-r--r--app/assets/stylesheets/framework/avatar.scss194
-rw-r--r--app/assets/stylesheets/framework/awards.scss6
-rw-r--r--app/assets/stylesheets/framework/blocks.scss9
-rw-r--r--app/assets/stylesheets/framework/buttons.scss40
-rw-r--r--app/assets/stylesheets/framework/common.scss47
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss4
-rw-r--r--app/assets/stylesheets/framework/files.scss29
-rw-r--r--app/assets/stylesheets/framework/filters.scss2
-rw-r--r--app/assets/stylesheets/framework/flash.scss29
-rw-r--r--app/assets/stylesheets/framework/forms.scss113
-rw-r--r--app/assets/stylesheets/framework/highlight.scss3
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss4
-rw-r--r--app/assets/stylesheets/framework/lists.scss8
-rw-r--r--app/assets/stylesheets/framework/mixins.scss35
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss52
-rw-r--r--app/assets/stylesheets/framework/snippets.scss4
-rw-r--r--app/assets/stylesheets/framework/timeline.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss1
-rw-r--r--app/assets/stylesheets/framework/variables.scss3
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/_ide_mixins.scss14
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss2
-rw-r--r--app/assets/stylesheets/pages/boards.scss9
-rw-r--r--app/assets/stylesheets/pages/clusters.scss2
-rw-r--r--app/assets/stylesheets/pages/commits.scss10
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss1
-rw-r--r--app/assets/stylesheets/pages/diff.scss35
-rw-r--r--app/assets/stylesheets/pages/events.scss6
-rw-r--r--app/assets/stylesheets/pages/groups.scss3
-rw-r--r--app/assets/stylesheets/pages/issuable.scss19
-rw-r--r--app/assets/stylesheets/pages/issues.scss11
-rw-r--r--app/assets/stylesheets/pages/labels.scss3
-rw-r--r--app/assets/stylesheets/pages/login.scss16
-rw-r--r--app/assets/stylesheets/pages/members.scss59
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss95
-rw-r--r--app/assets/stylesheets/pages/note_form.scss41
-rw-r--r--app/assets/stylesheets/pages/notes.scss80
-rw-r--r--app/assets/stylesheets/pages/notifications.scss28
-rw-r--r--app/assets/stylesheets/pages/search.scss10
-rw-r--r--app/controllers/acme_challenges_controller.rb17
-rw-r--r--app/controllers/admin/logs_controller.rb3
-rw-r--r--app/controllers/admin/projects_controller.rb11
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/clusters/clusters_controller.rb3
-rw-r--r--app/controllers/concerns/import_url_params.rb19
-rw-r--r--app/controllers/concerns/issuable_collections.rb6
-rw-r--r--app/controllers/concerns/milestone_actions.rb8
-rw-r--r--app/controllers/dashboard/projects_controller.rb19
-rw-r--r--app/controllers/graphql_controller.rb47
-rw-r--r--app/controllers/import/fogbugz_controller.rb8
-rw-r--r--app/controllers/import/phabricator_controller.rb35
-rw-r--r--app/controllers/profiles/accounts_controller.rb2
-rw-r--r--app/controllers/profiles/emails_controller.rb4
-rw-r--r--app/controllers/profiles/groups_controller.rb24
-rw-r--r--app/controllers/profiles/notifications_controller.rb4
-rw-r--r--app/controllers/profiles/passwords_controller.rb10
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb6
-rw-r--r--app/controllers/projects/clusters_controller.rb4
-rw-r--r--app/controllers/projects/environments/prometheus_api_controller.rb2
-rw-r--r--app/controllers/projects/environments_controller.rb4
-rw-r--r--app/controllers/projects/git_http_client_controller.rb1
-rw-r--r--app/controllers/projects/imports_controller.rb7
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb10
-rw-r--r--app/controllers/projects/merge_requests_controller.rb44
-rw-r--r--app/controllers/projects/pages_domains_controller.rb4
-rw-r--r--app/controllers/projects/serverless/functions_controller.rb10
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb3
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/controllers/search_controller.rb1
-rw-r--r--app/controllers/sessions_controller.rb9
-rw-r--r--app/finders/clusters/knative_services_finder.rb112
-rw-r--r--app/finders/issuable_finder.rb8
-rw-r--r--app/finders/members_finder.rb43
-rw-r--r--app/finders/projects/serverless/functions_finder.rb40
-rw-r--r--app/graphql/gitlab_schema.rb42
-rw-r--r--app/graphql/mutations/merge_requests/base.rb2
-rw-r--r--app/graphql/resolvers/base_resolver.rb2
-rw-r--r--app/graphql/resolvers/concerns/resolves_pipelines.rb2
-rw-r--r--app/graphql/resolvers/issues_resolver.rb6
-rw-r--r--app/graphql/resolvers/merge_requests_resolver.rb4
-rw-r--r--app/graphql/resolvers/namespace_projects_resolver.rb33
-rw-r--r--app/graphql/resolvers/namespace_resolver.rb13
-rw-r--r--app/graphql/types/base_field.rb17
-rw-r--r--app/graphql/types/base_object.rb5
-rw-r--r--app/graphql/types/ci/pipeline_type.rb2
-rw-r--r--app/graphql/types/issue_type.rb6
-rw-r--r--app/graphql/types/merge_request_type.rb2
-rw-r--r--app/graphql/types/namespace_type.rb5
-rw-r--r--app/graphql/types/project_statistics_type.rb16
-rw-r--r--app/graphql/types/project_type.rb4
-rw-r--r--app/graphql/types/query_type.rb5
-rw-r--r--app/graphql/types/tree/blob_type.rb7
-rw-r--r--app/graphql/types/tree/tree_entry_type.rb4
-rw-r--r--app/graphql/types/tree/tree_type.rb10
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/helpers/ci_variables_helper.rb6
-rw-r--r--app/helpers/emails_helper.rb13
-rw-r--r--app/helpers/environments_helper.rb3
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/helpers/labels_helper.rb67
-rw-r--r--app/helpers/merge_requests_helper.rb2
-rw-r--r--app/helpers/notifications_helper.rb4
-rw-r--r--app/helpers/projects_helper.rb33
-rw-r--r--app/helpers/sorting_helper.rb55
-rw-r--r--app/helpers/storage_helper.rb3
-rw-r--r--app/helpers/tracking_helper.rb7
-rw-r--r--app/helpers/visibility_level_helper.rb38
-rw-r--r--app/mailers/emails/issues.rb4
-rw-r--r--app/mailers/emails/members.rb3
-rw-r--r--app/mailers/emails/merge_requests.rb2
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/mailers/emails/pages_domains.rb8
-rw-r--r--app/mailers/emails/projects.rb10
-rw-r--r--app/mailers/emails/remote_mirrors.rb2
-rw-r--r--app/mailers/notify.rb16
-rw-r--r--app/models/application_setting.rb6
-rw-r--r--app/models/application_setting_implementation.rb1
-rw-r--r--app/models/broadcast_message.rb11
-rw-r--r--app/models/ci/build.rb71
-rw-r--r--app/models/ci/job_artifact.rb15
-rw-r--r--app/models/ci/pipeline.rb10
-rw-r--r--app/models/ci/pipeline_schedule.rb26
-rw-r--r--app/models/clusters/applications/jupyter.rb11
-rw-r--r--app/models/clusters/applications/knative.rb48
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb52
-rw-r--r--app/models/clusters/platforms/kubernetes.rb1
-rw-r--r--app/models/commit.rb11
-rw-r--r--app/models/concerns/artifact_migratable.rb58
-rw-r--r--app/models/concerns/cache_markdown_field.rb88
-rw-r--r--app/models/concerns/maskable.rb4
-rw-r--r--app/models/concerns/milestoneish.rb20
-rw-r--r--app/models/concerns/noteable.rb12
-rw-r--r--app/models/concerns/prometheus_adapter.rb1
-rw-r--r--app/models/concerns/reactive_caching.rb4
-rw-r--r--app/models/concerns/referable.rb1
-rw-r--r--app/models/concerns/resolvable_note.rb2
-rw-r--r--app/models/concerns/taskable.rb7
-rw-r--r--app/models/concerns/update_project_statistics.rb52
-rw-r--r--app/models/diff_note.rb22
-rw-r--r--app/models/group.rb14
-rw-r--r--app/models/identity.rb1
-rw-r--r--app/models/issue.rb8
-rw-r--r--app/models/key.rb5
-rw-r--r--app/models/label.rb1
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/merge_request.rb43
-rw-r--r--app/models/milestone.rb11
-rw-r--r--app/models/namespace.rb5
-rw-r--r--app/models/notification_recipient.rb30
-rw-r--r--app/models/notification_setting.rb9
-rw-r--r--app/models/pages_domain.rb11
-rw-r--r--app/models/pages_domain_acme_order.rb24
-rw-r--r--app/models/project.rb15
-rw-r--r--app/models/project_auto_devops.rb25
-rw-r--r--app/models/project_ci_cd_setting.rb20
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/models/project_services/pipelines_email_service.rb18
-rw-r--r--app/models/project_services/youtrack_service.rb6
-rw-r--r--app/models/project_statistics.rb20
-rw-r--r--app/models/todo.rb3
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/presenters/blob_presenter.rb6
-rw-r--r--app/presenters/ci/build_runner_presenter.rb19
-rw-r--r--app/presenters/ci/pipeline_presenter.rb2
-rw-r--r--app/presenters/clusters/cluster_presenter.rb4
-rw-r--r--app/presenters/issue_presenter.rb12
-rw-r--r--app/presenters/label_presenter.rb8
-rw-r--r--app/presenters/member_presenter.rb5
-rw-r--r--app/presenters/merge_request_presenter.rb6
-rw-r--r--app/presenters/tree_entry_presenter.rb9
-rw-r--r--app/serializers/analytics_stage_entity.rb5
-rw-r--r--app/serializers/build_details_entity.rb15
-rw-r--r--app/serializers/deployment_entity.rb33
-rw-r--r--app/serializers/issue_entity.rb8
-rw-r--r--app/serializers/job_artifact_report_entity.rb13
-rw-r--r--app/serializers/merge_request_widget_entity.rb10
-rw-r--r--app/serializers/pipeline_details_entity.rb5
-rw-r--r--app/serializers/pipeline_entity.rb3
-rw-r--r--app/services/auto_merge/base_service.rb63
-rw-r--r--app/services/auto_merge/merge_when_pipeline_succeeds_service.rb30
-rw-r--r--app/services/auto_merge_service.rb56
-rw-r--r--app/services/ci/pipeline_schedule_service.rb13
-rw-r--r--app/services/clusters/applications/base_service.rb2
-rw-r--r--app/services/issuable/clone/content_rewriter.rb1
-rw-r--r--app/services/issues/close_service.rb2
-rw-r--r--app/services/merge_requests/base_service.rb30
-rw-r--r--app/services/merge_requests/close_service.rb1
-rw-r--r--app/services/merge_requests/create_pipeline_service.rb37
-rw-r--r--app/services/merge_requests/merge_when_pipeline_succeeds_service.rb47
-rw-r--r--app/services/merge_requests/refresh_service.rb8
-rw-r--r--app/services/merge_requests/update_service.rb4
-rw-r--r--app/services/notification_service.rb2
-rw-r--r--app/services/pages_domains/create_acme_order_service.rb31
-rw-r--r--app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb41
-rw-r--r--app/services/preview_markdown_service.rb4
-rw-r--r--app/services/projects/fork_service.rb20
-rw-r--r--app/services/projects/update_service.rb1
-rw-r--r--app/services/projects/update_statistics_service.rb2
-rw-r--r--app/services/search_service.rb4
-rw-r--r--app/services/system_note_service.rb12
-rw-r--r--app/services/todos/destroy/base_service.rb2
-rw-r--r--app/services/todos/destroy/confidential_issue_service.rb35
-rw-r--r--app/uploaders/legacy_artifact_uploader.rb27
-rw-r--r--app/views/abuse_reports/new.html.haml14
-rw-r--r--app/views/admin/application_settings/_external_authorization_service_form.html.haml2
-rw-r--r--app/views/admin/application_settings/_outbound.html.haml8
-rw-r--r--app/views/admin/application_settings/_performance_bar.html.haml2
-rw-r--r--app/views/admin/application_settings/metrics_and_profiling.html.haml2
-rw-r--r--app/views/admin/applications/_form.html.haml12
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml15
-rw-r--r--app/views/admin/groups/_form.html.haml3
-rw-r--r--app/views/admin/identities/_form.html.haml6
-rw-r--r--app/views/admin/labels/_form.html.haml14
-rw-r--r--app/views/admin/projects/_projects.html.haml4
-rw-r--r--app/views/admin/projects/show.html.haml3
-rw-r--r--app/views/admin/users/_access_levels.html.haml22
-rw-r--r--app/views/admin/users/_form.html.haml62
-rw-r--r--app/views/admin/users/show.html.haml2
-rw-r--r--app/views/award_emoji/_awards_block.html.haml6
-rw-r--r--app/views/ci/variables/_content.html.haml2
-rw-r--r--app/views/ci/variables/_index.html.haml2
-rw-r--r--app/views/ci/variables/_variable_row.html.haml4
-rw-r--r--app/views/clusters/clusters/_banner.html.haml12
-rw-r--r--app/views/clusters/clusters/show.html.haml3
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml24
-rw-r--r--app/views/discussions/_notes.html.haml20
-rw-r--r--app/views/groups/_create_chat_team.html.haml9
-rw-r--r--app/views/groups/_group_admin_settings.html.haml9
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/import/bitbucket_server/status.html.haml2
-rw-r--r--app/views/import/gitlab_projects/new.html.haml23
-rw-r--r--app/views/import/manifest/new.html.haml6
-rw-r--r--app/views/import/phabricator/new.html.haml25
-rw-r--r--app/views/import/shared/_errors.html.haml4
-rw-r--r--app/views/import/shared/_new_project_form.html.haml21
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/devise.html.haml12
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml32
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/notify/closed_issue_email.html.haml2
-rw-r--r--app/views/notify/closed_issue_email.text.haml2
-rw-r--r--app/views/notify/new_user_email.html.haml3
-rw-r--r--app/views/notify/new_user_email.text.erb11
-rw-r--r--app/views/profiles/_event_table.html.haml6
-rw-r--r--app/views/profiles/active_sessions/_active_session.html.haml11
-rw-r--r--app/views/profiles/active_sessions/index.html.haml4
-rw-r--r--app/views/profiles/audit_log.html.haml4
-rw-r--r--app/views/profiles/chat_names/_chat_name.html.haml4
-rw-r--r--app/views/profiles/chat_names/index.html.haml16
-rw-r--r--app/views/profiles/emails/index.html.haml44
-rw-r--r--app/views/profiles/gpg_keys/_form.html.haml6
-rw-r--r--app/views/profiles/gpg_keys/_key.html.haml16
-rw-r--r--app/views/profiles/gpg_keys/_key_table.html.haml4
-rw-r--r--app/views/profiles/gpg_keys/index.html.haml12
-rw-r--r--app/views/profiles/keys/_form.html.haml8
-rw-r--r--app/views/profiles/keys/_key.html.haml9
-rw-r--r--app/views/profiles/keys/_key_details.html.haml13
-rw-r--r--app/views/profiles/keys/_key_table.html.haml4
-rw-r--r--app/views/profiles/keys/index.html.haml8
-rw-r--r--app/views/profiles/keys/show.html.haml2
-rw-r--r--app/views/profiles/notifications/_group_settings.html.haml23
-rw-r--r--app/views/profiles/notifications/show.html.haml21
-rw-r--r--app/views/profiles/passwords/edit.html.haml25
-rw-r--r--app/views/profiles/passwords/new.html.haml15
-rw-r--r--app/views/profiles/preferences/show.html.haml27
-rw-r--r--app/views/profiles/show.html.haml6
-rw-r--r--app/views/profiles/two_factor_auths/_codes.html.haml10
-rw-r--r--app/views/profiles/two_factor_auths/codes.html.haml5
-rw-r--r--app/views/profiles/two_factor_auths/create.html.haml4
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml61
-rw-r--r--app/views/projects/_files.html.haml7
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/_import_project_pane.html.haml28
-rw-r--r--app/views/projects/_merge_request_merge_checks_settings.html.haml2
-rw-r--r--app/views/projects/_new_project_fields.html.haml4
-rw-r--r--app/views/projects/blob/_header_content.html.haml2
-rw-r--r--app/views/projects/buttons/_download_links.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml5
-rw-r--r--app/views/projects/commit/_commit_box.html.haml4
-rw-r--r--app/views/projects/commits/_commit.html.haml6
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml2
-rw-r--r--app/views/projects/diffs/_text_file.html.haml2
-rw-r--r--app/views/projects/issues/_new_branch.html.haml8
-rw-r--r--app/views/projects/jobs/_table.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml43
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/notes/_actions.html.haml6
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml2
-rw-r--r--app/views/projects/pages_domains/_form.html.haml91
-rw-r--r--app/views/projects/pages_domains/_helper_text.html.haml9
-rw-r--r--app/views/projects/pages_domains/edit.html.haml1
-rw-r--r--app/views/projects/pages_domains/new.html.haml1
-rw-r--r--app/views/projects/project_members/index.html.haml42
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml4
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml4
-rw-r--r--app/views/projects/registry/repositories/_tag.html.haml2
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml40
-rw-r--r--app/views/projects/services/slack_slash_commands/_help.html.haml38
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml8
-rw-r--r--app/views/projects/settings/operations/_error_tracking.html.haml4
-rw-r--r--app/views/projects/settings/operations/_external_dashboard.html.haml3
-rw-r--r--app/views/projects/settings/operations/show.html.haml2
-rw-r--r--app/views/projects/settings/repository/_protected_branches.html.haml2
-rw-r--r--app/views/projects/settings/repository/show.html.haml7
-rw-r--r--app/views/projects/tags/index.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/projects/tree/_readme.html.haml2
-rw-r--r--app/views/projects/tree/_tree_commit_column.html.haml3
-rw-r--r--app/views/projects/tree/_tree_header.html.haml121
-rw-r--r--app/views/repository_check_mailer/notify.html.haml2
-rw-r--r--app/views/search/_form.html.haml1
-rw-r--r--app/views/search/_results.html.haml5
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml2
-rw-r--r--app/views/search/results/_milestone.html.haml2
-rw-r--r--app/views/sent_notifications/unsubscribe.html.haml2
-rw-r--r--app/views/shared/_delete_label_modal.html.haml2
-rw-r--r--app/views/shared/_email_with_badge.html.haml2
-rw-r--r--app/views/shared/_import_form.html.haml27
-rw-r--r--app/views/shared/_label.html.haml2
-rw-r--r--app/views/shared/_old_visibility_level.html.haml2
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml2
-rw-r--r--app/views/shared/icons/_emoji_slightly_smiling_face.svg1
-rw-r--r--app/views/shared/icons/_emoji_smile.svg1
-rw-r--r--app/views/shared/icons/_emoji_smiley.svg1
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml4
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml15
-rw-r--r--app/views/shared/labels/_form.html.haml16
-rw-r--r--app/views/shared/members/_group.html.haml19
-rw-r--r--app/views/shared/members/_member.html.haml41
-rw-r--r--app/views/shared/notes/_hints.html.haml5
-rw-r--r--app/views/shared/notes/_note.html.haml2
-rw-r--r--app/views/shared/notifications/_button.html.haml14
-rw-r--r--app/views/shared/notifications/_new_button.html.haml2
-rw-r--r--app/views/shared/notifications/_notification_dropdown.html.haml2
-rw-r--r--app/views/shared/snippets/_form.html.haml6
-rw-r--r--app/views/snippets/notes/_actions.html.haml6
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/all_queues.yml2
-rw-r--r--app/workers/auto_merge_process_worker.rb14
-rw-r--r--app/workers/pages_domain_verification_cron_worker.rb2
-rw-r--r--app/workers/pages_domain_verification_worker.rb2
-rw-r--r--app/workers/pipeline_schedule_worker.rb41
-rw-r--r--app/workers/pipeline_success_worker.rb8
-rw-r--r--app/workers/post_receive.rb2
-rw-r--r--app/workers/project_cache_worker.rb4
-rw-r--r--app/workers/run_pipeline_schedule_worker.rb26
-rw-r--r--app/workers/todos_destroyer/confidential_issue_worker.rb4
-rw-r--r--babel.config.js2
-rw-r--r--changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml5
-rw-r--r--changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml5
-rw-r--r--changelogs/unreleased/11105-fix-cs-with-proxy.yml5
-rw-r--r--changelogs/unreleased/11204-turn-off-mask-by-default.yml5
-rw-r--r--changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml5
-rw-r--r--changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml5
-rw-r--r--changelogs/unreleased/28996-create-mvc-ui-in-haml.yml5
-rw-r--r--changelogs/unreleased/30138-display-cycle-analytics-issue.yml5
-rw-r--r--changelogs/unreleased/33064-add-labels-to-note-event-payload.yml5
-rw-r--r--changelogs/unreleased/37495.yml5
-rw-r--r--changelogs/unreleased/38561-border-radii.yml5
-rw-r--r--changelogs/unreleased/45687-web-ide-empty-state.yml5
-rw-r--r--changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml5
-rw-r--r--changelogs/unreleased/49041-issue-board-input-height.yml5
-rw-r--r--changelogs/unreleased/50106-hide-whitespace-changes.yml5
-rw-r--r--changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml5
-rw-r--r--changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml5
-rw-r--r--changelogs/unreleased/51636-task-list-api-pderichs.yml5
-rw-r--r--changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml5
-rw-r--r--changelogs/unreleased/53134-multiple-extendes-for-a-job.yml5
-rw-r--r--changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml5
-rw-r--r--changelogs/unreleased/54169-flash-warning-rebrush.yml5
-rw-r--r--changelogs/unreleased/55033-discussion-system-note-alignment.yml5
-rw-r--r--changelogs/unreleased/55125-mr-tab-scrolling.yml5
-rw-r--r--changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml5
-rw-r--r--changelogs/unreleased/55447-validate-k8s-credentials.yml5
-rw-r--r--changelogs/unreleased/56959-drop-project_auto_devops_domain.yml5
-rw-r--r--changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml5
-rw-r--r--changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml5
-rw-r--r--changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml5
-rw-r--r--changelogs/unreleased/57414-show-pipeline-iid.yml5
-rw-r--r--changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml5
-rw-r--r--changelogs/unreleased/58269-separate-update-patch.yml5
-rw-r--r--changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml5
-rw-r--r--changelogs/unreleased/58632-fix-mr-widget-padding.yml5
-rw-r--r--changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml5
-rw-r--r--changelogs/unreleased/58984-doc-missing-milestones-and-labels-links.yml5
-rw-r--r--changelogs/unreleased/59026-replace-favicon.yml5
-rw-r--r--changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml5
-rw-r--r--changelogs/unreleased/59587-add-graphql-logging.yml5
-rw-r--r--changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml5
-rw-r--r--changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml5
-rw-r--r--changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml5
-rw-r--r--changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml5
-rw-r--r--changelogs/unreleased/60819_yamllint_gitlabci.yml5
-rw-r--r--changelogs/unreleased/609120-ref-link.yml5
-rw-r--r--changelogs/unreleased/60987-emoji-picker-popup.yml5
-rw-r--r--changelogs/unreleased/61024-update-resolved-icon.yml5
-rw-r--r--changelogs/unreleased/6104-ee-ce-difference.yml5
-rw-r--r--changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml5
-rw-r--r--changelogs/unreleased/61072-link-to-user-profile-not-distinguishable-on-latest-commit-widget.yml5
-rw-r--r--changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml5
-rw-r--r--changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml5
-rw-r--r--changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml2
-rw-r--r--changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml5
-rw-r--r--changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml5
-rw-r--r--changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml5
-rw-r--r--changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml5
-rw-r--r--changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml5
-rw-r--r--changelogs/unreleased/61821-tooltip-consistency.yml5
-rw-r--r--changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml5
-rw-r--r--changelogs/unreleased/61880-download-btn-group.yml5
-rw-r--r--changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml5
-rw-r--r--changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml5
-rw-r--r--changelogs/unreleased/62091-remove-time-windows-flag.yml5
-rw-r--r--changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml5
-rw-r--r--changelogs/unreleased/62107-fix-detail-page-header-height.yml5
-rw-r--r--changelogs/unreleased/62124-new-threaded-discussion-design.yml5
-rw-r--r--changelogs/unreleased/62144-fix-option-dropdown-button-size.yml5
-rw-r--r--changelogs/unreleased/62154-fe-create-fix-long-branch-name-in-dropdown.yml6
-rw-r--r--changelogs/unreleased/62227-webkit-icon-overlap.yml5
-rw-r--r--changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml5
-rw-r--r--changelogs/unreleased/62408-dropdown-truncate.yml5
-rw-r--r--changelogs/unreleased/62418-project-default-git-depth.yml5
-rw-r--r--changelogs/unreleased/62432-fix-participants-wrapping.yml5
-rw-r--r--changelogs/unreleased/62485-label-weights.yml5
-rw-r--r--changelogs/unreleased/62487-external-policy-desc.yml5
-rw-r--r--changelogs/unreleased/62656-adjusted-dropdown-styles.yml5
-rw-r--r--changelogs/unreleased/62788-graphql-pagination.yml5
-rw-r--r--changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml5
-rw-r--r--changelogs/unreleased/62974-follow-up-from-wip-align-merge-request-icons-and-text.yml5
-rw-r--r--changelogs/unreleased/9121-sort-relative-position.yml5
-rw-r--r--changelogs/unreleased/9578-adjust-milestone-completion-rate.yml5
-rw-r--r--changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml5
-rw-r--r--changelogs/unreleased/abstract-auto-merge.yml5
-rw-r--r--changelogs/unreleased/ac-63020-typeerror-nil-can-t-be-coerced-into-integer.yml5
-rw-r--r--changelogs/unreleased/ac-graphql-stats.yml5
-rw-r--r--changelogs/unreleased/ac-graphql-wikisize.yml5
-rw-r--r--changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml5
-rw-r--r--changelogs/unreleased/add-constraint-for-milestone-dates.yml5
-rw-r--r--changelogs/unreleased/add-lfs-blob-ids-to-tree-type.yml5
-rw-r--r--changelogs/unreleased/add-wiki-size-to-statistics.yml5
-rw-r--r--changelogs/unreleased/allow-emoji-in-references.yml5
-rw-r--r--changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml5
-rw-r--r--changelogs/unreleased/api_make_protected_boolean_type.yml5
-rw-r--r--changelogs/unreleased/api_masked_variables.yml5
-rw-r--r--changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml5
-rw-r--r--changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml5
-rw-r--r--changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml5
-rw-r--r--changelogs/unreleased/bvl-graphql-multiplex.yml5
-rw-r--r--changelogs/unreleased/bvl-use-global-ids-graphql.yml5
-rw-r--r--changelogs/unreleased/cancel-auto-merge-when-branch-is-changed.yml5
-rw-r--r--changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml5
-rw-r--r--changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml5
-rw-r--r--changelogs/unreleased/chore-remove-circuit-breaker-api.yml5
-rw-r--r--changelogs/unreleased/ci-variable-conjunction.yml5
-rw-r--r--changelogs/unreleased/copy-button-in-modals.yml5
-rw-r--r--changelogs/unreleased/dm-http-hostname-override.yml5
-rw-r--r--changelogs/unreleased/docs-add-chatops-request-doc.yml5
-rw-r--r--changelogs/unreleased/ee-11040-added-conditional-rendering.yml5
-rw-r--r--changelogs/unreleased/fe-fix-gl-dropdown-scrolling-to-top.yml5
-rw-r--r--changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml5
-rw-r--r--changelogs/unreleased/fix-allow-lower-case-issue-ids.yml5
-rw-r--r--changelogs/unreleased/fix-autodevops-postgres-versioning.yml5
-rw-r--r--changelogs/unreleased/fix-format-date-safari-ff.yml5
-rw-r--r--changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml5
-rw-r--r--changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml5
-rw-r--r--changelogs/unreleased/fix-import-member-access.yml5
-rw-r--r--changelogs/unreleased/fix-import-param-ordering.yml5
-rw-r--r--changelogs/unreleased/fix-issue-mr-badge.yml5
-rw-r--r--changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml5
-rw-r--r--changelogs/unreleased/fix-pipeline-schedule-owner-is-nil.yml5
-rw-r--r--changelogs/unreleased/fix-search-dropdown-blur-close.yml5
-rw-r--r--changelogs/unreleased/fix-time-window-default.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.43.0.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-profiles-preferences.yml5
-rw-r--r--changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml5
-rw-r--r--changelogs/unreleased/i18n-active_sessions-in-user-profile.yml5
-rw-r--r--changelogs/unreleased/i18n-chat-of-user-profile.yml5
-rw-r--r--changelogs/unreleased/i18n-email-of-user-profile.yml5
-rw-r--r--changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml5
-rw-r--r--changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml5
-rw-r--r--changelogs/unreleased/ignore-artifact-attirbutes-in-project-import-export.yml5
-rw-r--r--changelogs/unreleased/improve-email-text-part.yml5
-rw-r--r--changelogs/unreleased/increase-move-issue-dropdown-height.yml5
-rw-r--r--changelogs/unreleased/issue_49897.yml5
-rw-r--r--changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml5
-rw-r--r--changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml5
-rw-r--r--changelogs/unreleased/osw-avoid-encoding-errors-on-merge-to-ref-service.yml5
-rw-r--r--changelogs/unreleased/osw-fix-post-dep-migration-with-timeout.yml5
-rw-r--r--changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml5
-rw-r--r--changelogs/unreleased/patch-65.yml5
-rw-r--r--changelogs/unreleased/pb-update-gitaly-1-45-0.yml5
-rw-r--r--changelogs/unreleased/pipelines-email-default-branch-filter.yml5
-rw-r--r--changelogs/unreleased/referenced-labels.yml5
-rw-r--r--changelogs/unreleased/relative-urls-for-system-notes.yml5
-rw-r--r--changelogs/unreleased/remove-legacy-artifacts-related-code.yml5
-rw-r--r--changelogs/unreleased/remove-mr-diff-header-height.yml5
-rw-r--r--changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml5
-rw-r--r--changelogs/unreleased/security-60039.yml5
-rw-r--r--changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml5
-rw-r--r--changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml5
-rw-r--r--changelogs/unreleased/security-fix-project-existence-disclosure-master.yml5
-rw-r--r--changelogs/unreleased/security-fix_milestones_search_api_leak.yml5
-rw-r--r--changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml5
-rw-r--r--changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml5
-rw-r--r--changelogs/unreleased/security-unsubscribing-from-issue.yml5
-rw-r--r--changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml5
-rw-r--r--changelogs/unreleased/sh-add-backtrace-to-sql-queries.yml5
-rw-r--r--changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml5
-rw-r--r--changelogs/unreleased/sh-default-visibility-fix.yml5
-rw-r--r--changelogs/unreleased/sh-fix-fogbugz-import.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-55869.yml5
-rw-r--r--changelogs/unreleased/sh-fix-utf-8-encoding-resolve-conflicts.yml5
-rw-r--r--changelogs/unreleased/sh-project-import-visibility-error.yml5
-rw-r--r--changelogs/unreleased/t1.yml5
-rw-r--r--changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-shell-9-3-0.yml5
-rw-r--r--changelogs/unreleased/update-pages.yml5
-rw-r--r--changelogs/unreleased/update-psd-doc.yml5
-rw-r--r--changelogs/unreleased/update-smileys-new.yml5
-rw-r--r--changelogs/unreleased/variables-boolean-type.yml5
-rw-r--r--changelogs/unreleased/weimeng-email-routing.yml5
-rw-r--r--changelogs/unreleased/zj-remove-delta-island-feature-flag.yml3
-rw-r--r--config/database.yml.example0
-rw-r--r--config/environments/development.rb3
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/01_secret_token.rb7
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/initializers/7_prometheus_metrics.rb14
-rw-r--r--config/initializers/hipchat_client_patch.rb6
-rw-r--r--config/initializers/http_hostname_override.rb49
-rw-r--r--config/initializers/rack_attack_logging.rb10
-rw-r--r--config/initializers/rack_timeout.rb23
-rw-r--r--config/karma.config.js26
-rw-r--r--config/puma.example.development.rb1
-rw-r--r--config/routes.rb2
-rw-r--r--config/routes/admin.rb2
-rw-r--r--config/routes/import.rb2
-rw-r--r--config/routes/profile.rb6
-rw-r--r--config/routes/project.rb284
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--config/webpack.config.review_toolbar.js58
-rw-r--r--danger/commit_messages/Dangerfile4
-rw-r--r--danger/plugins/helper.rb3
-rw-r--r--danger/plugins/roulette.rb10
-rw-r--r--danger/roulette/Dangerfile57
-rw-r--r--danger/single_codebase/Dangerfile29
-rw-r--r--db/migrate/20180702134423_generate_missing_routes.rb1
-rw-r--r--db/migrate/20190327163904_add_notification_email_to_notification_settings.rb11
-rw-r--r--db/migrate/20190429082448_create_pages_domain_acme_orders.rb28
-rw-r--r--db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb16
-rw-r--r--db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb24
-rw-r--r--db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb38
-rw-r--r--db/migrate/20190524062810_generate_lets_encrypt_private_key.rb18
-rw-r--r--db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb16
-rw-r--r--db/migrate/20190527194830_add_wiki_size_to_statistics.rb9
-rw-r--r--db/migrate/20190529142545_add_dns_rebinding_protection_enabled_to_application_settings.rb23
-rw-r--r--db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb11
-rw-r--r--db/migrate/20190530154715_add_index_to_merge_requests_state_and_merge_status.rb21
-rw-r--r--db/migrate/20190611161641_add_target_project_id_to_merge_trains.rb14
-rw-r--r--db/post_migrate/20190522143720_drop_project_auto_devops_domain.rb11
-rw-r--r--db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb34
-rw-r--r--db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb32
-rw-r--r--db/post_migrate/20190528180441_enqueue_reset_merge_status.rb25
-rw-r--r--db/schema.rb33
-rw-r--r--doc/administration/auth/google_secure_ldap.md2
-rw-r--r--doc/administration/auth/oidc.md58
-rw-r--r--doc/administration/compliance.md4
-rw-r--r--doc/administration/custom_hooks.md5
-rw-r--r--doc/administration/geo/disaster_recovery/background_verification.md40
-rw-r--r--doc/administration/geo/disaster_recovery/planned_failover.md2
-rw-r--r--doc/administration/geo/replication/configuration.md8
-rw-r--r--doc/administration/geo/replication/configuration_source.md172
-rw-r--r--doc/administration/geo/replication/database.md27
-rw-r--r--doc/administration/geo/replication/database_source.md439
-rw-r--r--doc/administration/geo/replication/external_database.md2
-rw-r--r--doc/administration/geo/replication/high_availability.md4
-rw-r--r--doc/administration/geo/replication/index.md23
-rw-r--r--doc/administration/geo/replication/security_review.md4
-rw-r--r--doc/administration/geo/replication/troubleshooting.md85
-rw-r--r--doc/administration/geo/replication/tuning.md2
-rw-r--r--doc/administration/geo/replication/updating_the_geo_nodes.md49
-rw-r--r--doc/administration/gitaly/index.md36
-rw-r--r--doc/administration/high_availability/README.md23
-rw-r--r--doc/administration/high_availability/database.md119
-rw-r--r--doc/administration/high_availability/gitaly.md81
-rw-r--r--doc/administration/high_availability/img/fully-distributed.pngbin0 -> 46918 bytes
-rw-r--r--doc/administration/high_availability/img/geo-ha-diagram.pngbin0 -> 43826 bytes
-rw-r--r--doc/administration/high_availability/img/horizontal.pngbin0 -> 18660 bytes
-rw-r--r--doc/administration/high_availability/img/hybrid.pngbin0 -> 20698 bytes
-rw-r--r--doc/administration/high_availability/img/pg_ha_architecture.png (renamed from doc/administration/high_availability/pg_ha_architecture.png)bin18412 -> 18412 bytes
-rw-r--r--doc/administration/high_availability/nfs.md27
-rw-r--r--doc/administration/high_availability/redis.md20
-rw-r--r--doc/administration/incoming_email.md2
-rw-r--r--doc/administration/index.md37
-rw-r--r--doc/administration/integration/plantuml.md2
-rw-r--r--doc/administration/integration/terminal.md5
-rw-r--r--doc/administration/job_artifacts.md14
-rw-r--r--doc/administration/logs.md25
-rw-r--r--doc/administration/monitoring/performance/grafana_configuration.md8
-rw-r--r--doc/administration/monitoring/performance/index.md2
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md36
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md4
-rw-r--r--doc/administration/packages.md6
-rw-r--r--doc/administration/pages/index.md15
-rw-r--r--doc/administration/raketasks/maintenance.md19
-rw-r--r--doc/administration/raketasks/project_import_export.md1
-rw-r--r--doc/administration/repository_storage_paths.md2
-rw-r--r--doc/administration/repository_storage_types.md5
-rw-r--r--doc/analytics/README.md4
-rw-r--r--doc/analytics/contribution_analytics.md4
-rw-r--r--doc/api/README.md142
-rw-r--r--doc/api/boards.md179
-rw-r--r--doc/api/commits.md1
-rw-r--r--doc/api/discussions.md216
-rw-r--r--doc/api/epic_issues.md1
-rw-r--r--doc/api/epic_links.md4
-rw-r--r--doc/api/geo_nodes.md12
-rw-r--r--doc/api/graphql/index.md9
-rw-r--r--doc/api/groups.md1
-rw-r--r--doc/api/issues.md50
-rw-r--r--doc/api/jobs.md103
-rw-r--r--doc/api/members.md4
-rw-r--r--doc/api/merge_requests.md74
-rw-r--r--doc/api/notes.md130
-rw-r--r--doc/api/project_clusters.md2
-rw-r--r--doc/api/project_level_variables.md47
-rw-r--r--doc/api/projects.md5
-rw-r--r--doc/api/repository_storage_health.md5
-rw-r--r--doc/api/resource_label_events.md86
-rw-r--r--doc/api/scim.md2
-rw-r--r--doc/api/search.md193
-rw-r--r--doc/api/services.md70
-rw-r--r--doc/api/snippets.md10
-rw-r--r--doc/api/users.md6
-rw-r--r--doc/api/vulnerabilities.md2
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ee/index.md4
-rw-r--r--doc/ci/README.md39
-rw-r--r--doc/ci/caching/index.md26
-rw-r--r--doc/ci/chatops/README.md27
-rw-r--r--doc/ci/ci_cd_for_external_repos/bitbucket_integration.md49
-rw-r--r--doc/ci/ci_cd_for_external_repos/github_integration.md69
-rw-r--r--doc/ci/ci_cd_for_external_repos/index.md26
-rw-r--r--doc/ci/docker/README.md4
-rw-r--r--doc/ci/docker/using_docker_build.md59
-rw-r--r--doc/ci/docker/using_docker_images.md158
-rw-r--r--doc/ci/docker/using_kaniko.md42
-rw-r--r--doc/ci/enable_or_disable_ci.md61
-rw-r--r--doc/ci/environments.md47
-rw-r--r--doc/ci/environments/protected_environments.md16
-rw-r--r--doc/ci/examples/README.md67
-rw-r--r--doc/ci/examples/artifactory_and_gitlab/index.md5
-rw-r--r--doc/ci/examples/browser_performance.md26
-rw-r--r--doc/ci/examples/code_quality.md4
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md9
-rw-r--r--doc/ci/examples/deployment/README.md14
-rw-r--r--doc/ci/examples/deployment/composer-npm-deploy.md24
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md3
-rw-r--r--doc/ci/examples/end_to_end_testing_webdriverio/index.md5
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/index.md1
-rw-r--r--doc/ci/examples/php.md12
-rw-r--r--doc/ci/examples/test-and-deploy-python-application-to-heroku.md19
-rw-r--r--doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md17
-rw-r--r--doc/ci/examples/test-clojure-application.md20
-rw-r--r--doc/ci/examples/test-scala-application.md21
-rw-r--r--doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md7
-rw-r--r--doc/ci/git_submodules.md4
-rw-r--r--doc/ci/interactive_web_terminal/index.md4
-rw-r--r--doc/ci/introduction/index.md4
-rw-r--r--doc/ci/junit_test_reports.md4
-rw-r--r--doc/ci/large_repositories/index.md4
-rw-r--r--doc/ci/merge_request_pipelines/index.md4
-rw-r--r--doc/ci/metrics_reports.md9
-rw-r--r--doc/ci/multi_project_pipelines.md37
-rw-r--r--doc/ci/pipelines.md7
-rw-r--r--doc/ci/quick_start/README.md22
-rw-r--r--doc/ci/review_apps/index.md8
-rw-r--r--doc/ci/runners/README.md4
-rw-r--r--doc/ci/services/README.md13
-rw-r--r--doc/ci/services/mysql.md4
-rw-r--r--doc/ci/services/postgres.md4
-rw-r--r--doc/ci/services/redis.md4
-rw-r--r--doc/ci/ssh_keys/README.md1
-rw-r--r--doc/ci/triggers/README.md4
-rw-r--r--doc/ci/variables/README.md47
-rw-r--r--doc/ci/variables/deprecated_variables.md7
-rw-r--r--doc/ci/variables/predefined_variables.md4
-rw-r--r--doc/ci/variables/where_variables_can_be_used.md4
-rw-r--r--doc/ci/yaml/README.md95
-rw-r--r--doc/customization/issue_and_merge_request_template.md4
-rw-r--r--doc/development/README.md2
-rw-r--r--doc/development/api_graphql_styleguide.md21
-rw-r--r--doc/development/architecture.md245
-rw-r--r--doc/development/chatops_on_gitlabcom.md21
-rw-r--r--doc/development/code_review.md10
-rw-r--r--doc/development/contributing/index.md2
-rw-r--r--doc/development/contributing/issue_workflow.md47
-rw-r--r--doc/development/database_debugging.md2
-rw-r--r--doc/development/distributed_tracing.md2
-rw-r--r--doc/development/documentation/feature-change-workflow.md12
-rw-r--r--doc/development/documentation/index.md13
-rw-r--r--doc/development/documentation/structure.md8
-rw-r--r--doc/development/ee_features.md68
-rw-r--r--doc/development/elasticsearch.md6
-rw-r--r--doc/development/fe_guide/frontend_faq.md14
-rw-r--r--doc/development/fe_guide/style_guide_js.md20
-rw-r--r--doc/development/fe_guide/style_guide_scss.md40
-rw-r--r--doc/development/fe_guide/vue.md2
-rw-r--r--doc/development/feature_flags.md6
-rw-r--r--doc/development/geo.md82
-rw-r--r--doc/development/git_object_deduplication.md109
-rw-r--r--doc/development/go_guide/index.md6
-rw-r--r--doc/development/i18n/externalization.md6
-rw-r--r--doc/development/i18n/merging_translations.md2
-rw-r--r--doc/development/i18n/proofreader.md4
-rw-r--r--doc/development/i18n/translation.md8
-rw-r--r--doc/development/img/architecture_simplified.pngbin0 -> 61590 bytes
-rw-r--r--doc/development/import_export.md2
-rw-r--r--doc/development/licensed_feature_availability.md8
-rw-r--r--doc/development/licensing.md6
-rw-r--r--doc/development/migration_style_guide.md6
-rw-r--r--doc/development/new_fe_guide/development/accessibility.md1
-rw-r--r--doc/development/new_fe_guide/development/components.md2
-rw-r--r--doc/development/new_fe_guide/tips.md12
-rw-r--r--doc/development/packages.md6
-rw-r--r--doc/development/rake_tasks.md15
-rw-r--r--doc/development/rolling_out_changes_using_feature_flags.md9
-rw-r--r--doc/development/routing.md63
-rw-r--r--doc/development/testing_guide/best_practices.md34
-rw-r--r--doc/development/testing_guide/end_to_end/best_practices.md (renamed from qa/docs/BEST_PRACTICES.md)6
-rw-r--r--doc/development/testing_guide/end_to_end/dynamic_element_validation.md113
-rw-r--r--doc/development/testing_guide/end_to_end/index.md (renamed from doc/development/testing_guide/end_to_end_tests.md)10
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md (renamed from qa/qa/page/README.md)39
-rw-r--r--doc/development/testing_guide/end_to_end/quick_start_guide.md585
-rw-r--r--doc/development/testing_guide/end_to_end/resources.md (renamed from qa/qa/resource/README.md)6
-rw-r--r--doc/development/testing_guide/end_to_end/style_guide.md99
-rw-r--r--doc/development/testing_guide/frontend_testing.md48
-rw-r--r--doc/development/testing_guide/index.md2
-rw-r--r--doc/development/testing_guide/review_apps.md4
-rw-r--r--doc/development/testing_guide/smoke.md2
-rw-r--r--doc/development/testing_guide/testing_levels.md14
-rw-r--r--doc/development/understanding_explain_plans.md47
-rw-r--r--doc/git_hooks/git_hooks.md4
-rw-r--r--doc/gitlab-basics/README.md2
-rw-r--r--doc/gitlab-basics/create-project.md2
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md30
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.pngbin10534 -> 0 bytes
-rw-r--r--doc/gitlab-geo/configuration_source.md4
-rw-r--r--doc/gitlab-geo/database_source.md4
-rw-r--r--doc/install/README.md3
-rw-r--r--doc/install/aws/index.md24
-rw-r--r--doc/install/azure/index.md20
-rw-r--r--doc/install/database_mysql.md16
-rw-r--r--doc/install/digitaloceandocker.md41
-rw-r--r--doc/install/docker.md6
-rw-r--r--doc/install/google-protobuf.md29
-rw-r--r--doc/install/google_cloud_platform/img/gcp_landing.pngbin9978 -> 0 bytes
-rw-r--r--doc/install/google_cloud_platform/index.md24
-rw-r--r--doc/install/installation.md115
-rw-r--r--doc/install/kubernetes/index.md40
-rw-r--r--doc/install/openshift_and_gitlab/index.md5
-rw-r--r--doc/install/pivotal/index.md53
-rw-r--r--doc/install/redis.md63
-rw-r--r--doc/install/relative_url.md29
-rw-r--r--doc/install/requirements.md48
-rw-r--r--doc/install/structure.md22
-rw-r--r--doc/integration/elasticsearch.md144
-rw-r--r--doc/integration/github.md4
-rw-r--r--doc/integration/jenkins.md5
-rw-r--r--doc/integration/salesforce.md36
-rw-r--r--doc/integration/saml.md2
-rw-r--r--doc/integration/slash_commands.md4
-rw-r--r--doc/license/README.md4
-rw-r--r--doc/migrate_ci_to_ce/README.md19
-rw-r--r--doc/policy/maintenance.md25
-rw-r--r--doc/public_access/public_access.md53
-rw-r--r--doc/push_rules/push_rules.md31
-rw-r--r--doc/raketasks/backup_restore.md6
-rw-r--r--doc/raketasks/import.md2
-rw-r--r--doc/security/README.md1
-rw-r--r--doc/security/crime_vulnerability.md36
-rw-r--r--doc/security/img/two_factor_authentication_group_settings.pngbin19495 -> 0 bytes
-rw-r--r--doc/security/img/two_factor_authentication_settings.pngbin9936 -> 0 bytes
-rw-r--r--doc/security/information_exclusivity.md41
-rw-r--r--doc/security/password_length_limits.md21
-rw-r--r--doc/security/rack_attack.md28
-rw-r--r--doc/security/reset_root_password.md19
-rw-r--r--doc/security/ssh_keys_restrictions.md15
-rw-r--r--doc/security/two_factor_authentication.md50
-rw-r--r--doc/security/unlock_user.md20
-rw-r--r--doc/security/user_email_confirmation.md22
-rw-r--r--doc/security/user_file_uploads.md35
-rw-r--r--doc/security/webhooks.md52
-rw-r--r--doc/ssh/README.md33
-rw-r--r--doc/subscriptions/index.md29
-rw-r--r--doc/system_hooks/system_hooks.md24
-rw-r--r--doc/tools/email.md34
-rw-r--r--doc/topics/authentication/index.md10
-rw-r--r--doc/topics/autodevops/index.md183
-rw-r--r--doc/topics/autodevops/quick_start_guide.md4
-rw-r--r--doc/topics/git/how_to_install_git/index.md29
-rw-r--r--doc/topics/git/index.md61
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/index.md121
-rw-r--r--doc/topics/git/troubleshooting_git.md39
-rw-r--r--doc/university/README.md108
-rw-r--r--doc/university/bookclub/booklist.md107
-rw-r--r--doc/university/bookclub/index.md11
-rw-r--r--doc/university/glossary/README.md707
-rw-r--r--doc/university/high-availability/aws/README.md393
-rw-r--r--doc/university/high-availability/aws/img/auto-scaling-det.pngbin29967 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/db-subnet-group.pngbin29298 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/ec-subnet.pngbin28405 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/ig-rt.pngbin12547 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/ig.pngbin8140 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/instance_specs.pngbin11522 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/new_vpc.pngbin15691 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/policies.pngbin39723 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/rds-net-opt.pngbin16340 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/rds-sec-group.pngbin11579 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/redis-cluster-det.pngbin23761 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/redis-net.pngbin27261 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/reference-arch2.pngbin53510 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/route_table.pngbin12088 -> 0 bytes
-rw-r--r--doc/university/high-availability/aws/img/subnet.pngbin17070 -> 0 bytes
-rw-r--r--doc/university/support/README.md126
-rw-r--r--doc/university/training/gitlab_flow.md15
-rw-r--r--doc/university/training/index.md32
-rw-r--r--doc/university/training/topics/additional_resources.md12
-rw-r--r--doc/university/training/topics/agile_git.md9
-rw-r--r--doc/university/training/topics/bisect.md16
-rw-r--r--doc/university/training/topics/cherry_picking.md6
-rw-r--r--doc/university/training/topics/env_setup.md5
-rw-r--r--doc/university/training/topics/explore_gitlab.md2
-rw-r--r--doc/university/training/topics/feature_branching.md8
-rw-r--r--doc/university/training/topics/getting_started.md21
-rw-r--r--doc/university/training/topics/git_add.md4
-rw-r--r--doc/university/training/topics/git_intro.md4
-rw-r--r--doc/university/training/topics/git_log.md20
-rw-r--r--doc/university/training/topics/merge_conflicts.md11
-rw-r--r--doc/university/training/topics/merge_requests.md8
-rw-r--r--doc/university/training/topics/rollback_commits.md22
-rw-r--r--doc/university/training/topics/stash.md22
-rw-r--r--doc/university/training/topics/subtree.md20
-rw-r--r--doc/university/training/topics/tags.md21
-rw-r--r--doc/university/training/topics/unstage.md12
-rw-r--r--doc/university/training/user_training.md80
-rw-r--r--doc/update/patch_versions.md2
-rw-r--r--doc/update/upgrading_from_ce_to_ee.md3
-rw-r--r--doc/update/upgrading_from_source.md68
-rw-r--r--doc/user/admin_area/diff_limits.md41
-rw-r--r--doc/user/admin_area/geo_nodes.md24
-rw-r--r--doc/user/admin_area/img/index_runners_search_or_filter.pngbin0 -> 22414 bytes
-rw-r--r--doc/user/admin_area/index.md139
-rw-r--r--doc/user/admin_area/labels.md22
-rw-r--r--doc/user/admin_area/license.md28
-rw-r--r--doc/user/admin_area/monitoring/health_check.md41
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md53
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md16
-rw-r--r--doc/user/admin_area/settings/email.md26
-rw-r--r--doc/user/admin_area/settings/external_authorization.md16
-rw-r--r--doc/user/admin_area/settings/index.md12
-rw-r--r--doc/user/admin_area/settings/instance_template_repository.md16
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md16
-rw-r--r--doc/user/admin_area/settings/terms.md46
-rw-r--r--doc/user/admin_area/settings/third_party_offers.md23
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md18
-rw-r--r--doc/user/admin_area/settings/visibility_and_access_controls.md46
-rw-r--r--doc/user/application_security/container_scanning/index.md6
-rw-r--r--doc/user/application_security/dast/index.md2
-rw-r--r--doc/user/application_security/dependency_scanning/index.md17
-rw-r--r--doc/user/application_security/img/dismissed_info.pngbin0 -> 20244 bytes
-rw-r--r--doc/user/application_security/img/interactive_reports.pngbin23190 -> 93910 bytes
-rw-r--r--doc/user/application_security/index.md10
-rw-r--r--doc/user/application_security/sast/index.md2
-rw-r--r--doc/user/application_security/security_dashboard/img/project_security_dashboard.pngbin49062 -> 126356 bytes
-rw-r--r--doc/user/application_security/security_dashboard/index.md1
-rw-r--r--doc/user/clusters/applications.md290
-rw-r--r--doc/user/clusters/img/jupyter-git-extension.gifbin0 -> 2120084 bytes
-rw-r--r--doc/user/clusters/img/jupyter-gitclone.pngbin0 -> 64120 bytes
-rw-r--r--doc/user/group/clusters/index.md51
-rw-r--r--doc/user/group/contribution_analytics/index.md64
-rw-r--r--doc/user/group/custom_project_templates.md16
-rw-r--r--doc/user/group/epics/index.md16
-rw-r--r--doc/user/group/index.md58
-rw-r--r--doc/user/group/insights/index.md26
-rw-r--r--doc/user/group/issues_analytics/index.md41
-rw-r--r--doc/user/group/roadmap/index.md27
-rw-r--r--doc/user/group/saml_sso/index.md59
-rw-r--r--doc/user/group/saml_sso/scim_setup.md22
-rw-r--r--doc/user/group/security_dashboard/index.md4
-rw-r--r--doc/user/group/subgroups/index.md57
-rw-r--r--doc/user/img/markdown_inline_diffs_tags_rendered.pngbin0 -> 1804 bytes
-rw-r--r--doc/user/index.md26
-rw-r--r--doc/user/instance_statistics/convdev.md2
-rw-r--r--doc/user/markdown.md10
-rw-r--r--doc/user/permissions.md261
-rw-r--r--doc/user/profile/account/delete_account.md2
-rw-r--r--doc/user/profile/personal_access_tokens.md17
-rw-r--r--doc/user/project/ci_cd_for_external_repo.md4
-rw-r--r--doc/user/project/clusters/index.md200
-rw-r--r--doc/user/project/clusters/kubernetes_pod_logs.md6
-rw-r--r--doc/user/project/clusters/runbooks/index.md12
-rw-r--r--doc/user/project/clusters/serverless/index.md2
-rw-r--r--doc/user/project/container_registry.md4
-rw-r--r--doc/user/project/cycle_analytics.md18
-rw-r--r--doc/user/project/deploy_tokens/index.md2
-rw-r--r--doc/user/project/description_templates.md2
-rw-r--r--doc/user/project/file_lock.md14
-rw-r--r--doc/user/project/import/gemnasium.md10
-rw-r--r--doc/user/project/import/github.md6
-rw-r--r--doc/user/project/import/gitlab_com.md5
-rw-r--r--doc/user/project/import/index.md3
-rw-r--r--doc/user/project/import/phabricator.md29
-rw-r--r--doc/user/project/index.md13
-rw-r--r--doc/user/project/insights/index.md13
-rw-r--r--doc/user/project/integrations/img/jira_add_user_to_group.pngbin24838 -> 266180 bytes
-rw-r--r--doc/user/project/integrations/img/jira_added_user_to_group.pngbin0 -> 82473 bytes
-rw-r--r--doc/user/project/integrations/img/jira_create_new_group.pngbin19127 -> 262453 bytes
-rw-r--r--doc/user/project/integrations/img/jira_create_new_user.pngbin12625 -> 173516 bytes
-rw-r--r--doc/user/project/integrations/img/jira_group_access.pngbin19147 -> 112706 bytes
-rw-r--r--doc/user/project/integrations/img/jira_user_management_link.pngbin23906 -> 206155 bytes
-rw-r--r--doc/user/project/integrations/jira.md4
-rw-r--r--doc/user/project/integrations/jira_server_configuration.md45
-rw-r--r--doc/user/project/integrations/project_services.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/kubernetes.md4
-rw-r--r--doc/user/project/integrations/webhooks.md30
-rw-r--r--doc/user/project/integrations/youtrack.md4
-rw-r--r--doc/user/project/issues/create_new_issue.md2
-rw-r--r--doc/user/project/issues/img/comment-or-discussion.pngbin0 -> 8378 bytes
-rw-r--r--doc/user/project/issues/img/create_mr_from_issue.pngbin0 -> 21286 bytes
-rw-r--r--doc/user/project/issues/img/issues_main_view_numbered.jpgbin205803 -> 0 bytes
-rw-r--r--doc/user/project/issues/img/issues_main_view_numbered.pngbin0 -> 278285 bytes
-rw-r--r--doc/user/project/issues/img/reopen-issue.pngbin0 -> 2522 bytes
-rw-r--r--doc/user/project/issues/img/report-abuse.pngbin0 -> 1620 bytes
-rw-r--r--doc/user/project/issues/img/show-all-activity.pngbin0 -> 8119 bytes
-rw-r--r--doc/user/project/issues/index.md12
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md260
-rw-r--r--doc/user/project/issues/moving_issues.md25
-rw-r--r--doc/user/project/issues/multiple_assignees_for_issues.md2
-rw-r--r--doc/user/project/issues/related_issues.md11
-rw-r--r--doc/user/project/labels.md8
-rw-r--r--doc/user/project/maven_packages.md4
-rw-r--r--doc/user/project/merge_requests/allow_collaboration.md16
-rw-r--r--doc/user/project/merge_requests/authorization_for_merge_requests.md18
-rw-r--r--doc/user/project/merge_requests/browser_performance_testing.md20
-rw-r--r--doc/user/project/merge_requests/cherry_pick_changes.md26
-rw-r--r--doc/user/project/merge_requests/code_quality.md28
-rw-r--r--doc/user/project/merge_requests/code_quality_diff.md2
-rw-r--r--doc/user/project/merge_requests/container_scanning.md4
-rw-r--r--doc/user/project/merge_requests/dast.md4
-rw-r--r--doc/user/project/merge_requests/dependency_scanning.md4
-rw-r--r--doc/user/project/merge_requests/fast_forward_merge.md35
-rw-r--r--doc/user/project/merge_requests/index.md31
-rw-r--r--doc/user/project/merge_requests/license_management.md4
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md48
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md23
-rw-r--r--doc/user/project/merge_requests/resolve_conflicts.md16
-rw-r--r--doc/user/project/merge_requests/revert_changes.md29
-rw-r--r--doc/user/project/merge_requests/sast.md4
-rw-r--r--doc/user/project/merge_requests/sast_docker.md4
-rw-r--r--doc/user/project/merge_requests/squash_and_merge.md45
-rw-r--r--doc/user/project/merge_requests/versions.md34
-rw-r--r--doc/user/project/merge_requests/work_in_progress_merge_requests.md24
-rw-r--r--doc/user/project/milestones/burndown_charts.md2
-rw-r--r--doc/user/project/milestones/index.md2
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md9
-rw-r--r--doc/user/project/operations/img/external_dashboard_link.pngbin0 -> 63282 bytes
-rw-r--r--doc/user/project/operations/img/external_dashboard_settings.pngbin0 -> 103474 bytes
-rw-r--r--doc/user/project/operations/index.md1
-rw-r--r--doc/user/project/operations/linking_to_an_external_dashboard.md19
-rw-r--r--doc/user/project/packages/maven_repository.md2
-rw-r--r--doc/user/project/packages/npm_registry.md2
-rw-r--r--doc/user/project/pages/getting_started_part_four.md8
-rw-r--r--doc/user/project/pages/getting_started_part_one.md3
-rw-r--r--doc/user/project/pages/getting_started_part_three.md10
-rw-r--r--doc/user/project/pages/getting_started_part_two.md22
-rw-r--r--doc/user/project/pages/index.md5
-rw-r--r--doc/user/project/pages/introduction.md193
-rw-r--r--doc/user/project/pages/lets_encrypt_for_gitlab_pages.md2
-rw-r--r--doc/user/project/pipelines/job_artifacts.md16
-rw-r--r--doc/user/project/pipelines/settings.md16
-rw-r--r--doc/user/project/quick_actions.md2
-rw-r--r--doc/user/project/repository/index.md6
-rw-r--r--doc/user/project/repository/reducing_the_repo_size_using_git.md9
-rw-r--r--doc/user/project/security_dashboard.md4
-rw-r--r--doc/user/project/settings/img/import_export_download_export.pngbin24397 -> 25905 bytes
-rw-r--r--doc/user/project/settings/img/import_export_export_button.pngbin24118 -> 25102 bytes
-rw-r--r--doc/user/project/settings/img/import_export_mail_link.pngbin13496 -> 7561 bytes
-rw-r--r--doc/user/project/settings/img/import_export_new_project.pngbin13082 -> 13202 bytes
-rw-r--r--doc/user/project/settings/img/import_export_select_file.pngbin13514 -> 20580 bytes
-rw-r--r--doc/user/project/settings/img/settings_edit_button.pngbin6897 -> 0 bytes
-rw-r--r--doc/user/project/settings/import_export.md28
-rw-r--r--doc/user/project/settings/index.md16
-rw-r--r--doc/user/search/advanced_global_search.md2
-rw-r--r--doc/user/search/advanced_search_syntax.md2
-rw-r--r--doc/workflow/README.md10
-rw-r--r--doc/workflow/gitlab_flow.md2
-rw-r--r--doc/workflow/img/notification_global_settings.pngbin37542 -> 118914 bytes
-rw-r--r--doc/workflow/img/repository_mirroring_pull_settings_upper.pngbin64118 -> 50084 bytes
-rw-r--r--doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md64
-rw-r--r--doc/workflow/merge_request_approvals.md4
-rw-r--r--doc/workflow/notifications.md8
-rw-r--r--doc/workflow/repository_mirroring.md5
-rw-r--r--doc/workflow/shortcuts.md2
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/circuit_breakers.rb39
-rw-r--r--lib/api/commits.rb10
-rw-r--r--lib/api/discussions.rb2
-rw-r--r--lib/api/entities.rb8
-rw-r--r--lib/api/groups.rb27
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/helpers/groups_helpers.rb24
-rw-r--r--lib/api/helpers/issues_helpers.rb8
-rw-r--r--lib/api/helpers/members_helpers.rb19
-rw-r--r--lib/api/helpers/pagination.rb2
-rw-r--r--lib/api/helpers/projects_helpers.rb75
-rw-r--r--lib/api/helpers/protected_branches_helpers.rb13
-rw-r--r--lib/api/helpers/services_helpers.rb6
-rw-r--r--lib/api/helpers/settings_helpers.rb19
-rw-r--r--lib/api/helpers/users_helpers.rb16
-rw-r--r--lib/api/helpers/variables_helpers.rb13
-rw-r--r--lib/api/issues.rb25
-rw-r--r--lib/api/merge_requests.rb11
-rw-r--r--lib/api/project_import.rb3
-rw-r--r--lib/api/projects.rb23
-rw-r--r--lib/api/protected_branches.rb26
-rw-r--r--lib/api/settings.rb52
-rw-r--r--lib/api/users.rb12
-rw-r--r--lib/api/variables.rb16
-rw-r--r--lib/banzai/commit_renderer.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb8
-rw-r--r--lib/banzai/filter/label_reference_filter.rb8
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb4
-rw-r--r--lib/banzai/filter/wiki_link_filter/rewriter.rb8
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb2
-rw-r--r--lib/banzai/redactor.rb7
-rw-r--r--lib/gitlab.rb5
-rw-r--r--lib/gitlab/auth/o_auth/auth_hash.rb2
-rw-r--r--lib/gitlab/auth_logger.rb9
-rw-r--r--lib/gitlab/background_migration/calculate_wiki_sizes.rb18
-rw-r--r--lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb40
-rw-r--r--lib/gitlab/background_migration/reset_merge_status.rb17
-rw-r--r--lib/gitlab/ci/config/entry/image.rb2
-rw-r--r--lib/gitlab/ci/config/entry/job.rb2
-rw-r--r--lib/gitlab/ci/config/entry/service.rb3
-rw-r--r--lib/gitlab/ci/config/extendable/entry.rb59
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/and.rb27
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/base.rb6
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/equals.rb9
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/matches.rb25
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb9
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb9
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/operator.rb20
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/or.rb27
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb13
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexer.rb19
-rw-r--r--lib/gitlab/ci/pipeline/expression/parser.rb70
-rw-r--r--lib/gitlab/ci/pipeline/expression/statement.rb26
-rw-r--r--lib/gitlab/ci/templates/.yamllint5
-rw-r--r--lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Android.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/C++.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Chef.gitlab-ci.yml34
-rw-r--r--lib/gitlab/ci/templates/Clojure.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Crystal.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Django.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Elixir.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Go.gitlab-ci.yml28
-rw-r--r--lib/gitlab/ci/templates/Grails.gitlab-ci.yml36
-rw-r--r--lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml13
-rw-r--r--lib/gitlab/ci/templates/Julia.gitlab-ci.yml15
-rw-r--r--lib/gitlab/ci/templates/Laravel.gitlab-ci.yml20
-rw-r--r--lib/gitlab/ci/templates/Maven.gitlab-ci.yml74
-rw-r--r--lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/PHP.gitlab-ci.yml24
-rw-r--r--lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml26
-rw-r--r--lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml15
-rw-r--r--lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml12
-rw-r--r--lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml12
-rw-r--r--lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml22
-rw-r--r--lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml14
-rw-r--r--lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Python.gitlab-ci.yml28
-rw-r--r--lib/gitlab/ci/templates/Ruby.gitlab-ci.yml16
-rw-r--r--lib/gitlab/ci/templates/Rust.gitlab-ci.yml18
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml32
-rw-r--r--lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Serverless.gitlab-ci.yml18
-rw-r--r--lib/gitlab/ci/templates/Swift.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/dotNET-Core.yml113
-rw-r--r--lib/gitlab/ci/templates/dotNET.gitlab-ci.yml27
-rw-r--r--lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml8
-rw-r--r--lib/gitlab/cluster/lifecycle_events.rb8
-rw-r--r--lib/gitlab/cluster/puma_worker_killer_initializer.rb3
-rw-r--r--lib/gitlab/cluster/puma_worker_killer_observer.rb24
-rw-r--r--lib/gitlab/danger/helper.rb33
-rw-r--r--lib/gitlab/danger/roulette.rb84
-rw-r--r--lib/gitlab/danger/teammate.rb29
-rw-r--r--lib/gitlab/data_builder/note.rb1
-rw-r--r--lib/gitlab/data_builder/pipeline.rb2
-rw-r--r--lib/gitlab/diff/suggestion.rb2
-rw-r--r--lib/gitlab/diff/suggestions_parser.rb6
-rw-r--r--lib/gitlab/git/repository.rb33
-rw-r--r--lib/gitlab/git_ref_validator.rb23
-rw-r--r--lib/gitlab/gitaly_client.rb2
-rw-r--r--lib/gitlab/gitaly_client/conflicts_service.rb6
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb2
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb7
-rw-r--r--lib/gitlab/github_import/parallel_importer.rb18
-rw-r--r--lib/gitlab/graphql/loaders/batch_commit_loader.rb25
-rw-r--r--lib/gitlab/graphql/loaders/batch_model_loader.rb2
-rw-r--r--lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb23
-rw-r--r--lib/gitlab/graphql/query_analyzers/log_query_complexity.rb18
-rw-r--r--lib/gitlab/graphql/query_analyzers/logger_analyzer.rb71
-rw-r--r--lib/gitlab/graphql/representation/tree_entry.rb31
-rw-r--r--lib/gitlab/graphql_logger.rb9
-rw-r--r--lib/gitlab/hook_data/issue_builder.rb3
-rw-r--r--lib/gitlab/http.rb9
-rw-r--r--lib/gitlab/http_connection_adapter.rb (renamed from lib/gitlab/proxy_http_connection_adapter.rb)24
-rw-r--r--lib/gitlab/import/set_async_jid.rb27
-rw-r--r--lib/gitlab/import_export/attribute_cleaner.rb7
-rw-r--r--lib/gitlab/import_export/import_export.yml3
-rw-r--r--lib/gitlab/import_export/members_mapper.rb6
-rw-r--r--lib/gitlab/import_export/relation_factory.rb3
-rw-r--r--lib/gitlab/import_sources.rb3
-rw-r--r--lib/gitlab/kubernetes/errors.rb23
-rw-r--r--lib/gitlab/kubernetes/helm/api.rb1
-rw-r--r--lib/gitlab/lets_encrypt/challenge.rb2
-rw-r--r--lib/gitlab/lets_encrypt/client.rb23
-rw-r--r--lib/gitlab/lets_encrypt/order.rb11
-rw-r--r--lib/gitlab/markdown_cache.rb12
-rw-r--r--lib/gitlab/markdown_cache/active_record/extension.rb49
-rw-r--r--lib/gitlab/markdown_cache/field_data.rb35
-rw-r--r--lib/gitlab/markdown_cache/redis/extension.rb63
-rw-r--r--lib/gitlab/markdown_cache/redis/store.rb56
-rw-r--r--lib/gitlab/metrics/dashboard/base_service.rb4
-rw-r--r--lib/gitlab/metrics/dashboard/processor.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/stages/base_stage.rb9
-rw-r--r--lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb42
-rw-r--r--lib/gitlab/metrics/samplers/puma_sampler.rb93
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb33
-rw-r--r--lib/gitlab/metrics/samplers/unicorn_sampler.rb36
-rw-r--r--lib/gitlab/metrics/system.rb28
-rw-r--r--lib/gitlab/omniauth_initializer.rb21
-rw-r--r--lib/gitlab/path_regex.rb1
-rw-r--r--lib/gitlab/performance_bar/peek_query_tracker.rb7
-rw-r--r--lib/gitlab/phabricator_import.rb12
-rw-r--r--lib/gitlab/phabricator_import/base_worker.rb80
-rw-r--r--lib/gitlab/phabricator_import/cache/map.rb65
-rw-r--r--lib/gitlab/phabricator_import/conduit.rb9
-rw-r--r--lib/gitlab/phabricator_import/conduit/client.rb41
-rw-r--r--lib/gitlab/phabricator_import/conduit/maniphest.rb28
-rw-r--r--lib/gitlab/phabricator_import/conduit/pagination.rb24
-rw-r--r--lib/gitlab/phabricator_import/conduit/response.rb60
-rw-r--r--lib/gitlab/phabricator_import/conduit/tasks_response.rb24
-rw-r--r--lib/gitlab/phabricator_import/import_tasks_worker.rb10
-rw-r--r--lib/gitlab/phabricator_import/importer.rb44
-rw-r--r--lib/gitlab/phabricator_import/issues/importer.rb42
-rw-r--r--lib/gitlab/phabricator_import/issues/task_importer.rb54
-rw-r--r--lib/gitlab/phabricator_import/project_creator.rb78
-rw-r--r--lib/gitlab/phabricator_import/representation/task.rb60
-rw-r--r--lib/gitlab/phabricator_import/worker_state.rb47
-rw-r--r--lib/gitlab/project_search_results.rb6
-rw-r--r--lib/gitlab/quick_actions/issue_and_merge_request_actions.rb2
-rw-r--r--lib/gitlab/rack_timeout_observer.rb46
-rw-r--r--lib/gitlab/search_results.rb32
-rw-r--r--lib/gitlab/setup_helper.rb7
-rw-r--r--lib/gitlab/url_blocker.rb75
-rw-r--r--lib/gitlab/url_sanitizer.rb4
-rw-r--r--lib/quality/test_level.rb75
-rw-r--r--lib/tasks/gettext.rake1
-rw-r--r--lib/tasks/gitlab/assets.rake6
-rw-r--r--lib/tasks/gitlab/shell.rake12
-rw-r--r--lib/tasks/spec.rake44
-rw-r--r--locale/gitlab.pot734
-rw-r--r--package.json15
-rw-r--r--qa/Gemfile1
-rw-r--r--qa/Gemfile.lock3
-rw-r--r--qa/README.md12
-rw-r--r--qa/docs/GUIDELINES.md46
-rw-r--r--qa/docs/WRITING_TESTS_FROM_SCRATCH.md482
-rw-r--r--qa/knapsack/gitlab-ce/review-qa-all_master_report.json42
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/ce/strategy.rb5
-rw-r--r--qa/qa/fixtures/auto_devops_rack/Dockerfile9
-rw-r--r--qa/qa/page/base.rb22
-rw-r--r--qa/qa/page/element.rb25
-rw-r--r--qa/qa/page/file/form.rb2
-rw-r--r--qa/qa/page/main/login.rb16
-rw-r--r--qa/qa/page/main/menu.rb8
-rw-r--r--qa/qa/page/project/branches/show.rb8
-rw-r--r--qa/qa/page/project/new.rb5
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb4
-rw-r--r--qa/qa/page/project/settings/ci_variables.rb24
-rw-r--r--qa/qa/page/project/web_ide/edit.rb27
-rw-r--r--qa/qa/page/validatable.rb22
-rw-r--r--qa/qa/page/view.rb4
-rw-r--r--qa/qa/resource/base.rb64
-rw-r--r--qa/qa/resource/ci_variable.rb7
-rw-r--r--qa/qa/resource/file.rb31
-rw-r--r--qa/qa/resource/project.rb7
-rw-r--r--qa/qa/resource/repository/project_push.rb2
-rw-r--r--qa/qa/runtime/api/client.rb9
-rw-r--r--qa/qa/runtime/browser.rb1
-rw-r--r--qa/qa/runtime/env.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb9
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb26
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb11
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb5
-rw-r--r--qa/qa/specs/runner.rb19
-rw-r--r--qa/qa/support/page/logging.rb20
-rw-r--r--qa/spec/page/element_spec.rb56
-rw-r--r--qa/spec/page/logging_spec.rb12
-rw-r--r--qa/spec/runtime/env_spec.rb24
-rw-r--r--qa/spec/spec_helper.rb5
-rw-r--r--rubocop/cop/qa/element_with_pattern.rb20
-rwxr-xr-xscripts/frontend/test.js114
-rw-r--r--scripts/prepare_build.sh11
-rw-r--r--spec/controllers/acme_challenges_controller_spec.rb44
-rw-r--r--spec/controllers/application_controller_spec.rb34
-rw-r--r--spec/controllers/concerns/import_url_params_spec.rb56
-rw-r--r--spec/controllers/groups_controller_spec.rb22
-rw-r--r--spec/controllers/import/fogbugz_controller_spec.rb38
-rw-r--r--spec/controllers/import/phabricator_controller_spec.rb92
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/projects/ci/lints_controller_spec.rb4
-rw-r--r--spec/controllers/projects/environments/prometheus_api_controller_spec.rb4
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb14
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb15
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb31
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb93
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb44
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb34
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb2
-rw-r--r--spec/controllers/projects/serverless/functions_controller_spec.rb92
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb17
-rw-r--r--spec/controllers/projects_controller_spec.rb12
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb109
-rw-r--r--spec/controllers/sessions_controller_spec.rb34
-rw-r--r--spec/factories/ci/builds.rb11
-rw-r--r--spec/factories/ci/job_artifacts.rb18
-rw-r--r--spec/factories/ci/pipeline_schedule.rb10
-rw-r--r--spec/factories/merge_requests.rb3
-rw-r--r--spec/factories/pages_domain_acme_orders.rb17
-rw-r--r--spec/factories/project_auto_devops.rb1
-rw-r--r--spec/features/admin/admin_appearance_spec.rb2
-rw-r--r--spec/features/admin/admin_browses_logs_spec.rb1
-rw-r--r--spec/features/admin/admin_sees_project_statistics_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb13
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb2
-rw-r--r--spec/features/boards/sidebar_spec.rb4
-rw-r--r--spec/features/clusters/cluster_detail_page_spec.rb3
-rw-r--r--spec/features/commits_spec.rb6
-rw-r--r--spec/features/global_search_spec.rb14
-rw-r--r--spec/features/groups/clusters/user_spec.rb1
-rw-r--r--spec/features/groups/merge_requests_spec.rb2
-rw-r--r--spec/features/instance_statistics/conversational_development_index_spec.rb2
-rw-r--r--spec/features/issuables/issuable_list_spec.rb2
-rw-r--r--spec/features/issues_spec.rb4
-rw-r--r--spec/features/markdown/gitlab_flavored_markdown_spec.rb3
-rw-r--r--spec/features/merge_request/user_creates_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb25
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb21
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb6
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb2
-rw-r--r--spec/features/project_variables_spec.rb2
-rw-r--r--spec/features/projects/clusters/user_spec.rb1
-rw-r--r--spec/features/projects/clusters_spec.rb2
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb1
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb1
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb2
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb1
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb2
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb4
-rw-r--r--spec/features/projects/jobs/user_browses_jobs_spec.rb6
-rw-r--r--spec/features/projects/jobs_spec.rb18
-rw-r--r--spec/features/projects/labels/user_promotes_label_spec.rb34
-rw-r--r--spec/features/projects/labels/user_removes_labels_spec.rb5
-rw-r--r--spec/features/projects/new_project_spec.rb30
-rw-r--r--spec/features/projects/pages_lets_encrypt_spec.rb131
-rw-r--r--spec/features/projects/pages_spec.rb36
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb16
-rw-r--r--spec/features/projects/serverless/functions_spec.rb59
-rw-r--r--spec/features/projects/settings/forked_project_settings_spec.rb1
-rw-r--r--spec/features/projects/settings/operations_settings_spec.rb6
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_collaboration_links_spec.rb1
-rw-r--r--spec/features/projects_spec.rb3
-rw-r--r--spec/features/security/profile_access_spec.rb2
-rw-r--r--spec/features/users/signup_spec.rb44
-rw-r--r--spec/finders/clusters/knative_services_finder_spec.rb105
-rw-r--r--spec/finders/issues_finder_spec.rb8
-rw-r--r--spec/finders/members_finder_spec.rb44
-rw-r--r--spec/finders/projects/serverless/functions_finder_spec.rb68
-rw-r--r--spec/fixtures/api/schemas/environment.json6
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json6
-rw-r--r--spec/fixtures/phabricator_responses/auth_failed.json1
-rw-r--r--spec/fixtures/phabricator_responses/maniphest.search.json98
-rw-r--r--spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json422
-rw-r--r--spec/fixtures/security-reports/master/gl-dast-report.json74
-rw-r--r--spec/frontend/activities_spec.js (renamed from spec/javascripts/activities_spec.js)3
-rw-r--r--spec/frontend/api_spec.js (renamed from spec/javascripts/api_spec.js)4
-rw-r--r--spec/frontend/autosave_spec.js (renamed from spec/javascripts/autosave_spec.js)19
-rw-r--r--spec/frontend/boards/modal_store_spec.js4
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js49
-rw-r--r--spec/frontend/clusters/components/application_row_spec.js76
-rw-r--r--spec/frontend/clusters/components/applications_spec.js97
-rw-r--r--spec/frontend/clusters/components/knative_domain_editor_spec.js141
-rw-r--r--spec/frontend/clusters/services/application_state_machine_spec.js2
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js5
-rw-r--r--spec/frontend/helpers/jest_helpers.js24
-rw-r--r--spec/frontend/helpers/jquery.js6
-rw-r--r--spec/frontend/helpers/local_storage_helper.js41
-rw-r--r--spec/frontend/helpers/timeout.js34
-rw-r--r--spec/frontend/helpers/vue_test_utils_helper.js4
-rw-r--r--spec/frontend/ide/stores/modules/commit/mutations_spec.js15
-rw-r--r--spec/frontend/ide/stores/mutations/branch_spec.js35
-rw-r--r--spec/frontend/ide/stores/mutations/project_spec.js23
-rw-r--r--spec/frontend/jobs/store/mutations_spec.js38
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js (renamed from spec/javascripts/lib/utils/datetime_utility_spec.js)22
-rw-r--r--spec/frontend/lib/utils/number_utility_spec.js11
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js (renamed from spec/javascripts/lib/utils/url_utility_spec.js)116
-rw-r--r--spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap2
-rw-r--r--spec/frontend/notes/components/note_app_spec.js (renamed from spec/javascripts/notes/components/note_app_spec.js)203
-rw-r--r--spec/frontend/notes/old_notes_spec.js (renamed from spec/javascripts/notes_spec.js)493
-rw-r--r--spec/frontend/operation_settings/components/external_dashboard_spec.js160
-rw-r--r--spec/frontend/operation_settings/store/mutations_spec.js19
-rw-r--r--spec/frontend/reports/components/report_section_spec.js40
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js44
-rw-r--r--spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap4
-rw-r--r--spec/frontend/repository/components/table/index_spec.js51
-rw-r--r--spec/frontend/repository/components/table/parent_row_spec.js64
-rw-r--r--spec/frontend/repository/components/table/row_spec.js43
-rw-r--r--spec/frontend/repository/utils/icon_spec.js2
-rw-r--r--spec/frontend/repository/utils/title_spec.js15
-rw-r--r--spec/frontend/serverless/components/environment_row_spec.js8
-rw-r--r--spec/frontend/serverless/components/functions_spec.js27
-rw-r--r--spec/frontend/serverless/mock_data.js110
-rw-r--r--spec/frontend/serverless/store/getters_spec.js2
-rw-r--r--spec/frontend/serverless/store/mutations_spec.js4
-rw-r--r--spec/frontend/test_setup.js2
-rw-r--r--spec/frontend/vue_mr_widget/stores/get_state_key_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/issue/issue_warning_spec.js18
-rw-r--r--spec/frontend/vue_shared/components/modal_copy_button_spec.js40
-rw-r--r--spec/frontend/vue_shared/droplab_dropdown_button_spec.js136
-rw-r--r--spec/graphql/features/authorization_spec.rb2
-rw-r--r--spec/graphql/gitlab_schema_spec.rb62
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb22
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb69
-rw-r--r--spec/graphql/types/base_field_spec.rb31
-rw-r--r--spec/graphql/types/issue_type_spec.rb6
-rw-r--r--spec/graphql/types/namespace_type_spec.rb (renamed from spec/graphql/types/namespace_type.rb)2
-rw-r--r--spec/graphql/types/project_statistics_type_spec.rb11
-rw-r--r--spec/graphql/types/project_type_spec.rb2
-rw-r--r--spec/graphql/types/query_type_spec.rb12
-rw-r--r--spec/graphql/types/tree/blob_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/tree_entry_type_spec.rb2
-rw-r--r--spec/helpers/emails_helper_spec.rb8
-rw-r--r--spec/helpers/environments_helper_spec.rb49
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb2
-rw-r--r--spec/helpers/labels_helper_spec.rb19
-rw-r--r--spec/helpers/markup_helper_spec.rb3
-rw-r--r--spec/helpers/projects_helper_spec.rb22
-rw-r--r--spec/helpers/storage_helper_spec.rb3
-rw-r--r--spec/helpers/tracking_helper_spec.rb11
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb45
-rw-r--r--spec/initializers/secret_token_spec.rb11
-rw-r--r--spec/javascripts/boards/board_card_spec.js4
-rw-r--r--spec/javascripts/boards/boards_store_spec.js91
-rw-r--r--spec/javascripts/boards/issue_card_spec.js4
-rw-r--r--spec/javascripts/boards/issue_spec.js4
-rw-r--r--spec/javascripts/boards/list_spec.js4
-rw-r--r--spec/javascripts/boards/mock_data.js8
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js8
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js59
-rw-r--r--spec/javascripts/diffs/components/commit_item_spec.js6
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js11
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js4
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/actions_spec.js43
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js73
-rw-r--r--spec/javascripts/ide/components/ide_spec.js92
-rw-r--r--spec/javascripts/ide/components/ide_tree_list_spec.js59
-rw-r--r--spec/javascripts/ide/mock_data.js1
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js16
-rw-r--r--spec/javascripts/ide/stores/actions/project_spec.js203
-rw-r--r--spec/javascripts/ide/stores/actions/tree_spec.js32
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js69
-rw-r--r--spec/javascripts/ide/stores/getters_spec.js32
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js216
-rw-r--r--spec/javascripts/ide/stores/modules/commit/getters_spec.js29
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js5
-rw-r--r--spec/javascripts/jobs/components/sidebar_spec.js14
-rw-r--r--spec/javascripts/jobs/components/stages_dropdown_spec.js21
-rw-r--r--spec/javascripts/jobs/mock_data.js296
-rw-r--r--spec/javascripts/jobs/store/actions_spec.js105
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js16
-rw-r--r--spec/javascripts/matchers.js2
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js14
-rw-r--r--spec/javascripts/monitoring/charts/single_stat_spec.js28
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js187
-rw-r--r--spec/javascripts/monitoring/helpers.js8
-rw-r--r--spec/javascripts/monitoring/mock_data.js5871
-rw-r--r--spec/javascripts/monitoring/monitoring_store_spec.js59
-rw-r--r--spec/javascripts/monitoring/store/actions_spec.js158
-rw-r--r--spec/javascripts/monitoring/store/mutations_spec.js92
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js3
-rw-r--r--spec/javascripts/pipelines/mock_data.js1
-rw-r--r--spec/javascripts/pipelines/pipeline_url_spec.js5
-rw-r--r--spec/javascripts/projects/project_new_spec.js14
-rw-r--r--spec/javascripts/test_bundle.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js28
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js26
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js5
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/pagination/graphql_pagination_spec.js70
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb34
-rw-r--r--spec/lib/banzai/commit_renderer_spec.rb4
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb7
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb21
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb42
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb28
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb23
-rw-r--r--spec/lib/banzai/redactor_spec.rb32
-rw-r--r--spec/lib/banzai/renderer_spec.rb14
-rw-r--r--spec/lib/gitlab/background_migration/delete_diff_files_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/reset_merge_status_spec.rb48
-rw-r--r--spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb62
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/extendable/entry_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb55
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb120
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb60
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb120
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb94
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb237
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb6
-rw-r--r--spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb27
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb97
-rw-r--r--spec/lib/gitlab/danger/roulette_spec.rb101
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb51
-rw-r--r--spec/lib/gitlab/data_builder/note_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb9
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb64
-rw-r--r--spec/lib/gitlab/git_ref_validator_spec.rb92
-rw-r--r--spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb21
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb18
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb11
-rw-r--r--spec/lib/gitlab/github_import/parallel_importer_spec.rb11
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_commit_loader_spec.rb23
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb18
-rw-r--r--spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb25
-rw-r--r--spec/lib/gitlab/graphql/representation/tree_entry_spec.rb20
-rw-r--r--spec/lib/gitlab/graphql_logger_spec.rb40
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb120
-rw-r--r--spec/lib/gitlab/http_spec.rb28
-rw-r--r--spec/lib/gitlab/import/set_async_jid_spec.rb23
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb20
-rw-r--r--spec/lib/gitlab/import_export/project.json9
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb20
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb6
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb13
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb24
-rw-r--r--spec/lib/gitlab/lets_encrypt/challenge_spec.rb18
-rw-r--r--spec/lib/gitlab/lets_encrypt/client_spec.rb42
-rw-r--r--spec/lib/gitlab/lets_encrypt/order_spec.rb38
-rw-r--r--spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb179
-rw-r--r--spec/lib/gitlab/markdown_cache/field_data_spec.rb15
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/extension_spec.rb76
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/store_spec.rb68
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb9
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb28
-rw-r--r--spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb123
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb30
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb25
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb24
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb30
-rw-r--r--spec/lib/gitlab/phabricator_import/base_worker_spec.rb74
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb66
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/client_spec.rb59
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb39
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/response_spec.rb79
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb27
-rw-r--r--spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb16
-rw-r--r--spec/lib/gitlab/phabricator_import/importer_spec.rb32
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/importer_spec.rb53
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb54
-rw-r--r--spec/lib/gitlab/phabricator_import/project_creator_spec.rb58
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/task_spec.rb33
-rw-r--r--spec/lib/gitlab/phabricator_import/worker_state_spec.rb46
-rw-r--r--spec/lib/gitlab/rack_timeout_observer_spec.rb58
-rw-r--r--spec/lib/gitlab/search_results_spec.rb24
-rw-r--r--spec/lib/gitlab/shell_spec.rb10
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb7
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb83
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb34
-rw-r--r--spec/lib/gitlab_spec.rb30
-rw-r--r--spec/lib/mattermost/session_spec.rb7
-rw-r--r--spec/lib/quality/test_level_spec.rb105
-rw-r--r--spec/mailers/emails/pages_domains_spec.rb6
-rw-r--r--spec/mailers/notify_spec.rb10
-rw-r--r--spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb35
-rw-r--r--spec/migrations/enqueue_reset_merge_status_spec.rb52
-rw-r--r--spec/migrations/enqueue_verify_pages_domain_workers_spec.rb6
-rw-r--r--spec/migrations/generate_lets_encrypt_private_key_spec.rb12
-rw-r--r--spec/migrations/generate_missing_routes_spec.rb2
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb4
-rw-r--r--spec/migrations/remove_orphaned_label_links_spec.rb6
-rw-r--r--spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb46
-rw-r--r--spec/models/broadcast_message_spec.rb8
-rw-r--r--spec/models/ci/build_spec.rb186
-rw-r--r--spec/models/ci/job_artifact_spec.rb23
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb145
-rw-r--r--spec/models/ci/pipeline_spec.rb34
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb2
-rw-r--r--spec/models/clusters/applications/knative_spec.rb76
-rw-r--r--spec/models/clusters/cluster_spec.rb227
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb446
-rw-r--r--spec/models/concerns/milestoneish_spec.rb78
-rw-r--r--spec/models/concerns/noteable_spec.rb12
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb13
-rw-r--r--spec/models/diff_note_spec.rb8
-rw-r--r--spec/models/issue_spec.rb15
-rw-r--r--spec/models/merge_request_spec.rb56
-rw-r--r--spec/models/milestone_spec.rb71
-rw-r--r--spec/models/namespace_spec.rb6
-rw-r--r--spec/models/pages_domain_acme_order_spec.rb49
-rw-r--r--spec/models/pages_domain_spec.rb11
-rw-r--r--spec/models/project_auto_devops_spec.rb60
-rw-r--r--spec/models/project_ci_cd_setting_spec.rb40
-rw-r--r--spec/models/project_services/assembla_service_spec.rb6
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb3
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb10
-rw-r--r--spec/models/project_services/campfire_service_spec.rb24
-rw-r--r--spec/models/project_services/pipelines_email_service_spec.rb76
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb10
-rw-r--r--spec/models/project_services/pushover_service_spec.rb6
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb3
-rw-r--r--spec/models/project_spec.rb77
-rw-r--r--spec/models/project_statistics_spec.rb92
-rw-r--r--spec/models/resource_label_event_spec.rb4
-rw-r--r--spec/policies/project_policy_spec.rb2
-rw-r--r--spec/presenters/blob_presenter_spec.rb10
-rw-r--r--spec/presenters/ci/build_runner_presenter_spec.rb66
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb40
-rw-r--r--spec/presenters/issue_presenter_spec.rb29
-rw-r--r--spec/presenters/label_presenter_spec.rb28
-rw-r--r--spec/presenters/merge_request_presenter_spec.rb10
-rw-r--r--spec/presenters/tree_entry_presenter_spec.rb16
-rw-r--r--spec/rack_servers/puma_spec.rb2
-rw-r--r--spec/requests/api/circuit_breakers_spec.rb46
-rw-r--r--spec/requests/api/commits_spec.rb92
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb138
-rw-r--r--spec/requests/api/graphql/group_query_spec.rb11
-rw-r--r--spec/requests/api/graphql/multiplexed_queries_spec.rb39
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb82
-rw-r--r--spec/requests/api/graphql/project/project_statistics_spec.rb43
-rw-r--r--spec/requests/api/graphql_spec.rb48
-rw-r--r--spec/requests/api/groups_spec.rb3
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb4
-rw-r--r--spec/requests/api/issues/issues_spec.rb20
-rw-r--r--spec/requests/api/jobs_spec.rb8
-rw-r--r--spec/requests/api/members_spec.rb13
-rw-r--r--spec/requests/api/merge_requests_spec.rb6
-rw-r--r--spec/requests/api/projects_spec.rb49
-rw-r--r--spec/requests/api/runner_spec.rb26
-rw-r--r--spec/requests/api/search_spec.rb46
-rw-r--r--spec/requests/api/settings_spec.rb1
-rw-r--r--spec/requests/api/system_hooks_spec.rb8
-rw-r--r--spec/requests/api/task_completion_status_spec.rb85
-rw-r--r--spec/requests/api/users_spec.rb12
-rw-r--r--spec/requests/api/variables_spec.rb5
-rw-r--r--spec/requests/rack_attack_global_spec.rb22
-rw-r--r--spec/routing/import_routing_spec.rb12
-rw-r--r--spec/routing/project_routing_spec.rb29
-rw-r--r--spec/rubocop/cop/qa/element_with_pattern_spec.rb11
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb30
-rw-r--r--spec/serializers/build_details_entity_spec.rb33
-rw-r--r--spec/serializers/deployment_entity_spec.rb41
-rw-r--r--spec/serializers/job_artifact_report_entity_spec.rb28
-rw-r--r--spec/serializers/merge_request_widget_entity_spec.rb46
-rw-r--r--spec/serializers/pipeline_entity_spec.rb16
-rw-r--r--spec/services/auto_merge/base_service_spec.rb178
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb (renamed from spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb)48
-rw-r--r--spec/services/auto_merge_service_spec.rb164
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb6
-rw-r--r--spec/services/ci/pipeline_schedule_service_spec.rb36
-rw-r--r--spec/services/ci/retry_build_service_spec.rb5
-rw-r--r--spec/services/issuable/clone/content_rewriter_spec.rb16
-rw-r--r--spec/services/merge_requests/close_service_spec.rb8
-rw-r--r--spec/services/merge_requests/create_pipeline_service_spec.rb71
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb5
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb10
-rw-r--r--spec/services/merge_requests/update_service_spec.rb15
-rw-r--r--spec/services/pages_domains/create_acme_order_service_spec.rb63
-rw-r--r--spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb146
-rw-r--r--spec/services/preview_markdown_service_spec.rb4
-rw-r--r--spec/services/projects/fork_service_spec.rb24
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_service_spec.rb19
-rw-r--r--spec/services/projects/update_pages_service_spec.rb59
-rw-r--r--spec/services/projects/update_service_spec.rb1
-rw-r--r--spec/services/projects/update_statistics_service_spec.rb12
-rw-r--r--spec/services/submit_usage_ping_service_spec.rb4
-rw-r--r--spec/services/suggestions/apply_service_spec.rb68
-rw-r--r--spec/services/suggestions/create_service_spec.rb22
-rw-r--r--spec/services/system_note_service_spec.rb4
-rw-r--r--spec/services/todos/destroy/confidential_issue_service_spec.rb58
-rw-r--r--spec/services/web_hook_service_spec.rb10
-rw-r--r--spec/spec_helper.rb5
-rw-r--r--spec/support/capybara.rb3
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb4
-rw-r--r--spec/support/features/variable_list_shared_examples.rb32
-rw-r--r--spec/support/helpers/git_helpers.rb8
-rw-r--r--spec/support/helpers/graphql_helpers.rb13
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb46
-rw-r--r--spec/support/helpers/lets_encrypt_helpers.rb40
-rw-r--r--spec/support/helpers/stub_requests.rb40
-rw-r--r--spec/support/matchers/eq_pem.rb11
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb (renamed from spec/support/shared_context/policies/project_policy_shared_context.rb)2
-rw-r--r--spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb116
-rw-r--r--spec/support/shared_examples/finders/assignees_filter_shared_examples.rb (renamed from spec/support/shared_examples/finders/assignees_filter_spec.rb)6
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb (renamed from spec/support/shared_examples/models/atomic_internal_id_spec.rb)0
-rw-r--r--spec/support/shared_examples/models/chat_service_shared_examples.rb (renamed from spec/support/shared_examples/models/chat_service_spec.rb)0
-rw-r--r--spec/support/shared_examples/models/update_project_statistics_shared_examples.rb (renamed from spec/support/shared_examples/models/update_project_statistics_spec.rb)13
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb45
-rw-r--r--spec/support/shared_examples/requests/api/issues_shared_examples.rb (renamed from spec/support/shared_examples/requests/api/issues_shared_example_spec.rb)0
-rw-r--r--spec/tasks/gitlab/artifacts/migrate_rake_spec.rb55
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb18
-rw-r--r--spec/tasks/tokens_spec.rb4
-rw-r--r--spec/uploaders/legacy_artifact_uploader_spec.rb61
-rw-r--r--spec/uploaders/workers/object_storage/background_move_worker_spec.rb34
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb4
-rw-r--r--spec/views/projects/jobs/_build.html.haml_spec.rb10
-rw-r--r--spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb8
-rw-r--r--spec/workers/auto_merge_process_worker_spec.rb31
-rw-r--r--spec/workers/expire_build_instance_artifacts_worker_spec.rb6
-rw-r--r--spec/workers/pages_domain_verification_cron_worker_spec.rb7
-rw-r--r--spec/workers/pages_domain_verification_worker_spec.rb7
-rw-r--r--spec/workers/pipeline_schedule_worker_spec.rb57
-rw-r--r--spec/workers/pipeline_success_worker_spec.rb26
-rw-r--r--spec/workers/project_cache_worker_spec.rb5
-rw-r--r--spec/workers/run_pipeline_schedule_worker_spec.rb32
-rw-r--r--spec/workers/todos_destroyer/confidential_issue_worker_spec.rb13
-rw-r--r--vendor/assets/javascripts/visual_review_toolbar.js (renamed from public/visual-review-toolbar.js)379
-rw-r--r--vendor/jupyter/values.yaml18
-rw-r--r--yarn.lock1520
1937 files changed, 33108 insertions, 21039 deletions
diff --git a/.gitignore b/.gitignore
index 627c806787b..cb718a6939f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,3 +79,4 @@ package-lock.json
/junit_*.xml
/coverage-frontend/
jsdoc/
+**/tmp/rubocop_cache/** \ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c971df3ba5f..d49b620f22c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,10 +13,8 @@ variables:
BUILD_ASSETS_IMAGE: "false"
before_script:
- - bundle --version
- date
- source scripts/utils.sh
- - date
- source scripts/prepare_build.sh
- date
@@ -46,3 +44,4 @@ include:
- local: .gitlab/ci/review.gitlab-ci.yml
- local: .gitlab/ci/setup.gitlab-ci.yml
- local: .gitlab/ci/test-metadata.gitlab-ci.yml
+ - local: .gitlab/ci/yaml.gitlab-ci.yml
diff --git a/.gitlab/CODEOWNERS.disabled b/.gitlab/CODEOWNERS
index 52fb651f551..63ac5a408bd 100644
--- a/.gitlab/CODEOWNERS.disabled
+++ b/.gitlab/CODEOWNERS
@@ -1,9 +1,9 @@
# Backend Maintainers are the default for all ruby files
-*.rb @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @nick.thomas @rspeicher @rymai @smcgivern @mayra-cabrera
-*.rake @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @nick.thomas @rspeicher @rymai @smcgivern @mayra-cabrera
+*.rb @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @mayra-cabrera @nick.thomas @rspeicher @rymai @reprazent @smcgivern @tkuah
+*.rake @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @mayra-cabrera @nick.thomas @rspeicher @rymai @reprazent @smcgivern @tkuah
# Technical writing team are the default reviewers for everything in `doc/`
-/doc/ @axil @marcia
+/doc/ @axil @marcia @eread
# Frontend maintainers should see everything in `app/assets/`
app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya
@@ -18,3 +18,4 @@ db/ @abrandl @NikolayS
/lib/gitlab/auth/ldap/ @dblessing @mkozono
/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
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 986ba7558d5..fd5733593ef 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -33,15 +33,16 @@ gitlab:assets:compile:
DOCKER_HOST: tcp://docker:2375
script:
- node --version
- - yarn install --frozen-lockfile --production --cache-folder .yarn-cache
+ - retry yarn install --frozen-lockfile --production --cache-folder .yarn-cache
- free -m
- - bundle exec rake gitlab:assets:compile
+ - retry bundle exec rake gitlab:assets:compile
- time scripts/build_assets_image
- scripts/clean-old-cached-assets
+ - rm -f /etc/apt/sources.list.d/google*.list # We don't need to update Chrome here
# Play dependent manual jobs
- install_api_client_dependencies_with_apt
- - play_job "review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
- - play_job "schedule:review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
+ - play_job "review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
+ - play_job "schedule:review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
artifacts:
name: webpack-report
expire_in: 31d
@@ -64,9 +65,9 @@ compile-assets:
stage: prepare
script:
- node --version
- - yarn install --frozen-lockfile --cache-folder .yarn-cache
+ - retry yarn install --frozen-lockfile --cache-folder .yarn-cache
- free -m
- - bundle exec rake gitlab:assets:compile
+ - retry bundle exec rake gitlab:assets:compile
- scripts/clean-old-cached-assets
variables:
# we override the max_old_space_size to prevent OOM errors
@@ -140,8 +141,8 @@ jest:
extends: .dedicated-no-docs-and-no-qa-pull-cache-job
<<: *use-pg
dependencies:
- - compile-assets
- - setup-test-env
+ - compile-assets
+ - setup-test-env
script:
- scripts/gitaly-test-spawn
- date
@@ -153,8 +154,8 @@ jest:
expire_in: 31d
when: always
paths:
- - coverage-frontend/
- - junit_jest.xml
+ - coverage-frontend/
+ - junit_jest.xml
reports:
junit: junit_jest.xml
cache:
@@ -219,7 +220,7 @@ lint:javascript:report:
before_script: []
script:
- date
- - yarn run eslint-report || true # ignore exit code
+ - yarn run eslint-report || true # ignore exit code
artifacts:
name: eslint-report
expire_in: 31d
@@ -234,7 +235,7 @@ jsdoc:
before_script: []
script:
- date
- - yarn run jsdoc || true # ignore exit code
+ - yarn run jsdoc || true # ignore exit code
artifacts:
name: jsdoc
expire_in: 31d
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index cf87f5eb39c..aa25bad00e8 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -1,6 +1,6 @@
.dedicated-runner:
retry:
- max: 2 # This is confusing but this means "3 runs at max".
+ max: 2 # This is confusing but this means "3 runs at max".
when:
- unknown_failure
- api_failure
diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml
index 7d9136b8213..f7b18b809b4 100644
--- a/.gitlab/ci/pages.gitlab-ci.yml
+++ b/.gitlab/ci/pages.gitlab-ci.yml
@@ -1,4 +1,3 @@
-
pages:
extends: .dedicated-no-docs-no-db-pull-cache-job
before_script: []
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 6b23af952e6..ee9e467886a 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -1,6 +1,6 @@
package-and-qa:
image: ruby:2.6-alpine
- stage: review # So even if review-deploy failed we can still run this
+ stage: review # So even if review-deploy failed we can still run this
when: manual
before_script: []
dependencies: []
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 29534e40a14..33e7b00784c 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -6,8 +6,9 @@
.use-pg-10: &use-pg-10
services:
- - postgres:10.7
- - redis:alpine
+ - name: postgres:10.7
+ command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+ - name: redis:alpine
.use-mysql: &use-mysql
services:
@@ -52,8 +53,10 @@
script:
- JOB_NAME=( $CI_JOB_NAME )
- TEST_TOOL=${JOB_NAME[0]}
- - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${TEST_TOOL}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- - export KNAPSACK_GENERATE_REPORT=true
+ - TEST_LEVEL=${JOB_NAME[1]}
+ - DATABASE=${JOB_NAME[2]}
+ - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${TEST_TOOL}_${TEST_LEVEL}_${DATABASE}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
+ - export KNAPSACK_GENERATE_REPORT=true KNAPSACK_LOG_LEVEL=debug KNAPSACK_TEST_DIR=spec
- export SUITE_FLAKY_RSPEC_REPORT_PATH=${FLAKY_RSPEC_SUITE_REPORT_PATH}
- export FLAKY_RSPEC_REPORT_PATH=rspec_flaky/all_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export NEW_FLAKY_RSPEC_REPORT_PATH=rspec_flaky/new_${TEST_TOOL}_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
@@ -63,7 +66,10 @@
- '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
- scripts/gitaly-test-spawn
- - knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml"
+ - date
+ - 'export KNAPSACK_TEST_FILE_PATTERN=$(ruby -r./lib/quality/test_level.rb -e "puts Quality::TestLevel.new.pattern(:${TEST_LEVEL})")'
+ - knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml --tag level:${TEST_LEVEL} --tag ~geo"
+ - date
artifacts:
expire_in: 31d
when: always
@@ -130,7 +136,7 @@ setup-test-env:
stage: prepare
script:
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
- - scripts/gitaly-test-build # Do not use 'bundle exec' here
+ - scripts/gitaly-test-build # Do not use 'bundle exec' here
artifacts:
expire_in: 7d
paths:
@@ -140,19 +146,68 @@ setup-test-env:
except:
- /(^docs[\/-].*|.*-docs$)/
-rspec-pg:
+rspec unit pg:
+ <<: *rspec-metadata-pg
+ parallel: 20
+
+rspec integration pg:
<<: *rspec-metadata-pg
- parallel: 50
+ parallel: 6
+
+rspec system pg:
+ <<: *rspec-metadata-pg
+ parallel: 24
+
+rspec unit pg-10:
+ <<: *rspec-metadata-pg-10
+ <<: *only-schedules-master
+ parallel: 20
+
+rspec integration pg-10:
+ <<: *rspec-metadata-pg-10
+ <<: *only-schedules-master
+ parallel: 6
-rspec-pg-10:
+rspec system pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
- parallel: 50
+ parallel: 24
-rspec-mysql:
+rspec unit mysql:
<<: *rspec-metadata-mysql
<<: *only-schedules-master
- parallel: 50
+ parallel: 20
+
+rspec integration mysql:
+ <<: *rspec-metadata-mysql
+ <<: *only-schedules-master
+ parallel: 6
+
+rspec system mysql:
+ <<: *rspec-metadata-mysql
+ <<: *only-schedules-master
+ parallel: 24
+
+.rspec-mysql-on-demand: &rspec-mysql-on-demand
+ only:
+ variables:
+ - $CI_COMMIT_MESSAGE =~ /\[run mysql\]/i
+ - $CI_COMMIT_REF_NAME =~ /mysql/
+
+rspec unit mysql on-demand:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-mysql-on-demand
+ parallel: 20
+
+rspec integration mysql on-demand:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-mysql-on-demand
+ parallel: 6
+
+rspec system mysql on-demand:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-mysql-on-demand
+ parallel: 24
rspec-fast-spec-helper:
<<: *rspec-metadata-pg
@@ -164,16 +219,17 @@ rspec-fast-spec-helper:
script:
- export CACHE_CLASSES=true
- scripts/gitaly-test-spawn
- - bin/rspec --color --format documentation --tag quarantine spec/
+ - bin/rspec --color --format documentation --tag quarantine -- spec/
-rspec-pg-quarantine:
+rspec quarantine pg:
<<: *rspec-metadata-pg
<<: *rspec-quarantine
allow_failure: true
-rspec-mysql-quarantine:
+rspec quarantine mysql:
<<: *rspec-metadata-mysql
<<: *rspec-quarantine
+ <<: *only-schedules-master
allow_failure: true
static-analysis:
@@ -286,8 +342,8 @@ coverage:
name: coverage
expire_in: 31d
paths:
- - coverage/index.html
- - coverage/assets/
+ - coverage/index.html
+ - coverage/assets/
except:
- /(^docs[\/-].*|.*-docs$)/
- /(^qa[\/-].*|.*-qa$)/
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 80356fa1dc2..9b764028be9 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -76,8 +76,8 @@ schedule:review-build-cng:
.review-deploy-base: &review-deploy-base
<<: *review-base
+ allow_failure: true
stage: review
- retry: 2
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
@@ -128,6 +128,7 @@ review-stop:
.review-qa-base: &review-qa-base
<<: *review-docker
+ allow_failure: true
stage: qa
variables:
<<: *review-docker-variables
@@ -157,7 +158,6 @@ review-stop:
review-qa-smoke:
<<: *review-qa-base
- retry: 2
script:
- gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
@@ -165,7 +165,10 @@ review-qa-all:
<<: *review-qa-base
allow_failure: true
when: manual
+ parallel: 5
script:
+ - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/review-qa-all_master_report.json
+ - export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
- gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
.review-performance-base: &review-performance-base
diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml
index 4b595083ec6..c51f825f831 100644
--- a/.gitlab/ci/test-metadata.gitlab-ci.yml
+++ b/.gitlab/ci/test-metadata.gitlab-ci.yml
@@ -40,12 +40,12 @@ update-tests-metadata:
policy: push
script:
- retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document
- - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
+ - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec_*_pg_node_*.json
+ - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH'
+ - rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
- FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
- - '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH'
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
- - rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
- scripts/insert-rspec-profiling-data
only:
diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml
new file mode 100644
index 00000000000..401318d2df2
--- /dev/null
+++ b/.gitlab/ci/yaml.gitlab-ci.yml
@@ -0,0 +1,9 @@
+# Yamllint of *.yml for .gitlab-ci.yml.
+# This uses rules from project root `.yamllint`.
+lint-ci-gitlab:
+ extends: .dedicated-runner
+ before_script: []
+ dependencies: []
+ image: sdesbure/yamllint:latest
+ script:
+ - yamllint .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates
diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md
new file mode 100644
index 00000000000..b7db5a33faf
--- /dev/null
+++ b/.gitlab/issue_templates/Feature Flag Roll Out.md
@@ -0,0 +1,43 @@
+<!-- Title suggestion: [Feature flag] Enable description of feature -->
+
+## What
+
+Remove the `:feature_name` feature flag ...
+
+## Owners
+
+- Team: NAME_OF_TEAM
+- Most appropriate slack channel to reach out to: `#g_TEAM_NAME`
+- Best individual to reach out to: NAME
+
+## Expectations
+
+### What are we expecting to happen?
+
+### What might happen if this goes wrong?
+
+### What can we monitor to detect problems with this?
+
+<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? -->
+
+## Beta groups/projects
+
+If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example.
+
+- `gitlab-org/gitlab-ce`/`gitlab-org/gitlab-ee` projects
+- `gitlab-org`/`gitlab-com` groups
+- ...
+
+## Roll Out Steps
+
+- [ ] Enable on staging
+- [ ] Test on staging
+- [ ] Ensure that documentation has been updated
+- [ ] Enable on GitLab.com for individual groups/projects listed above and verify behaviour
+- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com
+- [ ] Enable on GitLab.com by running chatops command in `#production`
+- [ ] Cross post chatops slack command to `#support_gitlab-com` and in your team channel
+- [ ] Announce on the issue that the flag has been enabled
+- [ ] Remove feature flag and add changelog entry
+
+/label ~"feature flag"
diff --git a/.gitlab/route-map.yml b/.gitlab/route-map.yml
index 0b37dc68f8b..cc0c2856d49 100644
--- a/.gitlab/route-map.yml
+++ b/.gitlab/route-map.yml
@@ -1,3 +1,3 @@
# Documentation
-- source: /doc/(.+?)\.md/ # doc/administration/build_artifacts.md
- public: '\1.html' # doc/administration/build_artifacts.html
+- source: /doc/(.+?)\.md/ # doc/administration/build_artifacts.md
+ public: '\1.html' # doc/administration/build_artifacts.html
diff --git a/.yamllint b/.yamllint
new file mode 100644
index 00000000000..df7cdf404bc
--- /dev/null
+++ b/.yamllint
@@ -0,0 +1,7 @@
+---
+
+extends: default
+
+rules:
+ line-length: disable
+ document-start: disable
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 88521222b8a..8498658b39c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,34 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.11.3 (2019-06-10)
+
+### Fixed (5 changes)
+
+- Fix invalid visibility string comparison in project import. !28612
+- Remove a default git depth in Pipelines for merge requests. !28926
+- Fix connection to Tiller error while uninstalling. !29131
+- Fix label click scrolling to top. !29202
+- Make OpenID Connect work without requiring a name. !29312
+
+
+## 11.11.2 (2019-06-04)
+
+### Fixed (7 changes)
+
+- Update SAST.gitlab-ci.yml - Add SAST_GITLEAKS_ENTROPY_LEVEL. !28607
+- Fix OmniAuth OAuth2Generic strategy not loading. !28680
+- Use source ref in pipeline webhook. !28772
+- Fix migration failure when groups are missing route. !29022
+- Stop two-step rebase from hanging when errors occur. !29068
+- Fix project settings not being able to update. !29097
+- Fix display of 'Promote to group label' button.
+
+### Other (1 change)
+
+- Fix input group height.
+
+
## 11.11.0 (2019-05-22)
### Security (1 change)
@@ -187,6 +215,23 @@ entry.
- Add some frozen string to spec/**/*.rb. (gfyoung)
+## 11.10.6 (2019-06-04)
+
+### Fixed (7 changes, 1 of them is from the community)
+
+- Allow a member to have an access level equal to parent group. !27913
+- Fix uploading of LFS tracked file through UI. !28052
+- Use 3-way merge for squashing commits. !28078
+- Use a path for the related merge requests endpoint. !28171
+- Fix project visibility level validation. !28305 (Peter Marko)
+- Fix Rugged get_tree_entries recursive flag not working. !28494
+- Use source ref in pipeline webhook. !28772
+
+### Other (1 change)
+
+- Fix input group height.
+
+
## 11.10.4 (2019-05-01)
### Fixed (12 changes)
@@ -478,6 +523,24 @@ entry.
- Removes EE differences for environment_item.vue.
+## 11.9.12 (2019-05-30)
+
+### Security (12 changes, 1 of them is from the community)
+
+- Protect Gitlab::HTTP against DNS rebinding attack.
+- Fix project visibility level validation. (Peter Marko)
+- Update Knative version.
+- Add DNS rebinding protection settings.
+- Prevent XSS injection in note imports.
+- Prevent invalid branch for merge request.
+- Filter relative links in wiki for XSS.
+- Fix confidential issue label disclosure on milestone view.
+- Fix url redaction for issue links.
+- Resolve: Milestones leaked via search API.
+- Prevent bypass of restriction disabling web password sign in.
+- Hide confidential issue title on unsubscribe for anonymous users.
+
+
## 11.9.10 (2019-04-26)
### Security (5 changes)
diff --git a/Dangerfile b/Dangerfile
index 9e3a08949b0..d0a605f8d8e 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -1,5 +1,6 @@
# frozen_string_literal: true
danger.import_plugin('danger/plugins/helper.rb')
+danger.import_plugin('danger/plugins/roulette.rb')
unless helper.release_automation?
danger.import_dangerfile(path: 'danger/metadata')
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index e640847f99c..0a3db35b241 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.42.1
+1.46.0
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index 9084fa2f716..26aaba0e866 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-1.1.0
+1.2.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index bc80560fad6..dc1e644a101 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.5.0
+1.6.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index deeb3d66ef0..b13d146a7b0 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-9.2.0
+9.3.0
diff --git a/Gemfile b/Gemfile
index e2842ca27db..c22a3191c3c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -154,6 +154,7 @@ end
group :puma do
gem 'puma', '~> 3.12', require: false
gem 'puma_worker_killer', require: false
+ gem 'rack-timeout', require: false
end
# State machine
@@ -274,7 +275,7 @@ gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
-gem 'sentry-raven', '~> 2.7'
+gem 'sentry-raven', '~> 2.9'
gem 'premailer-rails', '~> 1.9.7'
@@ -335,7 +336,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.7.0'
gem 'factory_bot_rails', '~> 4.8.2'
gem 'rspec-rails', '~> 3.7.0'
- gem 'rspec-retry', '~> 0.4.5'
+ gem 'rspec-retry', '~> 0.6.1'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
gem 'rspec-parameterized', require: false
@@ -361,7 +362,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.31.0', require: false
- gem 'simplecov', '~> 0.14.0', require: false
+ gem 'simplecov', '~> 0.16.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false
@@ -372,6 +373,7 @@ group :development, :test do
gem 'activerecord_sane_schema_dumper', '1.0'
gem 'stackprof', '~> 0.2.10', require: false
+ gem 'derailed_benchmarks', require: false
gem 'simple_po_parser', '~> 1.1.2', require: false
@@ -399,6 +401,9 @@ gem 'html2text'
gem 'ruby-prof', '~> 0.17.0'
gem 'rbtrace', '~> 0.4', require: false
+gem 'memory_profiler', '~> 0.9', require: false
+gem 'benchmark-memory', '~> 0.1', require: false
+gem 'activerecord-explain-analyze', '~> 0.1', require: false
# OAuth
gem 'oauth2', '~> 1.4'
@@ -421,7 +426,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 1.27.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 1.32.0', require: 'gitaly'
gem 'grpc', '~> 1.19.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index a63b7cc84aa..785a77bc202 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -38,6 +38,9 @@ GEM
activemodel (= 5.1.7)
activesupport (= 5.1.7)
arel (~> 8.0)
+ activerecord-explain-analyze (0.1.0)
+ activerecord (>= 4)
+ pg
activerecord_sane_schema_dumper (1.0)
rails (>= 5, < 6)
activesupport (5.1.7)
@@ -82,6 +85,8 @@ GEM
bcrypt (3.1.12)
bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0)
+ benchmark-memory (0.1.2)
+ memory_profiler (~> 0.9)
better_errors (2.5.0)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
@@ -137,9 +142,9 @@ GEM
concord (0.1.5)
adamantium (~> 0.2.0)
equalizer (~> 0.0.9)
- concurrent-ruby (1.1.3)
- concurrent-ruby-ext (1.1.3)
- concurrent-ruby (= 1.1.3)
+ concurrent-ruby (1.1.5)
+ concurrent-ruby-ext (1.1.5)
+ concurrent-ruby (= 1.1.5)
connection_pool (2.2.2)
crack (0.4.3)
safe_yaml (~> 1.0.0)
@@ -155,6 +160,14 @@ GEM
html-pipeline
declarative (0.0.10)
declarative-option (0.1.0)
+ derailed_benchmarks (1.3.5)
+ benchmark-ips (~> 2)
+ get_process_mem (~> 0)
+ heapy (~> 0)
+ memory_profiler (~> 0)
+ rack (>= 1)
+ rake (> 10, < 13)
+ thor (~> 0.19)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
device_detector (1.0.0)
@@ -174,7 +187,7 @@ GEM
diffy (3.1.0)
discordrb-webhooks-blackst0ne (3.3.0)
rest-client (~> 2.0)
- docile (1.1.5)
+ docile (1.3.1)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.2)
@@ -285,7 +298,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (1.27.0)
+ gitaly-proto (1.32.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
@@ -377,6 +390,7 @@ GEM
hashie (>= 3.0)
health_check (2.6.0)
rails (>= 4.0)
+ heapy (0.1.4)
hipchat (1.5.2)
httparty
mimemagic
@@ -478,6 +492,7 @@ GEM
memoist (0.16.0)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
+ memory_profiler (0.9.13)
method_source (0.9.2)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
@@ -668,6 +683,7 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
+ rack-timeout (0.5.1)
rails (5.1.7)
actioncable (= 5.1.7)
actionmailer (= 5.1.7)
@@ -782,8 +798,8 @@ GEM
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-support (~> 3.7.0)
- rspec-retry (0.4.5)
- rspec-core
+ rspec-retry (0.6.1)
+ rspec-core (> 3.3)
rspec-set (0.1.3)
rspec-support (3.7.1)
rspec_junit_formatter (0.4.1)
@@ -873,11 +889,11 @@ GEM
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simple_po_parser (1.1.2)
- simplecov (0.14.1)
- docile (~> 1.1.0)
+ simplecov (0.16.1)
+ docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
- simplecov-html (0.10.0)
+ simplecov-html (0.10.2)
slack-notifier (1.5.1)
spring (2.0.2)
activesupport (>= 4.2)
@@ -999,6 +1015,7 @@ DEPENDENCIES
RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
acme-client (~> 2.0.2)
+ activerecord-explain-analyze (~> 0.1)
activerecord_sane_schema_dumper (= 1.0)
acts-as-taggable-on (~> 6.0)
addressable (~> 2.5.2)
@@ -1014,6 +1031,7 @@ DEPENDENCIES
batch-loader (~> 1.4.0)
bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
+ benchmark-memory (~> 0.1)
better_errors (~> 2.5.0)
binding_of_caller (~> 0.8.0)
bootsnap (~> 1.4)
@@ -1034,6 +1052,7 @@ DEPENDENCIES
creole (~> 0.5.0)
database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.2.0)
+ derailed_benchmarks
device_detector
devise (~> 4.6)
devise-two-factor (~> 3.0.0)
@@ -1068,7 +1087,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.27.0)
+ gitaly-proto (~> 1.32.0)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
gitlab-labkit (~> 0.2.0)
@@ -1111,6 +1130,7 @@ DEPENDENCIES
lograge (~> 0.5)
loofah (~> 2.2)
mail_room (~> 0.9.1)
+ memory_profiler (~> 0.9)
method_source (~> 0.8)
mimemagic (~> 0.3.2)
mini_magick
@@ -1159,6 +1179,7 @@ DEPENDENCIES
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
+ rack-timeout
rails (= 5.1.7)
rails-controller-testing
rails-i18n (~> 5.1)
@@ -1178,7 +1199,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.7.0)
- rspec-retry (~> 0.4.5)
+ rspec-retry (~> 0.6.1)
rspec-set (~> 0.1.3)
rspec_junit_formatter
rspec_profiling (~> 0.0.5)
@@ -1196,14 +1217,14 @@ DEPENDENCIES
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
selenium-webdriver (~> 3.141)
- sentry-raven (~> 2.7)
+ sentry-raven (~> 2.9)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 4.0.1)
sidekiq (~> 5.2.7)
sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2)
- simplecov (~> 0.14.0)
+ simplecov (~> 0.16.1)
slack-notifier (~> 1.5.1)
spring (~> 2.0.0)
spring-commands-rspec (~> 1.0.4)
diff --git a/PROCESS.md b/PROCESS.md
index 1f99cebe081..3c40f658070 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -113,7 +113,7 @@ corresponding exception request to be created.
A level of common sense should be applied when deciding whether to have a feature
behind a feature flag off or on by default.
-The following guideliness can be applied to help make this decision:
+The following guidelines can be applied to help make this decision:
* If the feature is not fully ready or functioning, the feature flag should be disabled by default.
* If the feature is ready but there are concerns about performance or impact, the feature flag should be enabled by default, but
@@ -125,7 +125,7 @@ For more information on rolling out changes using feature flags, read [through t
In order to build the final package and present the feature for self-hosted
customers, the feature flag should be removed. This should happen before the
22nd, ideally _at least_ 2 days before. That means MRs with feature
-flags being picked at the 19th would have a quite tight schedule, so picking
+flags being picked at the 19th would have quite a tight schedule, so picking
these _earlier_ is preferable.
While rare, release managers may decide to reject picking a change into a stable
diff --git a/VERSION b/VERSION
index 6a02cfa7c06..11bf1ec035c 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.12.0-pre
+12.0.0-pre
diff --git a/app/assets/images/favicon-yellow.png b/app/assets/images/favicon-yellow.png
index 2d5289818b4..a80827808fc 100644
--- a/app/assets/images/favicon-yellow.png
+++ b/app/assets/images/favicon-yellow.png
Binary files differ
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index e583a8affd4..7cebb88f3a4 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -12,7 +12,7 @@ const Api = {
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
projectPath: '/api/:version/projects/:id',
- projectLabelsPath: '/:namespace_path/:project_path/labels',
+ projectLabelsPath: '/:namespace_path/:project_path/-/labels',
projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
diff --git a/app/assets/javascripts/batch_comments/mixins/resolved_status.js b/app/assets/javascripts/batch_comments/mixins/resolved_status.js
index 96ee9f62ba4..3bbbaa86b51 100644
--- a/app/assets/javascripts/batch_comments/mixins/resolved_status.js
+++ b/app/assets/javascripts/batch_comments/mixins/resolved_status.js
@@ -1,10 +1,12 @@
+import { sprintf, __ } from '~/locale';
+
export default {
computed: {
resolveButtonTitle() {
- let title = 'Mark comment as resolved';
+ let title = __('Mark comment as resolved');
if (this.resolvedBy) {
- title = `Resolved by ${this.resolvedBy.name}`;
+ title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
}
return title;
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index 670f66b005e..c8eb96a625c 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -37,7 +37,7 @@ export default class ShortcutsIssuable extends Shortcuts {
}
// Sanity check: Make sure the selected text comes from a discussion : it can either contain a message...
- let foundMessage = !!documentFragment.querySelector('.md');
+ let foundMessage = Boolean(documentFragment.querySelector('.md'));
// ... Or come from a message
if (!foundMessage) {
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index 47a46502bff..1cbd31729cd 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -1,6 +1,5 @@
<script>
/* global ListLabel */
-import _ from 'underscore';
import Cookies from 'js-cookie';
import boardsStore from '../stores/boards_store';
@@ -29,8 +28,6 @@ export default {
});
});
- boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
-
// Save the labels
gl.boardService
.generateDefaultLists()
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index b8882203cc7..179148b6887 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -66,7 +66,7 @@ export default {
eventHub.$emit('clearDetailIssue');
} else {
eventHub.$emit('newDetailIssue', this.issue);
- boardsStore.detail.list = this.list;
+ boardsStore.setListDetail(this.list);
}
}
},
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index c9972d051aa..b1a8b13f3ac 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -142,8 +142,10 @@ export default {
const card = this.$refs.issue[e.oldIndex];
card.showDetail = false;
- boardsStore.moving.list = card.list;
- boardsStore.moving.issue = boardsStore.moving.list.findIssue(+e.item.dataset.issueId);
+
+ const { list } = card;
+ const issue = list.findIssue(Number(e.item.dataset.issueId));
+ boardsStore.startMoving(list, issue);
sortableStart();
},
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index dc1bdc23b5e..cc6af8e88cd 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -72,8 +72,8 @@ export default {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
- boardsStore.detail.issue = issue;
- boardsStore.detail.list = this.list;
+ boardsStore.setIssueDetail(issue);
+ boardsStore.setListDetail(this.list);
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index 17de7b2cf1e..a8516f178fc 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -6,7 +6,6 @@ import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-import eventHub from '../eventhub';
import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
import boardsStore from '../stores/boards_store';
@@ -136,23 +135,7 @@ export default {
const labelTitle = encodeURIComponent(label.title);
const filter = `label_name[]=${labelTitle}`;
- this.applyFilter(filter);
- },
- applyFilter(filter) {
- const filterPath = boardsStore.filter.path.split('&');
- const filterIndex = filterPath.indexOf(filter);
-
- if (filterIndex === -1) {
- filterPath.push(filter);
- } else {
- filterPath.splice(filterIndex, 1);
- }
-
- boardsStore.filter.path = filterPath.join('&');
-
- boardsStore.updateFiltersUrl();
-
- eventHub.$emit('updateTokens');
+ boardsStore.toggleFilter(filter);
},
labelStyle(label) {
return {
diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue
index 8e09e265cfb..defa1f75ba2 100644
--- a/app/assets/javascripts/boards/components/modal/index.vue
+++ b/app/assets/javascripts/boards/components/modal/index.vue
@@ -124,7 +124,7 @@ export default {
data.issues.forEach(issueObj => {
const issue = new ListIssue(issueObj);
const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
- issue.selected = !!foundSelectedIssue;
+ issue.selected = Boolean(foundSelectedIssue);
this.issues.push(issue);
});
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index a5ed695af35..c8a9cb1c296 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -2,7 +2,6 @@
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
-import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
import boardsStore from '../stores/boards_store';
@@ -78,8 +77,6 @@ export default function initNewListDropdown() {
color: label.color,
},
});
-
- boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
}
},
});
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
index a2b8a0af236..4ab2b17301f 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
@@ -48,7 +48,7 @@ export default Vue.extend({
list.removeIssue(issue);
});
- boardsStore.detail.issue = {};
+ boardsStore.clearDetailIssue();
},
/**
* Build the default patch request.
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 4995a8d9367..f2f37d22b97 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,11 +1,10 @@
import $ from 'jquery';
-import _ from 'underscore';
import Vue from 'vue';
import Flash from '~/flash';
import { __ } from '~/locale';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import './models/label';
+import './models/assignee';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
@@ -106,18 +105,23 @@ export default () => {
gl.boardService
.all()
.then(res => res.data)
- .then(data => {
- data.forEach(board => {
- const list = boardsStore.addList(board, this.defaultAvatar);
-
- if (list.type === 'closed') {
- list.position = Infinity;
- } else if (list.type === 'backlog') {
- list.position = -1;
+ .then(lists => {
+ lists.forEach(listObj => {
+ let { position } = listObj;
+ if (listObj.list_type === 'closed') {
+ position = Infinity;
+ } else if (listObj.list_type === 'backlog') {
+ position = -1;
}
- });
- this.state.lists = _.sortBy(this.state.lists, 'position');
+ boardsStore.addList(
+ {
+ ...listObj,
+ position,
+ },
+ this.defaultAvatar,
+ );
+ });
boardsStore.addBlankState();
this.loading = false;
@@ -164,10 +168,10 @@ export default () => {
});
}
- boardsStore.detail.issue = newIssue;
+ boardsStore.setIssueDetail(newIssue);
},
clearDetailIssue() {
- boardsStore.detail.issue = {};
+ boardsStore.clearDetailIssue();
},
toggleSubscription(id) {
const { issue } = boardsStore.detail;
diff --git a/app/assets/javascripts/vue_shared/models/assignee.js b/app/assets/javascripts/boards/models/assignee.js
index 4a29b0d0581..4a29b0d0581 100644
--- a/app/assets/javascripts/vue_shared/models/assignee.js
+++ b/app/assets/javascripts/boards/models/assignee.js
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index f8ff20cb0cd..f858b162c6b 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -4,7 +4,7 @@
/* global ListAssignee */
import Vue from 'vue';
-import '~/vue_shared/models/label';
+import './label';
import { isEE, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import IssueProject from './project';
import boardsStore from '../stores/boards_store';
diff --git a/app/assets/javascripts/boards/models/label.js b/app/assets/javascripts/boards/models/label.js
new file mode 100644
index 00000000000..cd2a2c0137f
--- /dev/null
+++ b/app/assets/javascripts/boards/models/label.js
@@ -0,0 +1,11 @@
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+
+export default class ListLabel {
+ constructor(obj) {
+ Object.assign(this, convertObjectPropsToCamelCase(obj, { dropKeys: ['priority'] }), {
+ priority: obj.priority !== null ? obj.priority : Infinity,
+ });
+ }
+}
+
+window.ListLabel = ListLabel;
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 7e5d0e0f888..a9d88f19146 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -2,8 +2,8 @@
/* global ListIssue */
import { __ } from '~/locale';
-import ListLabel from '~/vue_shared/models/label';
-import ListAssignee from '~/vue_shared/models/assignee';
+import ListLabel from './label';
+import ListAssignee from './assignee';
import { isEE, urlParamsToObject } from '~/lib/utils/common_utils';
import boardsStore from '../stores/boards_store';
import ListMilestone from './milestone';
@@ -37,8 +37,8 @@ class List {
this.type = obj.list_type;
const typeInfo = this.getTypeInfo(this.type);
- this.preset = !!typeInfo.isPreset;
- this.isExpandable = !!typeInfo.isExpandable;
+ this.preset = Boolean(typeInfo.isPreset);
+ this.isExpandable = Boolean(typeInfo.isExpandable);
this.isExpanded = true;
this.page = 1;
this.loading = true;
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 70861fbf2b3..4b3b44574a8 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -8,6 +8,7 @@ import Cookies from 'js-cookie';
import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
+import eventHub from '../eventhub';
const boardsStore = {
disabled: false,
@@ -45,7 +46,7 @@ const boardsStore = {
},
addList(listObj, defaultAvatar) {
const list = new List(listObj, defaultAvatar);
- this.state.lists.push(list);
+ this.state.lists = _.sortBy([...this.state.lists, list], 'position');
return list;
},
@@ -82,8 +83,6 @@ const boardsStore = {
title: __('Welcome to your Issue Board!'),
position: 0,
});
-
- this.state.lists = _.sortBy(this.state.lists, 'position');
},
removeBlankState() {
this.removeList('blank');
@@ -111,6 +110,11 @@ const boardsStore = {
});
listFrom.update();
},
+
+ startMoving(list, issue) {
+ Object.assign(this.moving, { list, issue });
+ },
+
moveIssueToList(listFrom, listTo, issue, newIndex) {
const issueTo = listTo.findIssue(issue.id);
const issueLists = issue.getLists();
@@ -185,9 +189,39 @@ const boardsStore = {
findListByLabelId(id) {
return this.state.lists.find(list => list.type === 'label' && list.label.id === id);
},
+
+ toggleFilter(filter) {
+ const filterPath = this.filter.path.split('&');
+ const filterIndex = filterPath.indexOf(filter);
+
+ if (filterIndex === -1) {
+ filterPath.push(filter);
+ } else {
+ filterPath.splice(filterIndex, 1);
+ }
+
+ this.filter.path = filterPath.join('&');
+
+ this.updateFiltersUrl();
+
+ eventHub.$emit('updateTokens');
+ },
+
+ setListDetail(newList) {
+ this.detail.list = newList;
+ },
+
updateFiltersUrl() {
window.history.pushState(null, null, `?${this.filter.path}`);
},
+
+ clearDetailIssue() {
+ this.setIssueDetail({});
+ },
+
+ setIssueDetail(issueDetail) {
+ this.detail.issue = issueDetail;
+ },
};
BoardsStoreEE.initEESpecific(boardsStore);
diff --git a/app/assets/javascripts/branches/branches_delete_modal.js b/app/assets/javascripts/branches/branches_delete_modal.js
index f34496f84c6..f4c3fa185d8 100644
--- a/app/assets/javascripts/branches/branches_delete_modal.js
+++ b/app/assets/javascripts/branches/branches_delete_modal.js
@@ -23,7 +23,7 @@ class DeleteModal {
const branchData = e.currentTarget.dataset;
this.branchName = branchData.branchName || '';
this.deletePath = branchData.deletePath || '';
- this.isMerged = !!branchData.isMerged;
+ this.isMerged = Boolean(branchData.isMerged);
this.updateModal();
}
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
index 592e1fd1c31..0bba2a2e160 100644
--- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -27,15 +27,24 @@ function generateErrorBoxContent(errors) {
// Used for the variable list on CI/CD projects/groups settings page
export default class AjaxVariableList {
- constructor({ container, saveButton, errorBox, formField = 'variables', saveEndpoint }) {
+ constructor({
+ container,
+ saveButton,
+ errorBox,
+ formField = 'variables',
+ saveEndpoint,
+ maskableRegex,
+ }) {
this.container = container;
this.saveButton = saveButton;
this.errorBox = errorBox;
this.saveEndpoint = saveEndpoint;
+ this.maskableRegex = maskableRegex;
this.variableList = new VariableList({
container: this.container,
formField,
+ maskableRegex,
});
this.bindEvents();
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index 0390a3bf96a..0303e4e51dd 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -16,9 +16,10 @@ function createEnvironmentItem(value) {
}
export default class VariableList {
- constructor({ container, formField }) {
+ constructor({ container, formField, maskableRegex }) {
this.$container = $(container);
this.formField = formField;
+ this.maskableRegex = new RegExp(maskableRegex);
this.environmentDropdownMap = new WeakMap();
this.inputMap = {
@@ -196,9 +197,8 @@ export default class VariableList {
validateMaskability($row) {
const invalidInputClass = 'gl-field-error-outline';
- const maskableRegex = /^\w{8,}$/; // Eight or more alphanumeric characters plus underscores
const variableValue = $row.find(this.inputMap.secret_value.selector).val();
- const isValueMaskable = maskableRegex.test(variableValue) || variableValue === '';
+ const isValueMaskable = this.maskableRegex.test(variableValue) || variableValue === '';
const isMaskedChecked = $row.find(this.inputMap.masked.selector).val() === 'true';
// Show a validation error if the user wants to mask an unmaskable variable value
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 561b6bdd9f1..aacfa0d87e6 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -1,5 +1,6 @@
import Visibility from 'visibilityjs';
import Vue from 'vue';
+import AccessorUtilities from '~/lib/utils/accessor';
import { GlToast } from '@gitlab/ui';
import PersistentUserCallout from '../persistent_user_callout';
import { s__, sprintf } from '../locale';
@@ -43,8 +44,10 @@ export default class Clusters {
helpPath,
ingressHelpPath,
ingressDnsHelpPath,
+ clusterId,
} = document.querySelector('.js-edit-cluster-form').dataset;
+ this.clusterId = clusterId;
this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath);
@@ -69,6 +72,10 @@ export default class Clusters {
this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success');
this.creatingContainer = document.querySelector('.js-cluster-creating');
+ this.unreachableContainer = document.querySelector('.js-cluster-api-unreachable');
+ this.authenticationFailureContainer = document.querySelector(
+ '.js-cluster-authentication-failure',
+ );
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.showTokenButton = document.querySelector('.js-show-cluster-token');
@@ -125,21 +132,29 @@ export default class Clusters {
PersistentUserCallout.factory(callout);
}
+ addBannerCloseHandler(el, status) {
+ el.querySelector('.js-close-banner').addEventListener('click', () => {
+ el.classList.add('hidden');
+ this.setBannerDismissedState(status, true);
+ });
+ }
+
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
- eventHub.$on('upgradeApplication', data => this.upgradeApplication(data));
- eventHub.$on('dismissUpgradeSuccess', appId => this.dismissUpgradeSuccess(appId));
+ eventHub.$on('updateApplication', data => this.updateApplication(data));
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
+ // Add event listener to all the banner close buttons
+ this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
+ this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
}
removeListeners() {
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
eventHub.$off('installApplication', this.installApplication);
- eventHub.$off('upgradeApplication', this.upgradeApplication);
- eventHub.$off('dismissUpgradeSuccess', this.dismissUpgradeSuccess);
+ eventHub.$off('updateApplication', this.updateApplication);
eventHub.$off('saveKnativeDomain');
eventHub.$off('setKnativeHostname');
eventHub.$off('uninstallApplication');
@@ -205,6 +220,8 @@ export default class Clusters {
this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden');
this.creatingContainer.classList.add('hidden');
+ this.unreachableContainer.classList.add('hidden');
+ this.authenticationFailureContainer.classList.add('hidden');
}
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
@@ -228,9 +245,32 @@ export default class Clusters {
}
}
+ setBannerDismissedState(status, isDismissed) {
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ window.localStorage.setItem(
+ `cluster_${this.clusterId}_banner_dismissed`,
+ `${status}_${isDismissed}`,
+ );
+ }
+ }
+
+ isBannerDismissed(status) {
+ let bannerState;
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ bannerState = window.localStorage.getItem(`cluster_${this.clusterId}_banner_dismissed`);
+ }
+
+ return bannerState === `${status}_true`;
+ }
+
updateContainer(prevStatus, status, error) {
this.hideAll();
+ if (this.isBannerDismissed(status)) {
+ return;
+ }
+ this.setBannerDismissedState(status, false);
+
// We poll all the time but only want the `created` banner to show when newly created
if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) {
switch (status) {
@@ -241,6 +281,12 @@ export default class Clusters {
this.errorContainer.classList.remove('hidden');
this.errorReasonContainer.textContent = error;
break;
+ case 'unreachable':
+ this.unreachableContainer.classList.remove('hidden');
+ break;
+ case 'authentication_failure':
+ this.authenticationFailureContainer.classList.remove('hidden');
+ break;
case 'scheduled':
case 'creating':
this.creatingContainer.classList.remove('hidden');
@@ -283,19 +329,13 @@ export default class Clusters {
});
}
- upgradeApplication(data) {
- const appId = data.id;
-
+ updateApplication({ id: appId, params }) {
this.store.updateApplication(appId);
- this.service.installApplication(appId, data.params).catch(() => {
+ this.service.installApplication(appId, params).catch(() => {
this.store.notifyUpdateFailure(appId);
});
}
- dismissUpgradeSuccess(appId) {
- this.store.acknowledgeSuccessfulUpdate(appId);
- }
-
toggleIngressDomainHelpText({ externalIp }, { externalIp: newExternalIp }) {
if (externalIp !== newExternalIp) {
this.ingressDomainHelpText.classList.toggle('hide', !newExternalIp);
@@ -305,8 +345,10 @@ export default class Clusters {
saveKnativeDomain(data) {
const appId = data.id;
- this.store.updateAppProperty(appId, 'status', APPLICATION_STATUS.UPDATING);
- this.service.updateApplication(appId, data.params);
+ this.store.updateApplication(appId);
+ this.service.updateApplication(appId, data.params).catch(() => {
+ this.store.notifyUpdateFailure(appId);
+ });
}
setKnativeHostname(data) {
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 5f7675bb432..4771090aa7e 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -2,7 +2,7 @@
/* eslint-disable vue/require-default-prop */
import { GlLink, GlModalDirective } from '@gitlab/ui';
import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
-import { s__, sprintf } from '../../locale';
+import { s__, __, sprintf } from '~/locale';
import eventHub from '../event_hub';
import identicon from '../../vue_shared/components/identicon.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
@@ -85,10 +85,14 @@ export default {
type: String,
required: false,
},
- upgradeAvailable: {
+ updateAvailable: {
type: Boolean,
required: false,
},
+ updateable: {
+ type: Boolean,
+ default: true,
+ },
updateSuccessful: {
type: Boolean,
required: false,
@@ -109,11 +113,6 @@ export default {
required: false,
default: false,
},
- updateAcknowledged: {
- type: Boolean,
- required: false,
- default: true,
- },
installApplicationRequestParams: {
type: Object,
required: false,
@@ -138,7 +137,7 @@ export default {
);
},
hasLogo() {
- return !!this.logoUrl;
+ return Boolean(this.logoUrl);
},
identiconId() {
// generate a deterministic integer id for the identicon background
@@ -170,11 +169,11 @@ export default {
installButtonLabel() {
let label;
if (this.canInstall) {
- label = s__('ClusterIntegration|Install');
+ label = __('Install');
} else if (this.isInstalling) {
- label = s__('ClusterIntegration|Installing');
+ label = __('Installing');
} else if (this.installed) {
- label = s__('ClusterIntegration|Installed');
+ label = __('Installed');
}
return label;
@@ -183,7 +182,7 @@ export default {
return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
},
manageButtonLabel() {
- return s__('ClusterIntegration|Manage');
+ return __('Manage');
},
hasError() {
return this.installFailed || this.uninstallFailed;
@@ -203,42 +202,42 @@ export default {
},
versionLabel() {
if (this.updateFailed) {
- return s__('ClusterIntegration|Upgrade failed');
- } else if (this.isUpgrading) {
- return s__('ClusterIntegration|Upgrading');
+ return __('Update failed');
+ } else if (this.isUpdating) {
+ return __('Updating');
}
- return s__('ClusterIntegration|Upgraded');
+ return __('Updated');
},
- upgradeFailureDescription() {
+ updateFailureDescription() {
return s__('ClusterIntegration|Update failed. Please check the logs and try again.');
},
- upgradeSuccessDescription() {
- return sprintf(s__('ClusterIntegration|%{title} upgraded successfully.'), {
+ updateSuccessDescription() {
+ return sprintf(s__('ClusterIntegration|%{title} updated successfully.'), {
title: this.title,
});
},
- upgradeButtonLabel() {
+ updateButtonLabel() {
let label;
- if (this.upgradeAvailable && !this.updateFailed && !this.isUpgrading) {
- label = s__('ClusterIntegration|Upgrade');
- } else if (this.isUpgrading) {
- label = s__('ClusterIntegration|Updating');
+ if (this.updateAvailable && !this.updateFailed && !this.isUpdating) {
+ label = __('Update');
+ } else if (this.isUpdating) {
+ label = __('Updating');
} else if (this.updateFailed) {
- label = s__('ClusterIntegration|Retry update');
+ label = __('Retry update');
}
return label;
},
- isUpgrading() {
+ isUpdating() {
// Since upgrading is handled asynchronously on the backend we need this check to prevent any delay on the frontend
return this.status === APPLICATION_STATUS.UPDATING;
},
- shouldShowUpgradeDetails() {
+ shouldShowUpdateDetails() {
// This method only returns true when;
- // Upgrade was successful OR Upgrade failed
- // AND new upgrade is unavailable AND version information is present.
- return (this.updateSuccessful || this.updateFailed) && !this.upgradeAvailable && this.version;
+ // Update was successful OR Update failed
+ // AND new update is unavailable AND version information is present.
+ return (this.updateSuccessful || this.updateFailed) && !this.updateAvailable && this.version;
},
uninstallSuccessDescription() {
return sprintf(s__('ClusterIntegration|%{title} uninstalled successfully.'), {
@@ -249,7 +248,7 @@ export default {
watch: {
updateSuccessful(updateSuccessful) {
if (updateSuccessful) {
- this.$toast.show(this.upgradeSuccessDescription);
+ this.$toast.show(this.updateSuccessDescription);
}
},
uninstallSuccessful(uninstallSuccessful) {
@@ -265,8 +264,8 @@ export default {
params: this.installApplicationRequestParams,
});
},
- upgradeClicked() {
- eventHub.$emit('upgradeApplication', {
+ updateClicked() {
+ eventHub.$emit('updateApplication', {
id: this.id,
params: this.installApplicationRequestParams,
});
@@ -326,36 +325,38 @@ export default {
</ul>
</div>
- <div
- v-if="shouldShowUpgradeDetails"
- class="form-text text-muted label p-0 js-cluster-application-upgrade-details"
- >
- {{ versionLabel }}
- <span v-if="updateSuccessful">to</span>
-
- <gl-link
- v-if="updateSuccessful"
- :href="chartRepo"
- target="_blank"
- class="js-cluster-application-upgrade-version"
- >chart v{{ version }}</gl-link
+ <div v-if="updateable">
+ <div
+ v-if="shouldShowUpdateDetails"
+ class="form-text text-muted label p-0 js-cluster-application-update-details"
>
- </div>
+ {{ versionLabel }}
+ <span v-if="updateSuccessful">to</span>
- <div
- v-if="updateFailed && !isUpgrading"
- class="bs-callout bs-callout-danger cluster-application-banner mt-2 mb-0 js-cluster-application-upgrade-failure-message"
- >
- {{ upgradeFailureDescription }}
+ <gl-link
+ v-if="updateSuccessful"
+ :href="chartRepo"
+ target="_blank"
+ class="js-cluster-application-update-version"
+ >chart v{{ version }}</gl-link
+ >
+ </div>
+
+ <div
+ v-if="updateFailed && !isUpdating"
+ class="bs-callout bs-callout-danger cluster-application-banner mt-2 mb-0 js-cluster-application-update-details"
+ >
+ {{ updateFailureDescription }}
+ </div>
+ <loading-button
+ v-if="updateAvailable || updateFailed || isUpdating"
+ class="btn btn-primary js-cluster-application-update-button mt-2"
+ :loading="isUpdating"
+ :disabled="isUpdating"
+ :label="updateButtonLabel"
+ @click="updateClicked"
+ />
</div>
- <loading-button
- v-if="upgradeAvailable || updateFailed || isUpgrading"
- class="btn btn-primary js-cluster-application-upgrade-button mt-2"
- :loading="isUpgrading"
- :disabled="isUpgrading"
- :label="upgradeButtonLabel"
- @click="upgradeClicked"
- />
</div>
<div
:class="{ 'section-25': showManageButton, 'section-15': !showManageButton }"
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 73760da9b98..970f5a7b297 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -15,6 +15,7 @@ import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
+import KnativeDomainEditor from './knative_domain_editor.vue';
import { CLUSTER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import eventHub from '~/clusters/event_hub';
@@ -25,6 +26,7 @@ export default {
clipboardButton,
LoadingButton,
GlLoadingIcon,
+ KnativeDomainEditor,
},
props: {
type: {
@@ -154,64 +156,21 @@ export default {
knative() {
return this.applications.knative;
},
- knativeInstalled() {
- return (
- this.knative.status === APPLICATION_STATUS.INSTALLED ||
- this.knativeUpgrading ||
- this.knativeUpgradeFailed ||
- this.knative.status === APPLICATION_STATUS.UPDATED
- );
- },
- knativeUpgrading() {
- return (
- this.knative.status === APPLICATION_STATUS.UPDATING ||
- this.knative.status === APPLICATION_STATUS.SCHEDULED
- );
- },
- knativeUpgradeFailed() {
- return this.knative.status === APPLICATION_STATUS.UPDATE_ERRORED;
- },
- knativeExternalEndpoint() {
- return this.knative.externalIp || this.knative.externalHostname;
- },
- knativeDescription() {
- return sprintf(
- _.escape(
- s__(
- `ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}.`,
- ),
- ),
- {
- pricingLink: `<strong><a href="https://cloud.google.com/compute/pricing#lb"
- target="_blank" rel="noopener noreferrer">
- ${_.escape(s__('ClusterIntegration|pricing'))}</a></strong>`,
- },
- false,
- );
- },
- canUpdateKnativeEndpoint() {
- return this.knativeExternalEndpoint && !this.knativeUpgradeFailed && !this.knativeUpgrading;
- },
- knativeHostname: {
- get() {
- return this.knative.hostname;
- },
- set(hostname) {
- eventHub.$emit('setKnativeHostname', {
- id: 'knative',
- hostname,
- });
- },
- },
},
created() {
this.helmInstallIllustration = helmInstallIllustration;
},
methods: {
- saveKnativeDomain() {
+ saveKnativeDomain(hostname) {
eventHub.$emit('saveKnativeDomain', {
id: 'knative',
- params: { hostname: this.knative.hostname },
+ params: { hostname },
+ });
+ },
+ setKnativeHostname(hostname) {
+ eventHub.$emit('setKnativeHostname', {
+ id: 'knative',
+ hostname,
});
},
},
@@ -318,9 +277,9 @@ export default {
generated endpoint in order to access
your application after it has been deployed.`)
}}
- <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
- __('More information')
- }}</a>
+ <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
+ {{ __('More information') }}
+ </a>
</p>
</div>
@@ -330,9 +289,9 @@ export default {
the process of being assigned. Please check your Kubernetes
cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
}}
- <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
- __('More information')
- }}</a>
+ <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
+ {{ __('More information') }}
+ </a>
</p>
</template>
<template v-if="!ingressInstalled">
@@ -361,9 +320,9 @@ export default {
<div slot="description">
<p v-html="certManagerDescription"></p>
<div class="form-group">
- <label for="cert-manager-issuer-email">{{
- s__('ClusterIntegration|Issuer Email')
- }}</label>
+ <label for="cert-manager-issuer-email">
+ {{ s__('ClusterIntegration|Issuer Email') }}
+ </label>
<div class="input-group">
<input
v-model="applications.cert_manager.email"
@@ -417,7 +376,7 @@ export default {
:request-reason="applications.runner.requestReason"
:version="applications.runner.version"
:chart-repo="applications.runner.chartRepo"
- :upgrade-available="applications.runner.upgradeAvailable"
+ :update-available="applications.runner.updateAvailable"
:installed="applications.runner.installed"
:install-failed="applications.runner.installFailed"
:update-successful="applications.runner.updateSuccessful"
@@ -491,9 +450,9 @@ export default {
s__(`ClusterIntegration|Replace this with your own hostname if you want.
If you do so, point hostname to Ingress IP Address from above.`)
}}
- <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
- __('More information')
- }}</a>
+ <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
+ {{ __('More information') }}
+ </a>
</p>
</div>
</template>
@@ -514,6 +473,7 @@ export default {
:uninstallable="applications.knative.uninstallable"
:uninstall-successful="applications.knative.uninstallSuccessful"
:uninstall-failed="applications.knative.uninstallFailed"
+ :updateable="false"
:disabled="!helmInstalled"
v-bind="applications.knative"
title-link="https://github.com/knative/docs"
@@ -525,9 +485,9 @@ export default {
s__(`ClusterIntegration|You must have an RBAC-enabled cluster
to install Knative.`)
}}
- <a :href="helpPath" target="_blank" rel="noopener noreferrer">{{
- __('More information')
- }}</a>
+ <a :href="helpPath" target="_blank" rel="noopener noreferrer">
+ {{ __('More information') }}
+ </a>
</p>
<br />
</span>
@@ -540,83 +500,13 @@ export default {
}}
</p>
- <div class="row">
- <template v-if="knativeInstalled || (helmInstalled && rbac)">
- <div
- :class="{ 'col-md-6': knativeInstalled, 'col-12': helmInstalled && rbac }"
- class="form-group col-sm-12 mb-0"
- >
- <label for="knative-domainname">
- <strong>{{ s__('ClusterIntegration|Knative Domain Name:') }}</strong>
- </label>
- <input
- id="knative-domainname"
- v-model="knativeHostname"
- type="text"
- class="form-control js-knative-domainname"
- />
- </div>
- </template>
- <template v-if="knativeInstalled">
- <div class="form-group col-sm-12 col-md-6 pl-md-0 mb-0 mt-3 mt-md-0">
- <label for="knative-endpoint">
- <strong>{{ s__('ClusterIntegration|Knative Endpoint:') }}</strong>
- </label>
- <div v-if="knativeExternalEndpoint" class="input-group">
- <input
- id="knative-endpoint"
- :value="knativeExternalEndpoint"
- type="text"
- class="form-control js-knative-endpoint"
- readonly
- />
- <span class="input-group-append">
- <clipboard-button
- :text="knativeExternalEndpoint"
- :title="s__('ClusterIntegration|Copy Knative Endpoint to clipboard')"
- class="input-group-text js-knative-endpoint-clipboard-btn"
- />
- </span>
- </div>
- <div v-else class="input-group">
- <input type="text" class="form-control js-endpoint" readonly />
- <gl-loading-icon
- class="position-absolute align-self-center ml-2 js-knative-ip-loading-icon"
- />
- </div>
- </div>
-
- <p class="form-text text-muted col-12">
- {{
- s__(
- `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>
- </p>
-
- <p
- v-if="!knativeExternalEndpoint"
- class="settings-message js-no-knative-endpoint-message mt-2 mr-3 mb-0 ml-3"
- >
- {{
- s__(`ClusterIntegration|The endpoint is in
- the process of being assigned. Please check your Kubernetes
- cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
- }}
- </p>
-
- <button
- v-if="canUpdateKnativeEndpoint"
- class="btn btn-success js-knative-save-domain-button mt-3 ml-3"
- @click="saveKnativeDomain"
- >
- {{ s__('ClusterIntegration|Save changes') }}
- </button>
- </template>
- </div>
+ <knative-domain-editor
+ v-if="knative.installed || (helmInstalled && rbac)"
+ :knative="knative"
+ :ingress-dns-help-path="ingressDnsHelpPath"
+ @save="saveKnativeDomain"
+ @set="setKnativeHostname"
+ />
</div>
</application-row>
</div>
diff --git a/app/assets/javascripts/clusters/components/knative_domain_editor.vue b/app/assets/javascripts/clusters/components/knative_domain_editor.vue
new file mode 100644
index 00000000000..480228619a5
--- /dev/null
+++ b/app/assets/javascripts/clusters/components/knative_domain_editor.vue
@@ -0,0 +1,150 @@
+<script>
+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 { APPLICATION_STATUS } from '~/clusters/constants';
+
+const { UPDATING, UNINSTALLING } = APPLICATION_STATUS;
+
+export default {
+ components: {
+ LoadingButton,
+ ClipboardButton,
+ GlLoadingIcon,
+ },
+ props: {
+ knative: {
+ type: Object,
+ required: true,
+ },
+ ingressDnsHelpPath: {
+ type: String,
+ default: '',
+ },
+ },
+ computed: {
+ saveButtonDisabled() {
+ return [UNINSTALLING, UPDATING].includes(this.knative.status);
+ },
+ saving() {
+ return [UPDATING].includes(this.knative.status);
+ },
+ saveButtonLabel() {
+ return this.saving ? this.__('Saving') : this.__('Save changes');
+ },
+ knativeInstalled() {
+ return this.knative.installed;
+ },
+ knativeExternalEndpoint() {
+ return this.knative.externalIp || this.knative.externalHostname;
+ },
+ knativeUpdateSuccessful() {
+ return this.knative.updateSuccessful;
+ },
+ knativeHostname: {
+ get() {
+ return this.knative.hostname;
+ },
+ set(hostname) {
+ this.$emit('set', hostname);
+ },
+ },
+ },
+ watch: {
+ knativeUpdateSuccessful(updateSuccessful) {
+ if (updateSuccessful) {
+ this.$toast.show(s__('ClusterIntegration|Knative domain name was updated successfully.'));
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="row">
+ <div
+ v-if="knative.updateFailed"
+ class="bs-callout bs-callout-danger cluster-application-banner col-12 mt-2 mb-2 js-cluster-knative-domain-name-failure-message"
+ >
+ {{ s__('ClusterIntegration|Something went wrong while updating Knative domain name.') }}
+ </div>
+
+ <template>
+ <div
+ :class="{ 'col-md-6': knativeInstalled, 'col-12': !knativeInstalled }"
+ class="form-group col-sm-12 mb-0"
+ >
+ <label for="knative-domainname">
+ <strong>{{ s__('ClusterIntegration|Knative Domain Name:') }}</strong>
+ </label>
+ <input
+ id="knative-domainname"
+ v-model="knativeHostname"
+ type="text"
+ class="form-control js-knative-domainname"
+ />
+ </div>
+ </template>
+ <template v-if="knativeInstalled">
+ <div class="form-group col-sm-12 col-md-6 pl-md-0 mb-0 mt-3 mt-md-0">
+ <label for="knative-endpoint">
+ <strong>{{ s__('ClusterIntegration|Knative Endpoint:') }}</strong>
+ </label>
+ <div v-if="knativeExternalEndpoint" class="input-group">
+ <input
+ id="knative-endpoint"
+ :value="knativeExternalEndpoint"
+ type="text"
+ class="form-control js-knative-endpoint"
+ readonly
+ />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="knativeExternalEndpoint"
+ :title="s__('ClusterIntegration|Copy Knative Endpoint to clipboard')"
+ class="input-group-text js-knative-endpoint-clipboard-btn"
+ />
+ </span>
+ </div>
+ <div v-else class="input-group">
+ <input type="text" class="form-control js-endpoint" readonly />
+ <gl-loading-icon
+ class="position-absolute align-self-center ml-2 js-knative-ip-loading-icon"
+ />
+ </div>
+ </div>
+
+ <p class="form-text text-muted col-12">
+ {{
+ s__(
+ `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>
+ </p>
+
+ <p
+ v-if="!knativeExternalEndpoint"
+ class="settings-message js-no-knative-endpoint-message mt-2 mr-3 mb-0 ml-3"
+ >
+ {{
+ s__(`ClusterIntegration|The endpoint is in
+ the process of being assigned. Please check your Kubernetes
+ cluster or Quotas on Google Kubernetes Engine if it takes a long time.`)
+ }}
+ </p>
+
+ <loading-button
+ class="btn-success js-knative-save-domain-button mt-3 ml-3"
+ :loading="saving"
+ :disabled="saveButtonDisabled"
+ :label="saveButtonLabel"
+ @click="$emit('save', knativeHostname)"
+ />
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters/services/application_state_machine.js b/app/assets/javascripts/clusters/services/application_state_machine.js
index 14b80a116a7..17ea4d77795 100644
--- a/app/assets/javascripts/clusters/services/application_state_machine.js
+++ b/app/assets/javascripts/clusters/services/application_state_machine.js
@@ -123,7 +123,6 @@ const applicationStateMachine = {
target: INSTALLED,
effects: {
updateSuccessful: true,
- updateAcknowledged: false,
},
},
[UPDATE_ERRORED]: {
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 1b4d7e8372c..f64f0ca616f 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -56,8 +56,7 @@ export default class ClusterStore {
title: s__('ClusterIntegration|GitLab Runner'),
version: null,
chartRepo: 'https://gitlab.com/charts/gitlab-runner',
- upgradeAvailable: null,
- updateAcknowledged: true,
+ updateAvailable: null,
updateSuccessful: false,
updateFailed: false,
},
@@ -77,6 +76,8 @@ export default class ClusterStore {
isEditingHostName: false,
externalIp: null,
externalHostname: null,
+ updateSuccessful: false,
+ updateFailed: false,
},
},
};
@@ -134,10 +135,6 @@ export default class ClusterStore {
this.state.applications[appId] = transitionApplicationState(currentAppState, event);
}
- acknowledgeSuccessfulUpdate(appId) {
- this.state.applications[appId].updateAcknowledged = true;
- }
-
updateAppProperty(appId, prop, value) {
this.state.applications[appId][prop] = value;
}
@@ -152,7 +149,7 @@ export default class ClusterStore {
status,
status_reason: statusReason,
version,
- update_available: upgradeAvailable,
+ update_available: updateAvailable,
can_uninstall: uninstallable,
} = serverAppEntry;
const currentApplicationState = this.state.applications[appId] || {};
@@ -189,7 +186,7 @@ export default class ClusterStore {
serverAppEntry.external_hostname || this.state.applications.knative.externalHostname;
} else if (appId === RUNNER) {
this.state.applications.runner.version = version;
- this.state.applications.runner.upgradeAvailable = upgradeAvailable;
+ this.state.applications.runner.updateAvailable = updateAvailable;
}
});
}
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index a0ca44caa07..d0cc4897aeb 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -1,19 +1,21 @@
// ECMAScript polyfills
-import 'core-js/fn/array/fill';
-import 'core-js/fn/array/find';
-import 'core-js/fn/array/find-index';
-import 'core-js/fn/array/from';
-import 'core-js/fn/array/includes';
-import 'core-js/fn/object/assign';
-import 'core-js/fn/object/values';
-import 'core-js/fn/promise';
-import 'core-js/fn/promise/finally';
-import 'core-js/fn/string/code-point-at';
-import 'core-js/fn/string/from-code-point';
-import 'core-js/fn/string/includes';
-import 'core-js/fn/symbol';
-import 'core-js/es6/map';
-import 'core-js/es6/weak-map';
+import 'core-js/es/array/fill';
+import 'core-js/es/array/find';
+import 'core-js/es/array/find-index';
+import 'core-js/es/array/from';
+import 'core-js/es/array/includes';
+import 'core-js/es/object/assign';
+import 'core-js/es/object/values';
+import 'core-js/es/object/entries';
+import 'core-js/es/promise';
+import 'core-js/es/promise/finally';
+import 'core-js/es/string/code-point-at';
+import 'core-js/es/string/from-code-point';
+import 'core-js/es/string/includes';
+import 'core-js/es/symbol';
+import 'core-js/es/map';
+import 'core-js/es/weak-map';
+import 'core-js/modules/web.url';
// Browser polyfills
import 'formdata-polyfill';
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 37a3ceb5341..5bfe158ceda 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -40,7 +40,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = (
},
selectable: true,
filterable: true,
- filterRemote: !!$dropdown.data('refsUrl'),
+ filterRemote: Boolean($dropdown.data('refsUrl')),
fieldName: $dropdown.data('fieldName'),
filterInput: 'input[type="search"]',
renderRow: function(ref) {
diff --git a/app/assets/javascripts/create_item_dropdown.js b/app/assets/javascripts/create_item_dropdown.js
index 916b190f469..fa0f04c7d82 100644
--- a/app/assets/javascripts/create_item_dropdown.js
+++ b/app/assets/javascripts/create_item_dropdown.js
@@ -12,7 +12,7 @@ export default class CreateItemDropdown {
this.fieldName = options.fieldName;
this.onSelect = options.onSelect || (() => {});
this.getDataOption = options.getData;
- this.getDataRemote = !!options.filterRemote;
+ this.getDataRemote = Boolean(options.filterRemote);
this.createNewItemFromValueOption = options.createNewItemFromValue;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index a767379d662..aaa9f8b759a 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -69,7 +69,7 @@ export default {
:link-href="authorUrl"
:img-src="authorAvatar"
:img-alt="authorName"
- :img-size="36"
+ :img-size="40"
class="avatar-cell d-none d-sm-block"
/>
<div class="commit-detail flex-list">
@@ -91,7 +91,7 @@ export default {
<icon :size="12" name="ellipsis_h" />
</button>
- <div class="commiter">
+ <div class="committer">
<a
:href="authorUrl"
:class="authorClass"
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index 2b3d6d1a3fa..d59b1136677 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -8,6 +8,7 @@ import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_d
import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
import InlineDiffView from './inline_diff_view.vue';
import ParallelDiffView from './parallel_diff_view.vue';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import NoteForm from '../../notes/components/note_form.vue';
import ImageDiffOverlay from './image_diff_overlay.vue';
import DiffDiscussions from './diff_discussions.vue';
@@ -26,6 +27,7 @@ export default {
ImageDiffOverlay,
NotDiffableViewer,
NoPreviewViewer,
+ userAvatarLink,
DiffFileDrafts: () => import('ee_component/batch_comments/components/diff_file_drafts.vue'),
},
mixins: [diffLineNoteFormMixin, draftCommentsMixin],
@@ -47,7 +49,7 @@ export default {
}),
...mapGetters('diffs', ['isInlineView', 'isParallelView']),
...mapGetters('diffs', ['getCommentFormForDiffFile']),
- ...mapGetters(['getNoteableData', 'noteableType']),
+ ...mapGetters(['getNoteableData', 'noteableType', 'getUserData']),
diffMode() {
return getDiffMode(this.diffFile);
},
@@ -72,6 +74,9 @@ export default {
diffFileHash() {
return this.diffFile.file_hash;
},
+ author() {
+ return this.getUserData;
+ },
},
methods: {
...mapActions('diffs', ['saveDiffDiscussion', 'closeDiffFileCommentForm']),
@@ -134,6 +139,14 @@ export default {
:can-comment="getNoteableData.current_user.can_create_note"
/>
<div v-if="showNotesContainer" class="note-container">
+ <user-avatar-link
+ v-if="diffFileCommentForm && author"
+ :link-href="author.path"
+ :img-src="author.avatar_url"
+ :img-alt="author.name"
+ :img-size="40"
+ class="d-none d-sm-block new-comment"
+ />
<diff-discussions
v-if="diffFile.discussions.length"
class="diff-file-discussions"
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 392de1c9f23..eb9f1465945 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -240,7 +240,7 @@ export default {
css-class="btn-default btn-transparent btn-clipboard"
/>
- <small v-if="isModeChanged" ref="fileMode">
+ <small v-if="isModeChanged" ref="fileMode" class="mr-1">
{{ diffFile.a_mode }} → {{ diffFile.b_mode }}
</small>
@@ -254,16 +254,17 @@ export default {
<diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" />
<div class="btn-group" role="group">
<template v-if="diffFile.blob && diffFile.blob.readable_text">
- <button
- :disabled="!diffHasDiscussions(diffFile)"
- :class="{ active: hasExpandedDiscussions }"
- :title="s__('MergeRequests|Toggle comments for this file')"
- class="js-btn-vue-toggle-comments btn"
- type="button"
- @click="handleToggleDiscussions"
- >
- <icon name="comment" />
- </button>
+ <span v-gl-tooltip.hover :title="s__('MergeRequests|Toggle comments for this file')">
+ <gl-button
+ :disabled="!diffHasDiscussions(diffFile)"
+ :class="{ active: hasExpandedDiscussions }"
+ class="js-btn-vue-toggle-comments btn"
+ type="button"
+ @click="handleToggleDiscussions"
+ >
+ <icon name="comment" />
+ </gl-button>
+ </span>
<edit-button
v-if="!diffFile.deleted_file"
diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
index 0c0a0faa59d..7cf3d90d468 100644
--- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
+++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
@@ -86,7 +86,6 @@ export default {
:key="note.id"
:img-src="note.author.avatar_url"
:tooltip-text="getTooltipText(note)"
- :size="19"
class="diff-comment-avatar js-diff-comment-avatar"
@click.native="toggleDiscussions"
/>
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 41670b45798..c209b857652 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -4,11 +4,13 @@ import { s__ } from '~/locale';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import noteForm from '../../notes/components/note_form.vue';
import autosave from '../../notes/mixins/autosave';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { DIFF_NOTE_TYPE } from '../constants';
export default {
components: {
noteForm,
+ userAvatarLink,
},
mixins: [autosave, diffLineNoteFormMixin],
props: {
@@ -41,7 +43,16 @@ export default {
diffViewType: state => state.diffs.diffViewType,
}),
...mapGetters('diffs', ['getDiffFileByHash']),
- ...mapGetters(['isLoggedIn', 'noteableType', 'getNoteableData', 'getNotesDataByProp']),
+ ...mapGetters([
+ 'isLoggedIn',
+ 'noteableType',
+ 'getNoteableData',
+ 'getNotesDataByProp',
+ 'getUserData',
+ ]),
+ author() {
+ return this.getUserData;
+ },
formData() {
return {
noteableData: this.noteableData,
@@ -99,6 +110,14 @@ export default {
<template>
<div class="content discussion-form discussion-form-container discussion-notes">
+ <user-avatar-link
+ v-if="author"
+ :link-href="author.path"
+ :img-src="author.avatar_url"
+ :img-alt="author.name"
+ :img-size="40"
+ class="d-none d-sm-block"
+ />
<note-form
ref="noteForm"
:is-editing="true"
diff --git a/app/assets/javascripts/diffs/components/edit_button.vue b/app/assets/javascripts/diffs/components/edit_button.vue
index f0cc5de4b33..dcb79cd5e16 100644
--- a/app/assets/javascripts/diffs/components/edit_button.vue
+++ b/app/assets/javascripts/diffs/components/edit_button.vue
@@ -38,7 +38,7 @@ export default {
<template>
<gl-button
- v-gl-tooltip.bottom
+ v-gl-tooltip.top
:href="editPath"
:title="__('Edit file')"
class="js-edit-blob"
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 35297b7c264..479afc50113 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -211,11 +211,12 @@ export const scrollToLineIfNeededParallel = (_, line) => {
}
};
-export const loadCollapsedDiff = ({ commit, getters }, file) =>
+export const loadCollapsedDiff = ({ commit, getters, state }, file) =>
axios
.get(file.load_collapsed_diff_url, {
params: {
commit_id: getters.commitId,
+ w: state.showWhitespace ? '0' : '1',
},
})
.then(res => {
diff --git a/app/assets/javascripts/emoji/no_emoji_validator.js b/app/assets/javascripts/emoji/no_emoji_validator.js
index 0fd4dd74953..384d62a133a 100644
--- a/app/assets/javascripts/emoji/no_emoji_validator.js
+++ b/app/assets/javascripts/emoji/no_emoji_validator.js
@@ -1,10 +1,11 @@
import { __ } from '~/locale';
import emojiRegex from 'emoji-regex';
+import InputValidator from '../validators/input_validator';
-const invalidInputClass = 'gl-field-error-outline';
-
-export default class NoEmojiValidator {
+export default class NoEmojiValidator extends InputValidator {
constructor(opts = {}) {
+ super();
+
const container = opts.container || '';
this.noEmojiEmelents = document.querySelectorAll(`${container} .js-block-emoji`);
@@ -19,45 +20,14 @@ export default class NoEmojiValidator {
const { value } = this.inputDomElement;
+ this.errorMessage = __('Invalid input, please avoid emojis');
+
this.validatePattern(value);
this.setValidationStateAndMessage();
}
validatePattern(value) {
const pattern = emojiRegex();
- this.hasEmojis = new RegExp(pattern).test(value);
-
- if (this.hasEmojis) {
- this.inputDomElement.setCustomValidity(__('Invalid input, please avoid emojis'));
- } else {
- this.inputDomElement.setCustomValidity('');
- }
- }
-
- setValidationStateAndMessage() {
- if (!this.inputDomElement.checkValidity()) {
- this.setInvalidState();
- } else {
- this.clearFieldValidationState();
- }
- }
-
- clearFieldValidationState() {
- this.inputDomElement.classList.remove(invalidInputClass);
- this.inputErrorMessage.classList.add('hide');
- }
-
- setInvalidState() {
- this.inputDomElement.classList.add(invalidInputClass);
- this.setErrorMessage();
- }
-
- setErrorMessage() {
- if (this.hasEmojis) {
- this.inputErrorMessage.innerHTML = this.inputDomElement.validationMessage;
- } else {
- this.inputErrorMessage.innerHTML = this.inputDomElement.title;
- }
- this.inputErrorMessage.classList.remove('hide');
+ this.invalidInput = new RegExp(pattern).test(value);
}
}
diff --git a/app/assets/javascripts/error_tracking_settings/store/getters.js b/app/assets/javascripts/error_tracking_settings/store/getters.js
index a008b181907..d77e5f15469 100644
--- a/app/assets/javascripts/error_tracking_settings/store/getters.js
+++ b/app/assets/javascripts/error_tracking_settings/store/getters.js
@@ -2,10 +2,10 @@ import _ from 'underscore';
import { __, s__, sprintf } from '~/locale';
import { getDisplayName } from '../utils';
-export const hasProjects = state => !!state.projects && state.projects.length > 0;
+export const hasProjects = state => Boolean(state.projects) && state.projects.length > 0;
export const isProjectInvalid = (state, getters) =>
- !!state.selectedProject &&
+ Boolean(state.selectedProject) &&
getters.hasProjects &&
!state.projects.some(project => _.isMatch(state.selectedProject, project));
diff --git a/app/assets/javascripts/frequent_items/store/actions.js b/app/assets/javascripts/frequent_items/store/actions.js
index 3dd89a82a42..ba62ab67e50 100644
--- a/app/assets/javascripts/frequent_items/store/actions.js
+++ b/app/assets/javascripts/frequent_items/store/actions.js
@@ -51,7 +51,7 @@ export const fetchSearchedItems = ({ state, dispatch }, searchQuery) => {
const params = {
simple: true,
per_page: 20,
- membership: !!gon.current_user_id,
+ membership: Boolean(gon.current_user_id),
};
if (state.namespace === 'projects') {
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index f437954881c..0af9aabd8cf 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import 'at.js';
import _ from 'underscore';
import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache';
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 1c6b18c0e03..bdb50606a53 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -307,8 +307,8 @@ GitLabDropdown = (function() {
// Set Defaults
this.filterInput = this.options.filterInput || this.getElement(FILTER_INPUT);
this.noFilterInput = this.options.noFilterInput || this.getElement(NO_FILTER_INPUT);
- this.highlight = !!this.options.highlight;
- this.icon = !!this.options.icon;
+ this.highlight = Boolean(this.options.highlight);
+ this.icon = Boolean(this.options.icon);
this.filterInputBlur =
this.options.filterInputBlur != null ? this.options.filterInputBlur : true;
// If no input is passed create a default one
@@ -335,6 +335,10 @@ GitLabDropdown = (function() {
_this.fullData = data;
_this.parseData(_this.fullData);
_this.focusTextInput();
+
+ // Update dropdown position since remote data may have changed dropdown size
+ _this.dropdown.find('.dropdown-menu-toggle').dropdown('update');
+
if (
_this.options.filterable &&
_this.filter &&
@@ -561,6 +565,11 @@ GitLabDropdown = (function() {
!$target.data('isLink')
) {
e.stopPropagation();
+
+ // This prevents automatic scrolling to the top
+ if ($target.closest('a').length) {
+ return false;
+ }
}
return true;
@@ -702,6 +711,10 @@ GitLabDropdown = (function() {
}
html = document.createElement('li');
+ if (rowHidden) {
+ html.style.display = 'none';
+ }
+
if (data === 'divider' || data === 'separator') {
html.className = data;
return html;
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 5a6d44ef838..a66555838ba 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -13,7 +13,7 @@ export default class GLForm {
const dataSources = (gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources) || {};
Object.keys(this.enableGFM).forEach(item => {
if (item !== 'emojis') {
- this.enableGFM[item] = !!dataSources[item];
+ this.enableGFM[item] = Boolean(dataSources[item]);
}
});
// Before we start, we should clean up any previous data for this form
diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue
index 7c769ab7fa0..7b4e03be8eb 100644
--- a/app/assets/javascripts/ide/components/activity_bar.vue
+++ b/app/assets/javascripts/ide/components/activity_bar.vue
@@ -78,7 +78,7 @@ export default {
data-container="body"
data-placement="right"
type="button"
- class="ide-sidebar-link js-ide-commit-mode"
+ class="ide-sidebar-link js-ide-commit-mode qa-commit-mode-tab"
@click.prevent="changedActivityView($event, $options.activityBarViews.commit)"
>
<icon name="commit" />
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index 1824a0f6147..685d8a6b245 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -1,23 +1,24 @@
<script>
import _ from 'underscore';
-import { mapActions, mapState, mapGetters, createNamespacedHelpers } from 'vuex';
+import { mapState, mapGetters, createNamespacedHelpers } from 'vuex';
import { sprintf, __ } from '~/locale';
import consts from '../../stores/modules/commit/constants';
import RadioGroup from './radio_group.vue';
+import NewMergeRequestOption from './new_merge_request_option.vue';
-const { mapState: mapCommitState, mapGetters: mapCommitGetters } = createNamespacedHelpers(
+const { mapState: mapCommitState, mapActions: mapCommitActions } = createNamespacedHelpers(
'commit',
);
export default {
components: {
RadioGroup,
+ NewMergeRequestOption,
},
computed: {
...mapState(['currentBranchId', 'changedFiles', 'stagedFiles']),
- ...mapCommitState(['commitAction', 'shouldCreateMR', 'shouldDisableNewMrOption']),
- ...mapGetters(['currentProject', 'currentBranch', 'currentMergeRequest']),
- ...mapCommitGetters(['shouldDisableNewMrOption']),
+ ...mapCommitState(['commitAction']),
+ ...mapGetters(['currentBranch']),
commitToCurrentBranchText() {
return sprintf(
__('Commit to %{branchName} branch'),
@@ -25,12 +26,12 @@ export default {
false,
);
},
- disableMergeRequestRadio() {
+ containsStagedChanges() {
return this.changedFiles.length > 0 && this.stagedFiles.length > 0;
},
},
watch: {
- disableMergeRequestRadio() {
+ containsStagedChanges() {
this.updateSelectedCommitAction();
},
},
@@ -38,11 +39,11 @@ export default {
this.updateSelectedCommitAction();
},
methods: {
- ...mapActions('commit', ['updateCommitAction', 'toggleShouldCreateMR']),
+ ...mapCommitActions(['updateCommitAction']),
updateSelectedCommitAction() {
if (this.currentBranch && !this.currentBranch.can_push) {
this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH);
- } else if (this.disableMergeRequestRadio) {
+ } else if (this.containsStagedChanges) {
this.updateCommitAction(consts.COMMIT_TO_CURRENT_BRANCH);
}
},
@@ -56,7 +57,7 @@ export default {
</script>
<template>
- <div class="append-bottom-15 ide-commit-radios">
+ <div class="append-bottom-15 ide-commit-options">
<radio-group
:value="$options.commitToCurrentBranch"
:disabled="currentBranch && !currentBranch.can_push"
@@ -69,17 +70,6 @@ export default {
:label="__('Create a new branch')"
:show-input="true"
/>
- <hr class="my-2" />
- <label class="mb-0">
- <input
- :checked="shouldCreateMR"
- :disabled="shouldDisableNewMrOption"
- type="checkbox"
- @change="toggleShouldCreateMR"
- />
- <span class="prepend-left-10" :class="{ 'text-secondary': shouldDisableNewMrOption }">
- {{ __('Start a new merge request') }}
- </span>
- </label>
+ <new-merge-request-option />
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
new file mode 100644
index 00000000000..b2e7b15089c
--- /dev/null
+++ b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
@@ -0,0 +1,43 @@
+<script>
+import { mapGetters, createNamespacedHelpers } from 'vuex';
+
+const {
+ mapState: mapCommitState,
+ mapGetters: mapCommitGetters,
+ mapActions: mapCommitActions,
+} = createNamespacedHelpers('commit');
+
+export default {
+ computed: {
+ ...mapCommitState(['shouldCreateMR']),
+ ...mapCommitGetters(['isCommittingToCurrentBranch', 'isCommittingToDefaultBranch']),
+ ...mapGetters(['hasMergeRequest', 'isOnDefaultBranch']),
+ currentBranchHasMr() {
+ return this.hasMergeRequest && this.isCommittingToCurrentBranch;
+ },
+ showNewMrOption() {
+ return (
+ this.isCommittingToDefaultBranch || !this.currentBranchHasMr || this.isCommittingToNewBranch
+ );
+ },
+ },
+ mounted() {
+ this.setShouldCreateMR();
+ },
+ methods: {
+ ...mapCommitActions(['toggleShouldCreateMR', 'setShouldCreateMR']),
+ },
+};
+</script>
+
+<template>
+ <div v-if="showNewMrOption">
+ <hr class="my-2" />
+ <label class="mb-0">
+ <input :checked="shouldCreateMR" type="checkbox" @change="toggleShouldCreateMR" />
+ <span class="prepend-left-10">
+ {{ __('Start a new merge request') }}
+ </span>
+ </label>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 9894ebb0624..e41b1530226 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -1,6 +1,7 @@
<script>
import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex';
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
import NewModal from './new_dropdown/modal.vue';
@@ -22,6 +23,8 @@ export default {
FindFile,
ErrorMessage,
CommitEditorHeader,
+ GlButton,
+ GlLoadingIcon,
},
props: {
rightPaneComponent: {
@@ -47,13 +50,15 @@ export default {
'someUncommittedChanges',
'isCommitModeActive',
'allBlobs',
+ 'emptyRepo',
+ 'currentTree',
]),
},
mounted() {
window.onbeforeunload = e => this.onBeforeUnload(e);
},
methods: {
- ...mapActions(['toggleFileFinder']),
+ ...mapActions(['toggleFileFinder', 'openNewEntryModal']),
onBeforeUnload(e = {}) {
const returnValue = __('Are you sure you want to lose unsaved changes?');
@@ -98,17 +103,40 @@ export default {
<repo-editor :file="activeFile" class="multi-file-edit-pane-content" />
</template>
<template v-else>
- <div v-once class="ide-empty-state">
+ <div class="ide-empty-state">
<div class="row js-empty-state">
<div class="col-12">
<div class="svg-content svg-250"><img :src="emptyStateSvgPath" /></div>
</div>
<div class="col-12">
<div class="text-content text-center">
- <h4>Welcome to the GitLab IDE</h4>
- <p>
- Select a file from the left sidebar to begin editing. Afterwards, you'll be able
- to commit your changes.
+ <h4>
+ {{ __('Make and review changes in the browser with the Web IDE') }}
+ </h4>
+ <template v-if="emptyRepo">
+ <p>
+ {{
+ __(
+ "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes.",
+ )
+ }}
+ </p>
+ <gl-button
+ variant="success"
+ :title="__('New file')"
+ :aria-label="__('New file')"
+ @click="openNewEntryModal({ type: 'blob' })"
+ >
+ {{ __('New file') }}
+ </gl-button>
+ </template>
+ <gl-loading-icon v-else-if="!currentTree || currentTree.loading" size="md" />
+ <p v-else>
+ {{
+ __(
+ "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes.",
+ )
+ }}
</p>
</div>
</div>
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index 81374f26645..95782b2c88a 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -54,14 +54,17 @@ export default {
<slot name="header"></slot>
</header>
<div class="ide-tree-body h-100">
- <file-row
- v-for="file in currentTree.tree"
- :key="file.key"
- :file="file"
- :level="0"
- :extra-component="$options.FileRowExtra"
- @toggleTreeOpen="toggleTreeOpen"
- />
+ <template v-if="currentTree.tree.length">
+ <file-row
+ v-for="file in currentTree.tree"
+ :key="file.key"
+ :file="file"
+ :level="0"
+ :extra-component="$options.FileRowExtra"
+ @toggleTreeOpen="toggleTreeOpen"
+ />
+ </template>
+ <div v-else class="file-row">{{ __('No files') }}</div>
</div>
</template>
</div>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index 412b07553dc..f67666f1fbf 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -134,6 +134,7 @@ export default {
<template>
<gl-modal
id="ide-new-entry"
+ class="qa-new-file-modal"
:header-title-text="modalTitle"
:footer-primary-button-text="buttonLabel"
footer-primary-button-variant="success"
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index c98dda00817..6999746f115 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -105,7 +105,7 @@ export default {
.then(() => {
this.initManager('#ide-preview', this.sandboxOpts, {
fileResolver: {
- isFile: p => Promise.resolve(!!this.entries[createPathWithExt(p)]),
+ isFile: p => Promise.resolve(Boolean(this.entries[createPathWithExt(p)])),
readFile: p => this.loadFileContent(createPathWithExt(p)).then(content => content),
},
});
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 99f1d4a573d..5201c33b1b4 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -30,7 +30,7 @@ export default {
...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommittedChanges', 'activeFile']),
...mapGetters('commit', ['discardDraftButtonDisabled']),
showStageUnstageArea() {
- return !!(this.someUncommittedChanges || this.lastCommitMsg || !this.unusedSeal);
+ return Boolean(this.someUncommittedChanges || this.lastCommitMsg || !this.unusedSeal);
},
activeFileKey() {
return this.activeFile ? this.activeFile.key : null;
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index e15b2a6f76b..b0c4969c5e4 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -125,6 +125,7 @@ export default {
'setFileEOL',
'updateViewer',
'removePendingTab',
+ 'triggerFilesChange',
]),
initEditor() {
if (this.shouldHideEditor) return;
@@ -256,6 +257,7 @@ export default {
'is-added': file.tempFile,
}"
class="multi-file-editor-holder"
+ @focusout="triggerFilesChange"
></div>
<content-viewer
v-if="showContentViewer"
diff --git a/app/assets/javascripts/ide/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js
index e35595ab1fd..dac2a8e8b51 100644
--- a/app/assets/javascripts/ide/lib/editor_options.js
+++ b/app/assets/javascripts/ide/lib/editor_options.js
@@ -11,7 +11,7 @@ export const defaultEditorOptions = {
export default [
{
- readOnly: model => !!model.file.file_lock,
+ readOnly: model => Boolean(model.file.file_lock),
quickSuggestions: model => !(model.language === 'markdown'),
},
];
diff --git a/app/assets/javascripts/ide/lib/keymap.json b/app/assets/javascripts/ide/lib/keymap.json
index 131abfebbed..2db87c07dde 100644
--- a/app/assets/javascripts/ide/lib/keymap.json
+++ b/app/assets/javascripts/ide/lib/keymap.json
@@ -7,5 +7,13 @@
"name": "toggleFileFinder",
"params": true
}
+ },
+ {
+ "id": "save-files",
+ "label": "Save files",
+ "bindings": ["CtrlCmd+KEY_S"],
+ "action": {
+ "name": "triggerFilesChange"
+ }
}
]
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index fd678e6e10c..5429b834708 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -1,12 +1,15 @@
import $ from 'jquery';
import Vue from 'vue';
+import { __, sprintf } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import flash from '~/flash';
+import _ from 'underscore';
import * as types from './mutation_types';
import { decorateFiles } from '../lib/files';
import { stageKeys } from '../constants';
+import service from '../services';
-export const redirectToUrl = (_, url) => visitUrl(url);
+export const redirectToUrl = (self, url) => visitUrl(url);
export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
@@ -96,6 +99,7 @@ export const createTempEntry = (
commit(types.TOGGLE_FILE_OPEN, file.path);
commit(types.ADD_FILE_TO_CHANGED, file.path);
dispatch('setFileActive', file.path);
+ dispatch('triggerFilesChange');
}
if (parentPath && !state.entries[parentPath].opened) {
@@ -207,6 +211,8 @@ export const deleteEntry = ({ commit, dispatch, state }, path) => {
if (entry.parentPath && state.entries[entry.parentPath].tree.length === 0) {
dispatch('deleteEntry', entry.parentPath);
}
+
+ dispatch('triggerFilesChange');
};
export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
@@ -237,8 +243,57 @@ export const renameEntry = (
if (!entryPath && !entry.tempFile) {
dispatch('deleteEntry', path);
}
+
+ dispatch('triggerFilesChange');
};
+export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) =>
+ new Promise((resolve, reject) => {
+ const currentProject = state.projects[projectId];
+ if (!currentProject || !currentProject.branches[branchId] || force) {
+ service
+ .getBranchData(projectId, branchId)
+ .then(({ data }) => {
+ const { id } = data.commit;
+ commit(types.SET_BRANCH, {
+ projectPath: projectId,
+ branchName: branchId,
+ branch: data,
+ });
+ commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
+ resolve(data);
+ })
+ .catch(e => {
+ if (e.response.status === 404) {
+ reject(e);
+ } else {
+ flash(
+ __('Error loading branch data. Please try again.'),
+ 'alert',
+ document,
+ null,
+ false,
+ true,
+ );
+
+ reject(
+ new Error(
+ sprintf(
+ __('Branch not loaded - %{branchId}'),
+ {
+ branchId: `<strong>${_.escape(projectId)}/${_.escape(branchId)}</strong>`,
+ },
+ false,
+ ),
+ ),
+ );
+ }
+ });
+ } else {
+ resolve(currentProject.branches[branchId]);
+ }
+ });
+
export * from './actions/tree';
export * from './actions/file';
export * from './actions/project';
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index e7e8ac6d80b..dc40a1fa6a2 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -265,3 +265,8 @@ export const removePendingTab = ({ commit }, file) => {
eventHub.$emit(`editor.update.model.dispose.${file.key}`);
};
+
+export const triggerFilesChange = () => {
+ // Used in EE for file mirroring
+ eventHub.$emit('ide.files.change');
+};
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index 4b10d148ebf..dd8f17e4f3a 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -35,48 +35,6 @@ export const getProjectData = ({ commit, state }, { namespace, projectId, force
}
});
-export const getBranchData = (
- { commit, dispatch, state },
- { projectId, branchId, force = false } = {},
-) =>
- new Promise((resolve, reject) => {
- if (
- typeof state.projects[`${projectId}`] === 'undefined' ||
- !state.projects[`${projectId}`].branches[branchId] ||
- force
- ) {
- service
- .getBranchData(`${projectId}`, branchId)
- .then(({ data }) => {
- const { id } = data.commit;
- commit(types.SET_BRANCH, {
- projectPath: `${projectId}`,
- branchName: branchId,
- branch: data,
- });
- commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
- resolve(data);
- })
- .catch(e => {
- if (e.response.status === 404) {
- dispatch('showBranchNotFoundError', branchId);
- } else {
- flash(
- __('Error loading branch data. Please try again.'),
- 'alert',
- document,
- null,
- false,
- true,
- );
- }
- reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
- });
- } else {
- resolve(state.projects[`${projectId}`].branches[branchId]);
- }
- });
-
export const refreshLastCommitData = ({ commit }, { projectId, branchId } = {}) =>
service
.getBranchData(projectId, branchId)
@@ -125,40 +83,66 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
});
};
-export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath }) => {
- dispatch('setCurrentBranchId', branchId);
-
- dispatch('getBranchData', {
- projectId,
- branchId,
+export const showEmptyState = ({ commit, state }, { projectId, branchId }) => {
+ const treePath = `${projectId}/${branchId}`;
+ commit(types.CREATE_TREE, { treePath });
+ commit(types.TOGGLE_LOADING, {
+ entry: state.trees[treePath],
+ forceValue: false,
});
+};
- return dispatch('getFiles', {
+export const openBranch = ({ dispatch, state, getters }, { projectId, branchId, basePath }) => {
+ dispatch('setCurrentBranchId', branchId);
+
+ if (getters.emptyRepo) {
+ return dispatch('showEmptyState', { projectId, branchId });
+ }
+ return dispatch('getBranchData', {
projectId,
branchId,
})
.then(() => {
- if (basePath) {
- const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
- const treeEntryKey = Object.keys(state.entries).find(
- key => key === path && !state.entries[key].pending,
- );
- const treeEntry = state.entries[treeEntryKey];
-
- if (treeEntry) {
- dispatch('handleTreeEntryAction', treeEntry);
- } else {
- dispatch('createTempEntry', {
- name: path,
- type: 'blob',
- });
- }
- }
- })
- .then(() => {
dispatch('getMergeRequestsForBranch', {
projectId,
branchId,
});
+ dispatch('getFiles', {
+ projectId,
+ branchId,
+ })
+ .then(() => {
+ if (basePath) {
+ const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
+ const treeEntryKey = Object.keys(state.entries).find(
+ key => key === path && !state.entries[key].pending,
+ );
+ const treeEntry = state.entries[treeEntryKey];
+
+ if (treeEntry) {
+ dispatch('handleTreeEntryAction', treeEntry);
+ } else {
+ dispatch('createTempEntry', {
+ name: path,
+ type: 'blob',
+ });
+ }
+ }
+ })
+ .catch(
+ () =>
+ new Error(
+ sprintf(
+ __('An error occurred whilst getting files for - %{branchId}'),
+ {
+ branchId: `<strong>${_.escape(projectId)}/${_.escape(branchId)}</strong>`,
+ },
+ false,
+ ),
+ ),
+ );
+ })
+ .catch(() => {
+ dispatch('showBranchNotFoundError', branchId);
});
};
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
index 3d83e4a9ba5..75511574d3e 100644
--- a/app/assets/javascripts/ide/stores/actions/tree.js
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -74,17 +74,13 @@ export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } =
resolve();
})
.catch(e => {
- if (e.response.status === 404) {
- dispatch('showBranchNotFoundError', branchId);
- } else {
- dispatch('setErrorMessage', {
- text: __('An error occurred whilst loading all the files.'),
- action: payload =>
- dispatch('getFiles', payload).then(() => dispatch('setErrorMessage', null)),
- actionText: __('Please try again'),
- actionPayload: { projectId, branchId },
- });
- }
+ dispatch('setErrorMessage', {
+ text: __('An error occurred whilst loading all the files.'),
+ action: payload =>
+ dispatch('getFiles', payload).then(() => dispatch('setErrorMessage', null)),
+ actionText: __('Please try again'),
+ actionPayload: { projectId, branchId },
+ });
reject(e);
});
} else {
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
index 490658a4543..406903129db 100644
--- a/app/assets/javascripts/ide/stores/getters.js
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -36,12 +36,16 @@ export const currentMergeRequest = state => {
export const currentProject = state => state.projects[state.currentProjectId];
+export const emptyRepo = state =>
+ state.projects[state.currentProjectId] && state.projects[state.currentProjectId].empty_repo;
+
export const currentTree = state =>
state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
-export const hasChanges = state => !!state.changedFiles.length || !!state.stagedFiles.length;
+export const hasChanges = state =>
+ Boolean(state.changedFiles.length) || Boolean(state.stagedFiles.length);
-export const hasMergeRequest = state => !!state.currentMergeRequestId;
+export const hasMergeRequest = state => Boolean(state.currentMergeRequestId);
export const allBlobs = state =>
Object.keys(state.entries)
@@ -67,7 +71,7 @@ export const isCommitModeActive = state => state.currentActivityView === activit
export const isReviewModeActive = state => state.currentActivityView === activityBarViews.review;
export const someUncommittedChanges = state =>
- !!(state.changedFiles.length || state.stagedFiles.length);
+ Boolean(state.changedFiles.length || state.stagedFiles.length);
export const getChangesInFolder = state => path => {
const changedFilesCount = state.changedFiles.filter(f => filePathMatches(f.path, path)).length;
@@ -93,7 +97,12 @@ export const lastCommit = (state, getters) => {
export const currentBranch = (state, getters) =>
getters.currentProject && getters.currentProject.branches[state.currentBranchId];
+export const branchName = (_state, getters) => getters.currentBranch && getters.currentBranch.name;
+
export const packageJson = state => state.entries[packageJsonPath];
+export const isOnDefaultBranch = (_state, getters) =>
+ getters.currentProject && getters.currentProject.default_branch === getters.branchName;
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index c2760eb1554..51062f092ad 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -18,15 +18,34 @@ export const discardDraft = ({ commit }) => {
commit(types.UPDATE_COMMIT_MESSAGE, '');
};
-export const updateCommitAction = ({ commit, rootGetters }, commitAction) => {
+export const updateCommitAction = ({ commit, dispatch }, commitAction) => {
commit(types.UPDATE_COMMIT_ACTION, {
commitAction,
- currentMergeRequest: rootGetters.currentMergeRequest,
});
+ dispatch('setShouldCreateMR');
};
export const toggleShouldCreateMR = ({ commit }) => {
commit(types.TOGGLE_SHOULD_CREATE_MR);
+ commit(types.INTERACT_WITH_NEW_MR);
+};
+
+export const setShouldCreateMR = ({
+ commit,
+ getters,
+ rootGetters,
+ state: { interactedWithNewMR },
+}) => {
+ const committingToExistingMR =
+ getters.isCommittingToCurrentBranch &&
+ rootGetters.hasMergeRequest &&
+ !rootGetters.isOnDefaultBranch;
+
+ if ((getters.isCommittingToDefaultBranch && !interactedWithNewMR) || committingToExistingMR) {
+ commit(types.TOGGLE_SHOULD_CREATE_MR, false);
+ } else if (!interactedWithNewMR) {
+ commit(types.TOGGLE_SHOULD_CREATE_MR, true);
+ }
};
export const updateBranchName = ({ commit }, branchName) => {
@@ -102,7 +121,7 @@ export const updateFilesAfterCommit = ({ commit, dispatch, rootState, rootGetter
eventHub.$emit(`editor.update.model.content.${file.key}`, {
content: file.content,
- changed: !!changedFile,
+ changed: Boolean(changedFile),
});
});
};
@@ -135,6 +154,17 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
return null;
}
+ if (!data.parent_ids.length) {
+ commit(
+ rootTypes.TOGGLE_EMPTY_STATE,
+ {
+ projectPath: rootState.currentProjectId,
+ value: false,
+ },
+ { root: true },
+ );
+ }
+
dispatch('setLastCommitMessage', data);
dispatch('updateCommitMessage', '');
return dispatch('updateFilesAfterCommit', {
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
index 6aa5d22a4ea..64779e9e4df 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -48,8 +48,11 @@ export const preBuiltCommitMessage = (state, _, rootState) => {
export const isCreatingNewBranch = state => state.commitAction === consts.COMMIT_TO_NEW_BRANCH;
-export const shouldDisableNewMrOption = (state, _getters, _rootState, rootGetters) =>
- rootGetters.currentMergeRequest && state.commitAction === consts.COMMIT_TO_CURRENT_BRANCH;
+export const isCommittingToCurrentBranch = state =>
+ state.commitAction === consts.COMMIT_TO_CURRENT_BRANCH;
+
+export const isCommittingToDefaultBranch = (_state, getters, _rootState, rootGetters) =>
+ getters.isCommittingToCurrentBranch && rootGetters.isOnDefaultBranch;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
index 7ad8f3570b7..b81918156b0 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
@@ -3,3 +3,4 @@ export const UPDATE_COMMIT_ACTION = 'UPDATE_COMMIT_ACTION';
export const UPDATE_NEW_BRANCH_NAME = 'UPDATE_NEW_BRANCH_NAME';
export const UPDATE_LOADING = 'UPDATE_LOADING';
export const TOGGLE_SHOULD_CREATE_MR = 'TOGGLE_SHOULD_CREATE_MR';
+export const INTERACT_WITH_NEW_MR = 'INTERACT_WITH_NEW_MR';
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutations.js b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
index be0f894c059..14957d283bb 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
@@ -1,5 +1,4 @@
import * as types from './mutation_types';
-import consts from './constants';
export default {
[types.UPDATE_COMMIT_MESSAGE](state, commitMessage) {
@@ -7,14 +6,8 @@ export default {
commitMessage,
});
},
- [types.UPDATE_COMMIT_ACTION](state, { commitAction, currentMergeRequest }) {
- Object.assign(state, {
- commitAction,
- shouldCreateMR:
- commitAction === consts.COMMIT_TO_CURRENT_BRANCH && currentMergeRequest
- ? false
- : state.shouldCreateMR,
- });
+ [types.UPDATE_COMMIT_ACTION](state, { commitAction }) {
+ Object.assign(state, { commitAction });
},
[types.UPDATE_NEW_BRANCH_NAME](state, newBranchName) {
Object.assign(state, {
@@ -26,9 +19,12 @@ export default {
submitCommitLoading,
});
},
- [types.TOGGLE_SHOULD_CREATE_MR](state) {
+ [types.TOGGLE_SHOULD_CREATE_MR](state, shouldCreateMR) {
Object.assign(state, {
- shouldCreateMR: !state.shouldCreateMR,
+ shouldCreateMR: shouldCreateMR === undefined ? !state.shouldCreateMR : shouldCreateMR,
});
},
+ [types.INTERACT_WITH_NEW_MR](state) {
+ Object.assign(state, { interactedWithNewMR: true });
+ },
};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/state.js b/app/assets/javascripts/ide/stores/modules/commit/state.js
index 5c0e6a41ca1..53647a7e3e3 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/state.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/state.js
@@ -4,4 +4,5 @@ export default () => ({
newBranchName: '',
submitCommitLoading: false,
shouldCreateMR: false,
+ interactedWithNewMR: false,
});
diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js
index ef7cd4ff8e8..1d127d915d7 100644
--- a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js
@@ -1,6 +1,6 @@
import { states } from './constants';
-export const hasLatestPipeline = state => !state.isLoadingPipeline && !!state.latestPipeline;
+export const hasLatestPipeline = state => !state.isLoadingPipeline && Boolean(state.latestPipeline);
export const pipelineFailed = state =>
state.latestPipeline && state.latestPipeline.details.status.text === states.failed;
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index a5f8098dc17..86ab76136df 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -12,6 +12,7 @@ export const SET_LINKS = 'SET_LINKS';
export const SET_PROJECT = 'SET_PROJECT';
export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN';
+export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
// Merge Request Mutation Types
export const SET_MERGE_REQUEST = 'SET_MERGE_REQUEST';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 344b189decf..ae42b87c9a7 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -142,7 +142,7 @@ export default {
Object.assign(state.entries[file.path], {
raw: file.content,
- changed: !!changedFile,
+ changed: Boolean(changedFile),
staged: false,
prevPath: '',
moved: false,
diff --git a/app/assets/javascripts/ide/stores/mutations/branch.js b/app/assets/javascripts/ide/stores/mutations/branch.js
index e09f88878f4..6afd8de2aa4 100644
--- a/app/assets/javascripts/ide/stores/mutations/branch.js
+++ b/app/assets/javascripts/ide/stores/mutations/branch.js
@@ -19,6 +19,12 @@ export default {
});
},
[types.SET_BRANCH_WORKING_REFERENCE](state, { projectId, branchId, reference }) {
+ if (!state.projects[projectId].branches[branchId]) {
+ Object.assign(state.projects[projectId].branches, {
+ [branchId]: {},
+ });
+ }
+
Object.assign(state.projects[projectId].branches[branchId], {
workingReference: reference,
});
diff --git a/app/assets/javascripts/ide/stores/mutations/project.js b/app/assets/javascripts/ide/stores/mutations/project.js
index 284b39a2c72..9230f3839c1 100644
--- a/app/assets/javascripts/ide/stores/mutations/project.js
+++ b/app/assets/javascripts/ide/stores/mutations/project.js
@@ -21,4 +21,9 @@ export default {
}),
});
},
+ [types.TOGGLE_EMPTY_STATE](state, { projectPath, value }) {
+ Object.assign(state.projects[projectPath], {
+ empty_repo: value,
+ });
+ },
};
diff --git a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
index 05000c73052..7051a968dac 100644
--- a/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
+++ b/app/assets/javascripts/image_diff/helpers/comment_indicator_helper.js
@@ -14,7 +14,7 @@ export function addCommentIndicator(containerEl, { x, y }) {
export function removeCommentIndicator(imageFrameEl) {
const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator');
const imageEl = imageFrameEl.querySelector('img');
- const willRemove = !!commentIndicatorEl;
+ const willRemove = Boolean(commentIndicatorEl);
let meta = {};
if (willRemove) {
diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js
index 3587f073a00..26c1b0ec7be 100644
--- a/app/assets/javascripts/image_diff/image_diff.js
+++ b/app/assets/javascripts/image_diff/image_diff.js
@@ -6,8 +6,8 @@ import { isImageLoaded } from '../lib/utils/image_utility';
export default class ImageDiff {
constructor(el, options) {
this.el = el;
- this.canCreateNote = !!(options && options.canCreateNote);
- this.renderCommentBadge = !!(options && options.renderCommentBadge);
+ this.canCreateNote = Boolean(options && options.canCreateNote);
+ this.renderCommentBadge = Boolean(options && options.renderCommentBadge);
this.$noteContainer = $('.note-container', this.el);
this.imageBadges = [];
}
diff --git a/app/assets/javascripts/image_diff/view_types.js b/app/assets/javascripts/image_diff/view_types.js
index ab0a595571f..1a5123de220 100644
--- a/app/assets/javascripts/image_diff/view_types.js
+++ b/app/assets/javascripts/image_diff/view_types.js
@@ -5,5 +5,5 @@ export const viewTypes = {
};
export function isValidViewType(validate) {
- return !!Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate);
+ return Boolean(Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate));
}
diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js
index f51c7a2f990..16f88cddce3 100644
--- a/app/assets/javascripts/issuable_index.js
+++ b/app/assets/javascripts/issuable_index.js
@@ -12,7 +12,7 @@ export default class IssuableIndex {
}
initBulkUpdate(pagePrefix) {
const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
- const alreadyInitialized = !!this.bulkUpdateSidebar;
+ const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
if (userCanBulkUpdate && !alreadyInitialized) {
IssuableBulkUpdateActions.init({
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index ab0b4231255..e88ca4747c5 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -156,7 +156,7 @@ export default {
return this.store.formState;
},
hasUpdated() {
- return !!this.state.updatedAt;
+ return Boolean(this.state.updatedAt);
},
issueChanged() {
const {
diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue
index d2f33dc31a7..1e1dce5f4fc 100644
--- a/app/assets/javascripts/issue_show/components/title.vue
+++ b/app/assets/javascripts/issue_show/components/title.vue
@@ -71,7 +71,7 @@ export default {
'issue-realtime-pre-pulse': preAnimation,
'issue-realtime-trigger-pulse': pulseAnimation,
}"
- class="title"
+ class="title qa-title"
dir="auto"
v-html="titleHtml"
></h2>
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 7594edfac27..79fb67d38cd 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -86,6 +86,7 @@ export default {
'isScrollTopDisabled',
'isScrolledToBottomBeforeReceivingTrace',
'hasError',
+ 'selectedStage',
]),
...mapGetters([
'headerTime',
@@ -121,7 +122,13 @@ export default {
// fetch the stages for the dropdown on the sidebar
job(newVal, oldVal) {
if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) {
- this.fetchStages();
+ const stages = this.job.pipeline.details.stages || [];
+
+ const defaultStage = stages.find(stage => stage && stage.name === this.selectedStage);
+
+ if (defaultStage) {
+ this.fetchJobsForStage(defaultStage);
+ }
}
if (newVal.archived) {
@@ -160,7 +167,7 @@ export default {
'setJobEndpoint',
'setTraceOptions',
'fetchJob',
- 'fetchStages',
+ 'fetchJobsForStage',
'hideSidebar',
'showSidebar',
'toggleSidebar',
@@ -269,7 +276,6 @@ export default {
:class="{ 'sticky-top border-bottom-0': hasTrace }"
>
<icon name="lock" class="align-text-bottom" />
-
{{ __('This job is archived. Only the complete pipeline can be retried.') }}
</div>
<!-- job log -->
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 1691ac62100..24276c06486 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -34,7 +34,7 @@ export default {
},
},
computed: {
- ...mapState(['job', 'stages', 'jobs', 'selectedStage', 'isLoadingStages']),
+ ...mapState(['job', 'stages', 'jobs', 'selectedStage']),
coverage() {
return `${this.job.coverage}%`;
},
@@ -208,7 +208,6 @@ export default {
/>
<stages-dropdown
- v-if="!isLoadingStages"
:stages="stages"
:pipeline="job.pipeline"
:selected-stage="selectedStage"
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index 6e92b599b0a..cb073a9b04d 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -2,6 +2,7 @@
import _ from 'underscore';
import { GlLink } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue';
import Icon from '~/vue_shared/components/icon.vue';
export default {
@@ -9,6 +10,7 @@ export default {
CiIcon,
Icon,
GlLink,
+ PipelineLink,
},
props: {
pipeline: {
@@ -48,9 +50,12 @@ export default {
<ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
<span class="font-weight-bold">{{ s__('Job|Pipeline') }}</span>
- <gl-link :href="pipeline.path" class="js-pipeline-path link-commit qa-pipeline-path"
- >#{{ pipeline.id }}</gl-link
- >
+ <pipeline-link
+ :href="pipeline.path"
+ :pipeline-id="pipeline.id"
+ :pipeline-iid="pipeline.iid"
+ class="js-pipeline-path link-commit qa-pipeline-path"
+ />
<template v-if="hasRef">
{{ s__('Job|for') }}
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 8045f6dc3ff..12d67a43599 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -179,37 +179,13 @@ export const receiveTraceError = ({ commit }) => {
};
/**
- * Stages dropdown on sidebar
- */
-export const requestStages = ({ commit }) => commit(types.REQUEST_STAGES);
-export const fetchStages = ({ state, dispatch }) => {
- dispatch('requestStages');
-
- axios
- .get(`${state.job.pipeline.path}.json`)
- .then(({ data }) => {
- // Set selected stage
- dispatch('receiveStagesSuccess', data.details.stages);
- const selectedStage = data.details.stages.find(stage => stage.name === state.selectedStage);
- dispatch('fetchJobsForStage', selectedStage);
- })
- .catch(() => dispatch('receiveStagesError'));
-};
-export const receiveStagesSuccess = ({ commit }, data) =>
- commit(types.RECEIVE_STAGES_SUCCESS, data);
-export const receiveStagesError = ({ commit }) => {
- commit(types.RECEIVE_STAGES_ERROR);
- flash(__('An error occurred while fetching stages.'));
-};
-
-/**
* Jobs list on sidebar - depend on stages dropdown
*/
export const requestJobsForStage = ({ commit }, stage) =>
commit(types.REQUEST_JOBS_FOR_STAGE, stage);
// On stage click, set selected stage + fetch job
-export const fetchJobsForStage = ({ dispatch }, stage) => {
+export const fetchJobsForStage = ({ dispatch }, stage = {}) => {
dispatch('requestJobsForStage', stage);
axios
diff --git a/app/assets/javascripts/jobs/store/mutation_types.js b/app/assets/javascripts/jobs/store/mutation_types.js
index fd098f13e90..39146b2eefd 100644
--- a/app/assets/javascripts/jobs/store/mutation_types.js
+++ b/app/assets/javascripts/jobs/store/mutation_types.js
@@ -24,10 +24,6 @@ export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE';
export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS';
export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR';
-export const REQUEST_STAGES = 'REQUEST_STAGES';
-export const RECEIVE_STAGES_SUCCESS = 'RECEIVE_STAGES_SUCCESS';
-export const RECEIVE_STAGES_ERROR = 'RECEIVE_STAGES_ERROR';
-
export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
export const REQUEST_JOBS_FOR_STAGE = 'REQUEST_JOBS_FOR_STAGE';
export const RECEIVE_JOBS_FOR_STAGE_SUCCESS = 'RECEIVE_JOBS_FOR_STAGE_SUCCESS';
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index cd440d21c1f..ad08f27b147 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -65,6 +65,11 @@ export default {
state.isLoading = false;
state.job = job;
+ state.stages =
+ job.pipeline && job.pipeline.details && job.pipeline.details.stages
+ ? job.pipeline.details.stages
+ : [];
+
/**
* We only update it on the first request
* The dropdown can be changed by the user
@@ -101,19 +106,7 @@ export default {
state.isScrolledToBottomBeforeReceivingTrace = toggle;
},
- [types.REQUEST_STAGES](state) {
- state.isLoadingStages = true;
- },
- [types.RECEIVE_STAGES_SUCCESS](state, stages) {
- state.isLoadingStages = false;
- state.stages = stages;
- },
- [types.RECEIVE_STAGES_ERROR](state) {
- state.isLoadingStages = false;
- state.stages = [];
- },
-
- [types.REQUEST_JOBS_FOR_STAGE](state, stage) {
+ [types.REQUEST_JOBS_FOR_STAGE](state, stage = {}) {
state.isLoadingJobs = true;
state.selectedStage = stage.name;
},
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
index 04825187c99..6019214e62c 100644
--- a/app/assets/javascripts/jobs/store/state.js
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -25,7 +25,6 @@ export default () => ({
traceState: null,
// sidebar dropdown & list of jobs
- isLoadingStages: false,
isLoadingJobs: false,
selectedStage: '',
stages: [],
diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js
index c6dd21cd2d4..7064731a5ea 100644
--- a/app/assets/javascripts/label_manager.js
+++ b/app/assets/javascripts/label_manager.js
@@ -53,7 +53,7 @@ export default class LabelManager {
toggleEmptyState($label, $btn, action) {
this.emptyState.classList.toggle(
'hidden',
- !!this.prioritizedLabels[0].querySelector(':scope > li'),
+ Boolean(this.prioritizedLabels[0].querySelector(':scope > li')),
);
}
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index 498c2348ca2..5857f9e22ae 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -1,24 +1,32 @@
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createUploadLink } from 'apollo-upload-client';
+import { ApolloLink } from 'apollo-link';
+import { BatchHttpLink } from 'apollo-link-batch-http';
import csrf from '~/lib/utils/csrf';
-export default (resolvers = {}, baseUrl = '') => {
+export default (resolvers = {}, config = {}) => {
let uri = `${gon.relative_url_root}/api/graphql`;
- if (baseUrl) {
+ if (config.baseUrl) {
// Prepend baseUrl and ensure that `///` are replaced with `/`
- uri = `${baseUrl}${uri}`.replace(/\/{3,}/g, '/');
+ uri = `${config.baseUrl}${uri}`.replace(/\/{3,}/g, '/');
}
+ const httpOptions = {
+ uri,
+ headers: {
+ [csrf.headerKey]: csrf.token,
+ },
+ };
+
return new ApolloClient({
- link: createUploadLink({
- uri,
- headers: {
- [csrf.headerKey]: csrf.token,
- },
- }),
- cache: new InMemoryCache(),
+ link: ApolloLink.split(
+ operation => operation.getContext().hasUpload,
+ createUploadLink(httpOptions),
+ new BatchHttpLink(httpOptions),
+ ),
+ cache: new InMemoryCache(config.cacheConfig),
resolvers,
});
};
diff --git a/app/assets/javascripts/lib/utils/accessor.js b/app/assets/javascripts/lib/utils/accessor.js
index 1d18992af63..39cffedcac6 100644
--- a/app/assets/javascripts/lib/utils/accessor.js
+++ b/app/assets/javascripts/lib/utils/accessor.js
@@ -2,7 +2,7 @@ function isPropertyAccessSafe(base, property) {
let safe;
try {
- safe = !!base[property];
+ safe = Boolean(base[property]);
} catch (error) {
safe = false;
}
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
index a24c71aeab1..28a7ebfdc69 100644
--- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
@@ -51,6 +51,7 @@ export default class LinkedTabs {
this.defaultAction = this.options.defaultAction;
this.action = this.options.action || this.defaultAction;
+ this.hashedTabs = this.options.hashedTabs || false;
if (this.action === 'show') {
this.action = this.defaultAction;
@@ -58,6 +59,10 @@ export default class LinkedTabs {
this.currentLocation = window.location;
+ if (this.hashedTabs) {
+ this.action = this.currentLocation.hash || this.action;
+ }
+
const tabSelector = `${this.options.parentEl} a[data-toggle="tab"]`;
// since this is a custom event we need jQuery :(
@@ -91,7 +96,9 @@ export default class LinkedTabs {
copySource.replace(/\/+$/, '');
- const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
+ const newState = this.hashedTabs
+ ? copySource
+ : `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
window.history.replaceState(
{
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index b236daff1e0..cc5e12aa467 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -94,6 +94,8 @@ export const handleLocationHash = () => {
const fixedNav = document.querySelector('.navbar-gitlab');
const performanceBar = document.querySelector('#js-peek');
const topPadding = 8;
+ const diffFileHeader = document.querySelector('.js-file-title');
+ const versionMenusContainer = document.querySelector('.mr-version-menus-container');
let adjustment = 0;
if (fixedNav) adjustment -= fixedNav.offsetHeight;
@@ -114,6 +116,14 @@ export const handleLocationHash = () => {
adjustment -= performanceBar.offsetHeight;
}
+ if (diffFileHeader) {
+ adjustment -= diffFileHeader.offsetHeight;
+ }
+
+ if (versionMenusContainer) {
+ adjustment -= versionMenusContainer.offsetHeight;
+ }
+
if (isInMRPage()) {
adjustment -= topPadding;
}
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 4d6327840db..d521c462ad8 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -3,7 +3,7 @@ import _ from 'underscore';
import timeago from 'timeago.js';
import dateFormat from 'dateformat';
import { pluralize } from './text_utility';
-import { languageCode, s__ } from '../../locale';
+import { languageCode, s__, __ } from '../../locale';
window.timeago = timeago;
@@ -63,7 +63,15 @@ export const pad = (val, len = 2) => `0${val}`.slice(-len);
* @returns {String}
*/
export const getDayName = date =>
- ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
+ [
+ __('Sunday'),
+ __('Monday'),
+ __('Tuesday'),
+ __('Wednesday'),
+ __('Thursday'),
+ __('Friday'),
+ __('Saturday'),
+ ][date.getDay()];
/**
* @example
@@ -71,7 +79,12 @@ export const getDayName = date =>
* @param {date} datetime
* @returns {String}
*/
-export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+export const formatDate = datetime => {
+ if (_.isString(datetime) && datetime.match(/\d+-\d+\d+ /)) {
+ throw new Error(__('Invalid date'));
+ }
+ return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+};
/**
* Timeago uses underscores instead of dashes to separate language from country code.
@@ -320,13 +333,13 @@ export const getSundays = date => {
}
const daysToSunday = [
- 'Saturday',
- 'Friday',
- 'Thursday',
- 'Wednesday',
- 'Tuesday',
- 'Monday',
- 'Sunday',
+ __('Saturday'),
+ __('Friday'),
+ __('Thursday'),
+ __('Wednesday'),
+ __('Tuesday'),
+ __('Monday'),
+ __('Sunday'),
];
const month = date.getMonth();
@@ -336,7 +349,7 @@ export const getSundays = date => {
while (dateOfMonth.getMonth() === month) {
const dayName = getDayName(dateOfMonth);
- if (dayName === 'Sunday') {
+ if (dayName === __('Sunday')) {
sundays.push(new Date(dateOfMonth.getTime()));
}
@@ -500,7 +513,7 @@ export const stringifyTime = (timeObject, fullNameFormat = false) => {
const reducedTime = _.reduce(
timeObject,
(memo, unitValue, unitName) => {
- const isNonZero = !!unitValue;
+ const isNonZero = Boolean(unitValue);
if (fullNameFormat && isNonZero) {
// Remove traling 's' if unit value is singular
diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js
index 19c4de6083d..61c8b8803d7 100644
--- a/app/assets/javascripts/lib/utils/number_utils.js
+++ b/app/assets/javascripts/lib/utils/number_utils.js
@@ -1,4 +1,5 @@
import { BYTES_IN_KIB } from './constants';
+import { sprintf, __ } from '~/locale';
/**
* Function that allows a number with an X amount of decimals
@@ -72,13 +73,13 @@ export function bytesToGiB(number) {
*/
export function numberToHumanSize(size) {
if (size < BYTES_IN_KIB) {
- return `${size} bytes`;
+ return sprintf(__('%{size} bytes'), { size });
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB) {
- return `${bytesToKiB(size).toFixed(2)} KiB`;
+ return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(2) });
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB) {
- return `${bytesToMiB(size).toFixed(2)} MiB`;
+ return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(2) });
}
- return `${bytesToGiB(size).toFixed(2)} GiB`;
+ return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(2) });
}
/**
@@ -99,3 +100,9 @@ export function numberToHumanSize(size) {
* @returns {Float} The summed value
*/
export const sum = (a = 0, b = 0) => a + b;
+
+/**
+ * Checks if the provided number is odd
+ * @param {Int} number
+ */
+export const isOdd = (number = 0) => number % 2;
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 84a617acb42..b7922e29bb0 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -223,9 +223,9 @@ export function insertMarkdownText({
return tag.replace(textPlaceholder, val);
}
if (val.indexOf(tag) === 0) {
- return '' + val.replace(tag, '');
+ return String(val.replace(tag, ''));
} else {
- return '' + tag + val;
+ return String(tag) + val;
}
})
.join('\n');
@@ -233,7 +233,7 @@ export function insertMarkdownText({
} else if (tag.indexOf(textPlaceholder) > -1) {
textToInsert = tag.replace(textPlaceholder, selected);
} else {
- textToInsert = '' + startChar + tag + selected + (wrap ? tag : ' ');
+ textToInsert = String(startChar) + tag + selected + (wrap ? tag : ' ');
}
if (removedFirstNewLine) {
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index bdfd06fc250..32fd0990374 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,3 +1,5 @@
+import { join as joinPaths } from 'path';
+
// Returns an array containing the value(s) of the
// of the key passed as an argument
export function getParameterValues(sParam) {
@@ -121,4 +123,48 @@ export function webIDEUrl(route = undefined) {
return returnUrl;
}
-export { join as joinPaths } from 'path';
+/**
+ * Returns current base URL
+ */
+export function getBaseURL() {
+ const { protocol, host } = window.location;
+ return `${protocol}//${host}`;
+}
+
+/**
+ * Returns true if url is an absolute or root-relative URL
+ *
+ * @param {String} url
+ */
+export function isAbsoluteOrRootRelative(url) {
+ return /^(https?:)?\//.test(url);
+}
+
+/**
+ * Checks if the provided URL is a safe URL (absolute http(s) or root-relative URL)
+ *
+ * @param {String} url that will be checked
+ * @returns {Boolean}
+ */
+export function isSafeURL(url) {
+ if (!isAbsoluteOrRootRelative(url)) {
+ return false;
+ }
+
+ try {
+ const parsedUrl = new URL(url, getBaseURL());
+ return ['http:', 'https:'].includes(parsedUrl.protocol);
+ } catch (e) {
+ return false;
+ }
+}
+
+export function getWebSocketProtocol() {
+ return window.location.protocol.replace('http', 'ws');
+}
+
+export function getWebSocketUrl(path) {
+ return `${getWebSocketProtocol()}//${joinPaths(window.location.host, path)}`;
+}
+
+export { joinPaths };
diff --git a/app/assets/javascripts/monitoring/components/charts/single_stat.vue b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
new file mode 100644
index 00000000000..b03a6ca1806
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
@@ -0,0 +1,37 @@
+<script>
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+
+export default {
+ components: {
+ GlSingleStat,
+ },
+ inheritAttrs: false,
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+ value: {
+ type: Number,
+ required: true,
+ },
+ unit: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ valueWithUnit() {
+ return `${this.value}${this.unit}`;
+ },
+ },
+};
+</script>
+<template>
+ <div class="prometheus-graph col-12 col-lg-6">
+ <div class="prometheus-graph-header">
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ title }}</h5>
+ </div>
+ <gl-single-stat :value="valueWithUnit" :title="title" variant="success" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index ff1e1805948..2314f7b80cf 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -8,16 +8,14 @@ import {
GlLink,
} from '@gitlab/ui';
import _ from 'underscore';
+import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import '~/vue_shared/mixins/is_ee';
import { getParameterValues } from '~/lib/utils/url_utility';
-import Flash from '../../flash';
-import MonitoringService from '../services/monitoring_service';
import MonitorAreaChart from './charts/area.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
-import MonitoringStore from '../stores/monitoring_store';
import { timeWindows, timeWindowsKeyNames } from '../constants';
import { getTimeDiff } from '../utils';
@@ -40,7 +38,7 @@ export default {
GlModalDirective,
},
props: {
- externalDashboardPath: {
+ externalDashboardUrl: {
type: String,
required: false,
default: '',
@@ -108,10 +106,6 @@ export default {
type: String,
required: true,
},
- showTimeWindowDropdown: {
- type: Boolean,
- required: true,
- },
customMetricsAvailable: {
type: Boolean,
required: false,
@@ -128,9 +122,7 @@ export default {
},
data() {
return {
- store: new MonitoringStore(),
state: 'gettingStarted',
- showEmptyState: true,
elWidth: 0,
selectedTimeWindow: '',
selectedTimeWindowKey: '',
@@ -141,13 +133,21 @@ export default {
canAddMetrics() {
return this.customMetricsAvailable && this.customMetricsPath.length;
},
+ ...mapState('monitoringDashboard', [
+ 'groups',
+ 'emptyState',
+ 'showEmptyState',
+ 'environments',
+ 'deploymentData',
+ ]),
},
created() {
- this.service = new MonitoringService({
+ this.setEndpoints({
metricsEndpoint: this.metricsEndpoint,
- deploymentEndpoint: this.deploymentEndpoint,
environmentsEndpoint: this.environmentsEndpoint,
+ deploymentsEndpoint: this.deploymentEndpoint,
});
+
this.timeWindows = timeWindows;
this.selectedTimeWindowKey =
_.escape(getParameterValues('time_window')[0]) || timeWindowsKeyNames.eightHours;
@@ -165,31 +165,11 @@ export default {
}
},
mounted() {
- const startEndWindow = getTimeDiff(this.timeWindows[this.selectedTimeWindowKey]);
- this.servicePromises = [
- this.service
- .getGraphsData(startEndWindow)
- .then(data => this.store.storeMetrics(data))
- .catch(() => Flash(s__('Metrics|There was an error while retrieving metrics'))),
- this.service
- .getDeploymentData()
- .then(data => this.store.storeDeploymentData(data))
- .catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
- ];
if (!this.hasMetrics) {
- this.state = 'gettingStarted';
+ this.setGettingStartedEmptyState();
} else {
- if (this.environmentsEndpoint) {
- this.servicePromises.push(
- this.service
- .getEnvironmentsData()
- .then(data => this.store.storeEnvironmentsData(data))
- .catch(() =>
- Flash(s__('Metrics|There was an error getting environments information.')),
- ),
- );
- }
- this.getGraphsData();
+ this.fetchData(getTimeDiff(this.selectedTimeWindow));
+
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
attributes: true,
@@ -199,6 +179,11 @@ export default {
}
},
methods: {
+ ...mapActions('monitoringDashboard', [
+ 'fetchData',
+ 'setGettingStartedEmptyState',
+ 'setEndpoints',
+ ]),
getGraphAlerts(queries) {
if (!this.allAlerts) return {};
const metricIdsForChart = queries.map(q => q.metricId);
@@ -207,21 +192,6 @@ export default {
getGraphAlertValues(queries) {
return Object.values(this.getGraphAlerts(queries));
},
- getGraphsData() {
- this.state = 'loading';
- Promise.all(this.servicePromises)
- .then(() => {
- if (this.store.groups.length < 1) {
- this.state = 'noData';
- return;
- }
-
- this.showEmptyState = false;
- })
- .catch(() => {
- this.state = 'unableToConnect';
- });
- },
hideAddMetricModal() {
this.$refs.addMetricModal.hide();
},
@@ -263,10 +233,10 @@ export default {
class="prepend-left-10 js-environments-dropdown"
toggle-class="dropdown-menu-toggle"
:text="currentEnvironmentName"
- :disabled="store.environmentsData.length === 0"
+ :disabled="environments.length === 0"
>
<gl-dropdown-item
- v-for="environment in store.environmentsData"
+ v-for="environment in environments"
:key="environment.id"
:active="environment.name === currentEnvironmentName"
active-class="is-active"
@@ -274,7 +244,7 @@ export default {
>
</gl-dropdown>
</div>
- <div v-if="showTimeWindowDropdown" class="d-flex align-items-center">
+ <div class="d-flex align-items-center prepend-left-8">
<strong>{{ s__('Metrics|Show last') }}</strong>
<gl-dropdown
class="prepend-left-10 js-time-window-dropdown"
@@ -325,10 +295,11 @@ export default {
</gl-modal>
</div>
<gl-button
- v-if="externalDashboardPath.length"
+ v-if="externalDashboardUrl.length"
class="js-external-dashboard-link prepend-left-8"
variant="primary"
- :href="externalDashboardPath"
+ :href="externalDashboardUrl"
+ target="_blank"
>
{{ __('View full dashboard') }}
<icon name="external-link" />
@@ -336,7 +307,7 @@ export default {
</div>
</div>
<graph-group
- v-for="(groupData, index) in store.groups"
+ v-for="(groupData, index) in groups"
:key="index"
:name="groupData.group"
:show-panels="showPanels"
@@ -345,7 +316,7 @@ export default {
v-for="(graphData, graphIndex) in groupData.metrics"
:key="graphIndex"
:graph-data="graphData"
- :deployment-data="store.deploymentData"
+ :deployment-data="deploymentData"
:thresholds="getGraphAlertValues(graphData.queries)"
:container-width="elWidth"
group-id="monitor-area-chart"
@@ -362,7 +333,7 @@ export default {
</div>
<empty-state
v-else
- :selected-state="state"
+ :selected-state="emptyState"
:documentation-path="documentationPath"
:settings-path="settingsPath"
:clusters-path="clustersPath"
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 08dc57d545c..62c0f44c1e6 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue';
+import store from './stores';
export default (props = {}) => {
const el = document.getElementById('prometheus-graphs');
@@ -9,12 +10,12 @@ export default (props = {}) => {
// eslint-disable-next-line no-new
new Vue({
el,
+ store,
render(createElement) {
return createElement(Dashboard, {
props: {
...el.dataset,
hasMetrics: parseBoolean(el.dataset.hasMetrics),
- showTimeWindowDropdown: gon.features.metricsTimeWindow,
...props,
},
});
diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js
deleted file mode 100644
index 1efa5189996..00000000000
--- a/app/assets/javascripts/monitoring/services/monitoring_service.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import axios from '../../lib/utils/axios_utils';
-import statusCodes from '../../lib/utils/http_status';
-import { backOff } from '../../lib/utils/common_utils';
-import { s__, __ } from '../../locale';
-
-const MAX_REQUESTS = 3;
-
-function backOffRequest(makeRequestCallback) {
- let requestCounter = 0;
- return backOff((next, stop) => {
- makeRequestCallback()
- .then(resp => {
- if (resp.status === statusCodes.NO_CONTENT) {
- requestCounter += 1;
- if (requestCounter < MAX_REQUESTS) {
- next();
- } else {
- stop(new Error(__('Failed to connect to the prometheus server')));
- }
- } else {
- stop(resp);
- }
- })
- .catch(stop);
- });
-}
-
-export default class MonitoringService {
- constructor({ metricsEndpoint, deploymentEndpoint, environmentsEndpoint }) {
- this.metricsEndpoint = metricsEndpoint;
- this.deploymentEndpoint = deploymentEndpoint;
- this.environmentsEndpoint = environmentsEndpoint;
- }
-
- getGraphsData(params = {}) {
- return backOffRequest(() => axios.get(this.metricsEndpoint, { params }))
- .then(resp => resp.data)
- .then(response => {
- if (!response || !response.data || !response.success) {
- throw new Error(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
- }
- return response.data;
- });
- }
-
- getDeploymentData() {
- if (!this.deploymentEndpoint) {
- return Promise.resolve([]);
- }
- return backOffRequest(() => axios.get(this.deploymentEndpoint))
- .then(resp => resp.data)
- .then(response => {
- if (!response || !response.deployments) {
- throw new Error(
- s__('Metrics|Unexpected deployment data response from prometheus endpoint'),
- );
- }
- return response.deployments;
- });
- }
-
- getEnvironmentsData() {
- return axios
- .get(this.environmentsEndpoint)
- .then(resp => resp.data)
- .then(response => {
- if (!response || !response.environments) {
- throw new Error(
- s__('Metrics|There was an error fetching the environments data, please try again'),
- );
- }
- return response.environments;
- });
- }
-}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
new file mode 100644
index 00000000000..63c23e8449d
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -0,0 +1,117 @@
+import * as types from './mutation_types';
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
+import statusCodes from '../../lib/utils/http_status';
+import { backOff } from '../../lib/utils/common_utils';
+import { s__, __ } from '../../locale';
+
+const MAX_REQUESTS = 3;
+
+function backOffRequest(makeRequestCallback) {
+ let requestCounter = 0;
+ return backOff((next, stop) => {
+ makeRequestCallback()
+ .then(resp => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ requestCounter += 1;
+ if (requestCounter < MAX_REQUESTS) {
+ next();
+ } else {
+ stop(new Error(__('Failed to connect to the prometheus server')));
+ }
+ } else {
+ stop(resp);
+ }
+ })
+ .catch(stop);
+ });
+}
+
+export const setGettingStartedEmptyState = ({ commit }) => {
+ commit(types.SET_GETTING_STARTED_EMPTY_STATE);
+};
+
+export const setEndpoints = ({ commit }, endpoints) => {
+ commit(types.SET_ENDPOINTS, endpoints);
+};
+
+export const requestMetricsData = ({ commit }) => commit(types.REQUEST_METRICS_DATA);
+export const receiveMetricsDataSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_METRICS_DATA_SUCCESS, data);
+export const receiveMetricsDataFailure = ({ commit }, error) =>
+ commit(types.RECEIVE_METRICS_DATA_FAILURE, error);
+export const receiveDeploymentsDataSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, data);
+export const receiveDeploymentsDataFailure = ({ commit }) =>
+ commit(types.RECEIVE_DEPLOYMENTS_DATA_FAILURE);
+export const receiveEnvironmentsDataSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data);
+export const receiveEnvironmentsDataFailure = ({ commit }) =>
+ commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE);
+
+export const fetchData = ({ dispatch }, params) => {
+ dispatch('fetchMetricsData', params);
+ dispatch('fetchDeploymentsData');
+ dispatch('fetchEnvironmentsData');
+};
+
+export const fetchMetricsData = ({ state, dispatch }, params) => {
+ dispatch('requestMetricsData');
+
+ return backOffRequest(() => axios.get(state.metricsEndpoint, { params }))
+ .then(resp => resp.data)
+ .then(response => {
+ if (!response || !response.data || !response.success) {
+ dispatch('receiveMetricsDataFailure', null);
+ createFlash(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
+ }
+ dispatch('receiveMetricsDataSuccess', response.data);
+ })
+ .catch(error => {
+ dispatch('receiveMetricsDataFailure', error);
+ createFlash(s__('Metrics|There was an error while retrieving metrics'));
+ });
+};
+
+export const fetchDeploymentsData = ({ state, dispatch }) => {
+ if (!state.deploymentEndpoint) {
+ return Promise.resolve([]);
+ }
+ return backOffRequest(() => axios.get(state.deploymentEndpoint))
+ .then(resp => resp.data)
+ .then(response => {
+ if (!response || !response.deployments) {
+ createFlash(s__('Metrics|Unexpected deployment data response from prometheus endpoint'));
+ }
+
+ dispatch('receiveDeploymentsDataSuccess', response.deployments);
+ })
+ .catch(() => {
+ dispatch('receiveDeploymentsDataFailure');
+ createFlash(s__('Metrics|There was an error getting deployment information.'));
+ });
+};
+
+export const fetchEnvironmentsData = ({ state, dispatch }) => {
+ if (!state.environmentsEndpoint) {
+ return Promise.resolve([]);
+ }
+ return axios
+ .get(state.environmentsEndpoint)
+ .then(resp => resp.data)
+ .then(response => {
+ if (!response || !response.environments) {
+ createFlash(
+ s__('Metrics|There was an error fetching the environments data, please try again'),
+ );
+ }
+ dispatch('receiveEnvironmentsDataSuccess', response.environments);
+ })
+ .catch(() => {
+ dispatch('receiveEnvironmentsDataFailure');
+ createFlash(s__('Metrics|There was an error getting environments information.'));
+ });
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/index.js b/app/assets/javascripts/monitoring/stores/index.js
new file mode 100644
index 00000000000..d58398c54ae
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/index.js
@@ -0,0 +1,21 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+export const createStore = () =>
+ new Vuex.Store({
+ modules: {
+ monitoringDashboard: {
+ namespaced: true,
+ actions,
+ mutations,
+ state,
+ },
+ },
+ });
+
+export default createStore();
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
new file mode 100644
index 00000000000..3fd9e07fa8b
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -0,0 +1,12 @@
+export const REQUEST_METRICS_DATA = 'REQUEST_METRICS_DATA';
+export const RECEIVE_METRICS_DATA_SUCCESS = 'RECEIVE_METRICS_DATA_SUCCESS';
+export const RECEIVE_METRICS_DATA_FAILURE = 'RECEIVE_METRICS_DATA_FAILURE';
+export const REQUEST_DEPLOYMENTS_DATA = 'REQUEST_DEPLOYMENTS_DATA';
+export const RECEIVE_DEPLOYMENTS_DATA_SUCCESS = 'RECEIVE_DEPLOYMENTS_DATA_SUCCESS';
+export const RECEIVE_DEPLOYMENTS_DATA_FAILURE = 'RECEIVE_DEPLOYMENTS_DATA_FAILURE';
+export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA';
+export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS';
+export const RECEIVE_ENVIRONMENTS_DATA_FAILURE = 'RECEIVE_ENVIRONMENTS_DATA_FAILURE';
+export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
+export const SET_ENDPOINTS = 'SET_ENDPOINTS';
+export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
new file mode 100644
index 00000000000..c1779333d75
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -0,0 +1,45 @@
+import * as types from './mutation_types';
+import { normalizeMetrics, sortMetrics } from './utils';
+
+export default {
+ [types.REQUEST_METRICS_DATA](state) {
+ state.emptyState = 'loading';
+ state.showEmptyState = true;
+ },
+ [types.RECEIVE_METRICS_DATA_SUCCESS](state, groupData) {
+ state.groups = groupData.map(group => ({
+ ...group,
+ metrics: normalizeMetrics(sortMetrics(group.metrics)),
+ }));
+
+ if (!state.groups.length) {
+ state.emptyState = 'noData';
+ } else {
+ state.showEmptyState = false;
+ }
+ },
+ [types.RECEIVE_METRICS_DATA_FAILURE](state, error) {
+ state.emptyState = error ? 'unableToConnect' : 'noData';
+ state.showEmptyState = true;
+ },
+ [types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](state, deployments) {
+ state.deploymentData = deployments;
+ },
+ [types.RECEIVE_DEPLOYMENTS_DATA_FAILURE](state) {
+ state.deploymentData = [];
+ },
+ [types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS](state, environments) {
+ state.environments = environments;
+ },
+ [types.RECEIVE_ENVIRONMENTS_DATA_FAILURE](state) {
+ state.environments = [];
+ },
+ [types.SET_ENDPOINTS](state, endpoints) {
+ state.metricsEndpoint = endpoints.metricsEndpoint;
+ state.environmentsEndpoint = endpoints.environmentsEndpoint;
+ state.deploymentsEndpoint = endpoints.deploymentsEndpoint;
+ },
+ [types.SET_GETTING_STARTED_EMPTY_STATE](state) {
+ state.emptyState = 'gettingStarted';
+ },
+};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
new file mode 100644
index 00000000000..5103122612a
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -0,0 +1,12 @@
+export default () => ({
+ hasMetrics: false,
+ showPanels: true,
+ metricsEndpoint: null,
+ environmentsEndpoint: null,
+ deploymentsEndpoint: null,
+ emptyState: 'gettingStarted',
+ showEmptyState: true,
+ groups: [],
+ deploymentData: [],
+ environments: [],
+});
diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/utils.js
index 013fb0d4540..9216554ecbf 100644
--- a/app/assets/javascripts/monitoring/stores/monitoring_store.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -1,12 +1,5 @@
import _ from 'underscore';
-function sortMetrics(metrics) {
- return _.chain(metrics)
- .sortBy('title')
- .sortBy('weight')
- .value();
-}
-
function checkQueryEmptyData(query) {
return {
...query,
@@ -59,7 +52,13 @@ function groupQueriesByChartInfo(metrics) {
return Object.values(metricsByChart);
}
-function normalizeMetrics(metrics) {
+export const sortMetrics = metrics =>
+ _.chain(metrics)
+ .sortBy('title')
+ .sortBy('weight')
+ .value();
+
+export const normalizeMetrics = metrics => {
const groupedMetrics = groupQueriesByChartInfo(metrics);
return groupedMetrics.map(metric => {
@@ -81,31 +80,4 @@ function normalizeMetrics(metrics) {
queries: removeTimeSeriesNoData(queries),
};
});
-}
-
-export default class MonitoringStore {
- constructor() {
- this.groups = [];
- this.deploymentData = [];
- this.environmentsData = [];
- }
-
- storeMetrics(groups = []) {
- this.groups = groups.map(group => ({
- ...group,
- metrics: normalizeMetrics(sortMetrics(group.metrics)),
- }));
- }
-
- storeDeploymentData(deploymentData = []) {
- this.deploymentData = deploymentData;
- }
-
- storeEnvironmentsData(environmentsData = []) {
- this.environmentsData = environmentsData.filter(environment => !!environment.last_deployment);
- }
-
- getMetricsCount() {
- return this.groups.reduce((count, group) => count + group.metrics.length, 0);
- }
-}
+};
diff --git a/app/assets/javascripts/mr_notes/stores/getters.js b/app/assets/javascripts/mr_notes/stores/getters.js
index b10e9f9f9f1..e48cfcd9564 100644
--- a/app/assets/javascripts/mr_notes/stores/getters.js
+++ b/app/assets/javascripts/mr_notes/stores/getters.js
@@ -1,5 +1,5 @@
export default {
isLoggedIn(state, getters) {
- return !!getters.getUserData.id;
+ return Boolean(getters.getUserData.id);
},
};
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index d03f4508fb8..a7156bd2406 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -7,6 +7,10 @@ no-unused-vars, no-shadow, no-useless-escape, class-methods-use-this */
/* global ResolveService */
/* global mrRefreshWidgetUrl */
+/*
+old_notes_spec.js is the spec for the legacy, jQuery notes application. It has nothing to do with the new, fancy Vue notes app.
+ */
+
import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
@@ -986,6 +990,14 @@ export default class Notes {
form.find('#note_position').val(dataHolder.attr('data-position'));
form
+ .prepend(
+ `<div class="avatar-note-form-holder"><div class="content"><a href="${escape(
+ gon.current_username,
+ )}" class="user-avatar-link d-none d-sm-block"><img class="avatar s40" src="${encodeURI(
+ gon.current_user_avatar_url,
+ )}" alt="${escape(gon.current_user_fullname)}" /></a></div></div>`,
+ )
+ .append('</div>')
.find('.js-close-discussion-note-form')
.show()
.removeClass('hide');
@@ -1021,6 +1033,9 @@ export default class Notes {
target: $link,
lineType: link.dataset.lineType,
showReplyInput,
+ currentUsername: gon.current_username,
+ currentUserAvatar: gon.current_user_avatar_url,
+ currentUserFullname: gon.current_user_fullname,
});
}
@@ -1049,7 +1064,15 @@ export default class Notes {
this.setupDiscussionNoteForm($link, newForm);
}
- toggleDiffNote({ target, lineType, forceShow, showReplyInput = false }) {
+ toggleDiffNote({
+ target,
+ lineType,
+ forceShow,
+ showReplyInput = false,
+ currentUsername,
+ currentUserAvatar,
+ currentUserFullname,
+ }) {
var $link,
addForm,
hasNotes,
@@ -1542,7 +1565,9 @@ export default class Notes {
<div class="note-header">
<div class="note-header-info">
<a href="/${_.escape(currentUsername)}">
- <span class="d-none d-sm-inline-block">${_.escape(currentUsername)}</span>
+ <span class="d-none d-sm-inline-block bold">${_.escape(
+ currentUsername,
+ )}</span>
<span class="note-headline-light">${_.escape(currentUsername)}</span>
</a>
</div>
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 688c06878ac..075c28e8d07 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -337,6 +337,8 @@ Please check your network connection and try again.`;
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
+ :locked-issue-docs-path="lockedIssueDocsPath"
+ :confidential-issue-docs-path="confidentialIssueDocsPath"
/>
<markdown-field
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index c7cfc0f0f3b..efd84f5722c 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -49,22 +49,26 @@ export default {
</script>
<template>
- <div v-if="resolvableDiscussionsCount > 0" class="line-resolve-all-container prepend-top-8">
- <div>
+ <div v-if="resolvableDiscussionsCount > 0" class="line-resolve-all-container full-width-mobile">
+ <div class="full-width-mobile d-flex d-sm-block">
<div :class="{ 'has-next-btn': hasNextButton }" class="line-resolve-all">
<span
:class="{ 'is-active': allResolved }"
class="line-resolve-btn is-disabled"
type="button"
>
- <icon name="check-circle" />
+ <icon :name="allResolved ? 'check-circle-filled' : 'check-circle'" />
</span>
<span class="line-resolve-text">
{{ resolvedDiscussionsCount }}/{{ resolvableDiscussionsCount }}
{{ n__('discussion resolved', 'discussions resolved', resolvableDiscussionsCount) }}
</span>
</div>
- <div v-if="resolveAllDiscussionsIssuePath && !allResolved" class="btn-group" role="group">
+ <div
+ v-if="resolveAllDiscussionsIssuePath && !allResolved"
+ class="btn-group btn-group-sm"
+ role="group"
+ >
<a
v-gl-tooltip
:href="resolveAllDiscussionsIssuePath"
@@ -74,7 +78,7 @@ export default {
<icon name="issue-new" />
</a>
</div>
- <div v-if="isLoggedIn && !allResolved" class="btn-group" role="group">
+ <div v-if="isLoggedIn && !allResolved" class="btn-group btn-group-sm" role="group">
<button
v-gl-tooltip
title="Jump to first unresolved discussion"
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 47951591e82..eb3fbbe1385 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -105,12 +105,12 @@ export default {
<template>
<div
v-if="displayFilters"
- class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom"
+ class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom full-width-mobile"
>
<button
id="discussion-filter-dropdown"
ref="dropdownToggle"
- class="btn btn-default qa-discussion-filter"
+ class="btn btn-sm qa-discussion-filter"
data-toggle="dropdown"
aria-expanded="false"
>
diff --git a/app/assets/javascripts/notes/components/discussion_locked_widget.vue b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
index c469a6b7bcd..53f509185a8 100644
--- a/app/assets/javascripts/notes/components/discussion_locked_widget.vue
+++ b/app/assets/javascripts/notes/components/discussion_locked_widget.vue
@@ -1,12 +1,24 @@
<script>
+import { GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
+import { __, sprintf } from '~/locale';
import Issuable from '~/vue_shared/mixins/issuable';
+import issuableStateMixin from '../mixins/issuable_state';
export default {
components: {
Icon,
+ GlLink,
+ },
+ mixins: [Issuable, issuableStateMixin],
+ computed: {
+ lockedIssueWarning() {
+ return sprintf(
+ __('This %{issuableDisplayName} is locked. Only project members can comment.'),
+ { issuableDisplayName: this.issuableDisplayName },
+ );
+ },
},
- mixins: [Issuable],
};
</script>
@@ -15,7 +27,11 @@ export default {
<span class="issuable-note-warning inline">
<icon :size="16" name="lock" class="icon" />
<span>
- This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.
+ {{ lockedIssueWarning }}
+
+ <gl-link :href="lockedIssueDocsPath" target="_blank" class="learn-more">
+ {{ __('Learn more') }}
+ </gl-link>
</span>
</span>
</div>
diff --git a/app/assets/javascripts/notes/components/discussion_notes.vue b/app/assets/javascripts/notes/components/discussion_notes.vue
index 5b6163a6214..228bb652597 100644
--- a/app/assets/javascripts/notes/components/discussion_notes.vue
+++ b/app/assets/javascripts/notes/components/discussion_notes.vue
@@ -49,7 +49,7 @@ export default {
computed: {
...mapGetters(['userCanReply']),
hasReplies() {
- return !!this.replies.length;
+ return Boolean(this.replies.length);
},
replies() {
return this.discussion.notes.slice(1);
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 7e8f23d6a96..844d0c3e376 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -135,7 +135,7 @@ export default {
@click="onResolve"
>
<template v-if="!isResolving">
- <icon name="check-circle" />
+ <icon :name="isResolved ? 'check-circle-filled' : 'check-circle'" />
</template>
<gl-loading-icon v-else inline />
</button>
@@ -147,13 +147,11 @@ export default {
class="note-action-button note-emoji-button js-add-award js-note-emoji"
href="#"
title="Add reaction"
+ data-position="right"
>
- <icon
- css-classes="link-highlight award-control-icon-neutral"
- name="emoji_slightly_smiling_face"
- />
- <icon css-classes="link-highlight award-control-icon-positive" name="emoji_smiley" />
- <icon css-classes="link-highlight award-control-icon-super-positive" name="emoji_smiley" />
+ <icon css-classes="link-highlight award-control-icon-neutral" name="slight-smile" />
+ <icon css-classes="link-highlight award-control-icon-positive" name="smiley" />
+ <icon css-classes="link-highlight award-control-icon-super-positive" name="smiley" />
</a>
</div>
<reply-button
@@ -197,7 +195,7 @@ export default {
</button>
<ul class="dropdown-menu more-actions-dropdown dropdown-open-left">
<li v-if="canReportAsAbuse">
- <a :href="reportAbusePath">{{ __('Report abuse to GitLab') }}</a>
+ <a :href="reportAbusePath">{{ __('Report abuse to admin') }}</a>
</li>
<li v-if="noteUrl">
<button
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index 17e5fcab5b7..941b6d5cab3 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -189,13 +189,13 @@ export default {
type="button"
>
<span class="award-control-icon award-control-icon-neutral">
- <icon name="emoji_slightly_smiling_face" />
+ <icon name="slight-smile" />
</span>
<span class="award-control-icon award-control-icon-positive">
- <icon name="emoji_smiley" />
+ <icon name="smiley" />
</span>
<span class="award-control-icon award-control-icon-super-positive">
- <icon name="emoji_smiley" />
+ <icon name="smiley" />
</span>
<i
aria-hidden="true"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index acbb91ce7be..09ecb695214 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -234,6 +234,8 @@ export default {
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
+ :locked-issue-docs-path="lockedIssueDocsPath"
+ :confidential-issue-docs-path="confidentialIssueDocsPath"
/>
<markdown-field
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 5c59c0c32dd..fbf82fab9e9 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -82,7 +82,7 @@ export default {
:data-username="author.username"
>
<slot name="note-header-info"></slot>
- <span class="note-header-author-name">{{ author.name }}</span>
+ <span class="note-header-author-name bold">{{ author.name }}</span>
<span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
<span class="note-headline-light">@{{ author.username }}</span>
</a>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 2c549e7abdd..eb6a4a67fff 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -87,7 +87,11 @@ export default {
'unresolvedDiscussionsCount',
'hasUnresolvedDiscussions',
'showJumpToNextDiscussion',
+ 'getUserData',
]),
+ currentUser() {
+ return this.getUserData;
+ },
author() {
return this.firstNote.author;
},
@@ -377,6 +381,14 @@ Please check your network connection and try again.`;
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder"
>
+ <user-avatar-link
+ v-if="!isReplying && currentUser"
+ :link-href="currentUser.path"
+ :img-src="currentUser.avatar_url"
+ :img-alt="currentUser.name"
+ :img-size="40"
+ class="d-none d-sm-block"
+ />
<discussion-actions
v-if="!isReplying && userCanReply"
:discussion="discussion"
@@ -388,18 +400,27 @@ Please check your network connection and try again.`;
@resolve="resolveHandler"
@jumpToNextDiscussion="jumpToNextDiscussion"
/>
- <note-form
- v-if="isReplying"
- ref="noteForm"
- :discussion="discussion"
- :is-editing="false"
- :line="diffLine"
- save-button-title="Comment"
- :autosave-key="autosaveKey"
- @handleFormUpdateAddToReview="addReplyToReview"
- @handleFormUpdate="saveReply"
- @cancelForm="cancelReplyForm"
- />
+ <div v-if="isReplying" class="avatar-note-form-holder">
+ <user-avatar-link
+ v-if="currentUser"
+ :link-href="currentUser.path"
+ :img-src="currentUser.avatar_url"
+ :img-alt="currentUser.name"
+ :img-size="40"
+ class="d-none d-sm-block"
+ />
+ <note-form
+ ref="noteForm"
+ :discussion="discussion"
+ :is-editing="false"
+ :line="diffLine"
+ save-button-title="Comment"
+ :autosave-key="autosaveKey"
+ @handleFormUpdateAddToReview="addReplyToReview"
+ @handleFormUpdate="saveReply"
+ @cancelForm="cancelReplyForm"
+ />
+ </div>
<note-signed-out-widget v-if="!userCanReply" />
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 47d74c2f892..aa80e25a3e0 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -10,7 +10,7 @@ import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue';
-import noteBody from './note_body.vue';
+import NoteBody from './note_body.vue';
import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
@@ -21,7 +21,7 @@ export default {
userAvatarLink,
noteHeader,
noteActions,
- noteBody,
+ NoteBody,
TimelineEntryItem,
},
mixins: [noteable, resolvable, draftMixin],
@@ -75,7 +75,7 @@ export default {
};
},
canReportAsAbuse() {
- return !!this.note.report_abuse_path && this.author.id !== this.getUserData.id;
+ return Boolean(this.note.report_abuse_path) && this.author.id !== this.getUserData.id;
},
noteAnchorId() {
return `note_${this.note.id}`;
@@ -209,7 +209,10 @@ export default {
// we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content
this.note.note = noteText;
- this.$refs.noteBody.note.note = noteText;
+ const { noteBody } = this.$refs;
+ if (noteBody) {
+ noteBody.note.note = noteText;
+ }
},
},
};
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 0f1976db37d..4d00e957973 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -127,6 +127,9 @@ export default {
initUserPopovers(this.$el.querySelectorAll('.js-user-link'));
});
},
+ beforeDestroy() {
+ this.stopPolling();
+ },
methods: {
...mapActions([
'setLoadingState',
@@ -144,6 +147,7 @@ export default {
'expandDiscussion',
'startTaskList',
'convertToDiscussion',
+ 'stopPolling',
]),
fetchNotes() {
if (this.isFetching) return null;
diff --git a/app/assets/javascripts/notes/mixins/issuable_state.js b/app/assets/javascripts/notes/mixins/issuable_state.js
index 97f3ea0d5de..d97d9f6850a 100644
--- a/app/assets/javascripts/notes/mixins/issuable_state.js
+++ b/app/assets/javascripts/notes/mixins/issuable_state.js
@@ -1,11 +1,22 @@
+import { mapGetters } from 'vuex';
+
export default {
+ computed: {
+ ...mapGetters(['getNoteableDataByProp']),
+ lockedIssueDocsPath() {
+ return this.getNoteableDataByProp('locked_discussion_docs_path');
+ },
+ confidentialIssueDocsPath() {
+ return this.getNoteableDataByProp('confidential_issues_docs_path');
+ },
+ },
methods: {
isConfidential(issue) {
- return !!issue.confidential;
+ return Boolean(issue.confidential);
},
isLocked(issue) {
- return !!issue.discussion_locked;
+ return Boolean(issue.discussion_locked);
},
hasWarning(issue) {
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 2d150e64ef7..d7982be3e4b 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -20,7 +20,7 @@ export const getNoteableData = state => state.noteableData;
export const getNoteableDataByProp = state => prop => state.noteableData[prop];
-export const userCanReply = state => !!state.noteableData.current_user.can_create_note;
+export const userCanReply = state => Boolean(state.noteableData.current_user.can_create_note);
export const openState = state => state.noteableData.state;
diff --git a/app/assets/javascripts/operation_settings/components/external_dashboard.vue b/app/assets/javascripts/operation_settings/components/external_dashboard.vue
index 0a87d193b72..ed518611d0b 100644
--- a/app/assets/javascripts/operation_settings/components/external_dashboard.vue
+++ b/app/assets/javascripts/operation_settings/components/external_dashboard.vue
@@ -1,4 +1,5 @@
<script>
+import { mapState, mapActions } from 'vuex';
import { GlButton, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
export default {
@@ -8,26 +9,34 @@ export default {
GlFormInput,
GlLink,
},
- props: {
- externalDashboardPath: {
- type: String,
- required: false,
- default: '',
- },
- externalDashboardHelpPagePath: {
- type: String,
- required: true,
+ computed: {
+ ...mapState([
+ 'externalDashboardHelpPagePath',
+ 'externalDashboardUrl',
+ 'operationsSettingsEndpoint',
+ ]),
+ userDashboardUrl: {
+ get() {
+ return this.externalDashboardUrl;
+ },
+ set(url) {
+ this.setExternalDashboardUrl(url);
+ },
},
},
+ methods: {
+ ...mapActions(['setExternalDashboardUrl', 'updateExternalDashboardUrl']),
+ },
};
</script>
<template>
- <section class="settings expanded">
+ <section class="settings no-animate">
<div class="settings-header">
<h4 class="js-section-header">
{{ s__('ExternalMetrics|External Dashboard') }}
</h4>
+ <gl-button class="js-settings-toggle">{{ __('Expand') }}</gl-button>
<p class="js-section-sub-header">
{{
s__(
@@ -44,11 +53,12 @@ export default {
:description="s__('ExternalMetrics|Enter the URL of the dashboard you want to link to')"
>
<gl-form-input
- :value="externalDashboardPath"
+ v-model="userDashboardUrl"
placeholder="https://my-org.gitlab.io/my-dashboards"
+ @keydown.enter.native.prevent="updateExternalDashboardUrl"
/>
</gl-form-group>
- <gl-button variant="success">
+ <gl-button variant="success" @click="updateExternalDashboardUrl">
{{ __('Save Changes') }}
</gl-button>
</form>
diff --git a/app/assets/javascripts/operation_settings/index.js b/app/assets/javascripts/operation_settings/index.js
index 1171f3ece9f..6946578e6d2 100644
--- a/app/assets/javascripts/operation_settings/index.js
+++ b/app/assets/javascripts/operation_settings/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import store from './store';
import ExternalDashboardForm from './components/external_dashboard.vue';
export default () => {
@@ -14,13 +15,9 @@ export default () => {
return new Vue({
el,
+ store: store(el.dataset),
render(createElement) {
- return createElement(ExternalDashboardForm, {
- props: {
- ...el.dataset,
- expanded: false,
- },
- });
+ return createElement(ExternalDashboardForm);
},
});
};
diff --git a/app/assets/javascripts/operation_settings/store/actions.js b/app/assets/javascripts/operation_settings/store/actions.js
new file mode 100644
index 00000000000..ec05b0c76cf
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/actions.js
@@ -0,0 +1,38 @@
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import createFlash from '~/flash';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import * as mutationTypes from './mutation_types';
+
+export const setExternalDashboardUrl = ({ commit }, url) =>
+ commit(mutationTypes.SET_EXTERNAL_DASHBOARD_URL, url);
+
+export const updateExternalDashboardUrl = ({ state, dispatch }) =>
+ axios
+ .patch(state.operationsSettingsEndpoint, {
+ project: {
+ metrics_setting_attributes: {
+ external_dashboard_url: state.externalDashboardUrl,
+ },
+ },
+ })
+ .then(() => dispatch('receiveExternalDashboardUpdateSuccess'))
+ .catch(error => dispatch('receiveExternalDashboardUpdateError', error));
+
+export const receiveExternalDashboardUpdateSuccess = () => {
+ /**
+ * The operations_controller currently handles successful requests
+ * by creating a flash banner messsage to notify the user.
+ */
+ refreshCurrentPage();
+};
+
+export const receiveExternalDashboardUpdateError = (_, error) => {
+ const { response } = error;
+ const message = response.data && response.data.message ? response.data.message : '';
+
+ createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert');
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/operation_settings/store/index.js b/app/assets/javascripts/operation_settings/store/index.js
new file mode 100644
index 00000000000..e96bb1e8aad
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import createState from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export const createStore = initialState =>
+ new Vuex.Store({
+ state: createState(initialState),
+ actions,
+ mutations,
+ });
+
+export default createStore;
diff --git a/app/assets/javascripts/operation_settings/store/mutation_types.js b/app/assets/javascripts/operation_settings/store/mutation_types.js
new file mode 100644
index 00000000000..237d2b6122f
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/mutation_types.js
@@ -0,0 +1,3 @@
+/* eslint-disable import/prefer-default-export */
+
+export const SET_EXTERNAL_DASHBOARD_URL = 'SET_EXTERNAL_DASHBOARD_URL';
diff --git a/app/assets/javascripts/operation_settings/store/mutations.js b/app/assets/javascripts/operation_settings/store/mutations.js
new file mode 100644
index 00000000000..64bb33bb89f
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/mutations.js
@@ -0,0 +1,7 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_EXTERNAL_DASHBOARD_URL](state, url) {
+ state.externalDashboardUrl = url;
+ },
+};
diff --git a/app/assets/javascripts/operation_settings/store/state.js b/app/assets/javascripts/operation_settings/store/state.js
new file mode 100644
index 00000000000..72167141c48
--- /dev/null
+++ b/app/assets/javascripts/operation_settings/store/state.js
@@ -0,0 +1,5 @@
+export default (initialState = {}) => ({
+ externalDashboardUrl: initialState.externalDashboardUrl || '',
+ operationsSettingsEndpoint: initialState.operationsSettingsEndpoint,
+ externalDashboardHelpPagePath: initialState.externalDashboardHelpPagePath,
+});
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index ae0a8c74964..8a5300c9266 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -12,5 +12,6 @@ document.addEventListener('DOMContentLoaded', () => {
saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint,
+ maskableRegex: variableListEl.dataset.maskableRegex,
});
});
diff --git a/app/assets/javascripts/pages/projects/pages_domains/edit/index.js b/app/assets/javascripts/pages/projects/pages_domains/edit/index.js
new file mode 100644
index 00000000000..27e4433ad4d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pages_domains/edit/index.js
@@ -0,0 +1,3 @@
+import initForm from '~/pages/projects/pages_domains/form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/pages_domains/form.js b/app/assets/javascripts/pages/projects/pages_domains/form.js
new file mode 100644
index 00000000000..1d0dbfe0406
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pages_domains/form.js
@@ -0,0 +1,43 @@
+import setupToggleButtons from '~/toggle_buttons';
+
+export default () => {
+ const toggleContainer = document.querySelector('.js-auto-ssl-toggle-container');
+
+ if (toggleContainer) {
+ const onToggleButtonClicked = isAutoSslEnabled => {
+ Array.from(document.querySelectorAll('.js-shown-if-auto-ssl')).forEach(el => {
+ if (isAutoSslEnabled) {
+ el.classList.remove('d-none');
+ } else {
+ el.classList.add('d-none');
+ }
+ });
+
+ Array.from(document.querySelectorAll('.js-shown-unless-auto-ssl')).forEach(el => {
+ if (isAutoSslEnabled) {
+ el.classList.add('d-none');
+ } else {
+ el.classList.remove('d-none');
+ }
+ });
+
+ Array.from(document.querySelectorAll('.js-enabled-if-auto-ssl')).forEach(el => {
+ if (isAutoSslEnabled) {
+ el.removeAttribute('disabled');
+ } else {
+ el.setAttribute('disabled', 'disabled');
+ }
+ });
+
+ Array.from(document.querySelectorAll('.js-enabled-unless-auto-ssl')).forEach(el => {
+ if (isAutoSslEnabled) {
+ el.setAttribute('disabled', 'disabled');
+ } else {
+ el.removeAttribute('disabled');
+ }
+ });
+ };
+
+ setupToggleButtons(toggleContainer, onToggleButtonClicked);
+ }
+};
diff --git a/app/assets/javascripts/pages/projects/pages_domains/new/index.js b/app/assets/javascripts/pages/projects/pages_domains/new/index.js
new file mode 100644
index 00000000000..27e4433ad4d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pages_domains/new/index.js
@@ -0,0 +1,3 @@
+import initForm from '~/pages/projects/pages_domains/form';
+
+document.addEventListener('DOMContentLoaded', initForm);
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index bd4309e47ad..bb490919a9a 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
@@ -29,7 +29,7 @@ export default {
// The text input is editable when there's a custom interval, or when it's
// a preset interval and the user clicks the 'custom' radio button
isEditable() {
- return !!(this.customInputEnabled || !this.intervalIsPreset);
+ return Boolean(this.customInputEnabled || !this.intervalIsPreset);
},
},
watch: {
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index b288989b252..f0d529758d5 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -39,6 +39,11 @@ export default class Project {
$label.text(activeText);
});
+ $('#modal-geo-info').data({
+ cloneUrlSecondary: $this.attr('href'),
+ cloneUrlPrimary: $this.data('primaryUrl') || '',
+ });
+
if (mobileCloneField) {
mobileCloneField.dataset.clipboardText = url;
} else {
@@ -67,6 +72,13 @@ export default class Project {
.remove();
return e.preventDefault();
});
+ $('.hide-shared-runner-limit-message').on('click', function(e) {
+ var $alert = $(this).parents('.shared-runner-quota-message');
+ var scope = $alert.data('scope');
+ Cookies.set('hide_shared_runner_quota_message', 'false', { path: scope });
+ $alert.remove();
+ e.preventDefault();
+ });
$('.hide-auto-devops-implicitly-enabled-banner').on('click', function(e) {
const projectId = $(this).data('project-id');
const cookieKey = `hide_auto_devops_implicitly_enabled_banner_${projectId}`;
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index 15c6fb550c1..885247335a4 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -21,6 +21,7 @@ document.addEventListener('DOMContentLoaded', () => {
saveButton: variableListEl.querySelector('.js-ci-variables-save-button'),
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint,
+ maskableRegex: variableListEl.dataset.maskableRegex,
});
// hide extra auto devops settings based checkbox state
diff --git a/app/assets/javascripts/pages/projects/settings/operations/show/index.js b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
index 5270a7924ec..98e19705976 100644
--- a/app/assets/javascripts/pages/projects/settings/operations/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
@@ -1,7 +1,9 @@
import mountErrorTrackingForm from '~/error_tracking_settings';
import mountOperationSettings from '~/operation_settings';
+import initSettingsPanels from '~/settings_panels';
document.addEventListener('DOMContentLoaded', () => {
mountErrorTrackingForm();
mountOperationSettings();
+ initSettingsPanels();
});
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 19d9903c988..dea7c586868 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
@@ -175,11 +175,6 @@ export default {
if (value === 0) toggleHiddenClassBySelector('.merge-requests-feature', true);
else if (oldValue === 0) toggleHiddenClassBySelector('.merge-requests-feature', false);
},
-
- buildsAccessLevel(value, oldValue) {
- if (value === 0) toggleHiddenClassBySelector('.builds-feature', true);
- else if (oldValue === 0) toggleHiddenClassBySelector('.builds-feature', false);
- },
},
methods: {
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index e1a3f42a71f..3f5a3e15c2c 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import LengthValidator from './length_validator';
import UsernameValidator from './username_validator';
import NoEmojiValidator from '../../../emoji/no_emoji_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer';
@@ -6,6 +7,7 @@ import OAuthRememberMe from './oauth_remember_me';
import preserveUrlFragment from './preserve_url_fragment';
document.addEventListener('DOMContentLoaded', () => {
+ new LengthValidator(); // eslint-disable-line no-new
new UsernameValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/sessions/new/length_validator.js b/app/assets/javascripts/pages/sessions/new/length_validator.js
new file mode 100644
index 00000000000..3d687ca08cc
--- /dev/null
+++ b/app/assets/javascripts/pages/sessions/new/length_validator.js
@@ -0,0 +1,32 @@
+import InputValidator from '../../../validators/input_validator';
+
+const errorMessageClass = 'gl-field-error';
+
+export default class LengthValidator extends InputValidator {
+ constructor(opts = {}) {
+ super();
+
+ const container = opts.container || '';
+ const validateLengthElements = document.querySelectorAll(`${container} .js-validate-length`);
+
+ validateLengthElements.forEach(element =>
+ element.addEventListener('input', this.eventHandler.bind(this)),
+ );
+ }
+
+ eventHandler(event) {
+ this.inputDomElement = event.target;
+ this.inputErrorMessage = this.inputDomElement.parentElement.querySelector(
+ `.${errorMessageClass}`,
+ );
+
+ const { value } = this.inputDomElement;
+ const { maxLengthMessage, maxLength } = this.inputDomElement.dataset;
+
+ this.errorMessage = maxLengthMessage;
+
+ this.invalidInput = value.length > parseInt(maxLength, 10);
+
+ this.setValidationStateAndMessage();
+ }
+}
diff --git a/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
index 482898b80c4..ebd7a17040a 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
@@ -69,7 +69,9 @@ export default {
>
<ci-icon :status="group.status" />
- <span class="ci-status-text"> {{ group.name }} </span>
+ <span class="ci-status-text text-truncate mw-70p gl-pl-1 d-inline-block align-bottom">
+ {{ group.name }}
+ </span>
<span class="dropdown-counter-badge"> {{ group.size }} </span>
</button>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_name_component.vue b/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
index 02451839330..7125790ac3d 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_name_component.vue
@@ -25,7 +25,7 @@ export default {
};
</script>
<template>
- <span class="ci-job-name-component">
+ <span class="ci-job-name-component mw-100">
<ci-icon :status="status" />
<span class="ci-status-text text-truncate mw-70p gl-pl-1 d-inline-block align-bottom">
{{ name }}
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index b2e365e5cde..f3a71ee434c 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -83,6 +83,8 @@ export default {
v-if="shouldRenderContent"
:status="status"
:item-id="pipeline.id"
+ :item-iid="pipeline.iid"
+ :item-id-tooltip="__('Pipeline ID (IID)')"
:time="pipeline.created_at"
:user="pipeline.user"
:actions="actions"
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index c41ecab1294..00c02e15562 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -2,6 +2,7 @@
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import _ from 'underscore';
import { __, sprintf } from '~/locale';
+import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import popover from '~/vue_shared/directives/popover';
@@ -19,6 +20,7 @@ export default {
components: {
UserAvatarLink,
GlLink,
+ PipelineLink,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -59,10 +61,13 @@ export default {
};
</script>
<template>
- <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags">
- <gl-link :href="pipeline.path" class="js-pipeline-url-link">
- <span class="pipeline-id">#{{ pipeline.id }}</span>
- </gl-link>
+ <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags section-wrap">
+ <pipeline-link
+ :href="pipeline.path"
+ :pipeline-id="pipeline.id"
+ :pipeline-iid="pipeline.iid"
+ class="js-pipeline-url-link"
+ />
<div class="label-container">
<span
v-if="pipeline.flags.latest"
diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js
index 59c13e1a042..f0d9642a2b2 100644
--- a/app/assets/javascripts/profile/account/index.js
+++ b/app/assets/javascripts/profile/account/index.js
@@ -35,7 +35,7 @@ export default () => {
return createElement('delete-account-modal', {
props: {
actionUrl: deleteAccountModalEl.dataset.actionUrl,
- confirmWithPassword: !!deleteAccountModalEl.dataset.confirmWithPassword,
+ confirmWithPassword: Boolean(deleteAccountModalEl.dataset.confirmWithPassword),
username: deleteAccountModalEl.dataset.username,
},
});
diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js
index 6e3800021b4..8dd37aee7e1 100644
--- a/app/assets/javascripts/profile/profile.js
+++ b/app/assets/javascripts/profile/profile.js
@@ -39,6 +39,7 @@ export default class Profile {
bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
+ $('.js-group-notification-email').on('change', this.submitForm);
$('#user_notification_email').on('change', this.submitForm);
$('#user_notified_of_own_activity').on('change', this.submitForm);
this.form.on('submit', this.onSubmitForm);
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js
index 4834a856271..f05ad7773a2 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js
@@ -57,7 +57,7 @@ export const validateProjectBilling = ({ dispatch, commit, state }) =>
resp => {
const { billingEnabled } = resp.result;
- commit(types.SET_PROJECT_BILLING_STATUS, !!billingEnabled);
+ commit(types.SET_PROJECT_BILLING_STATUS, Boolean(billingEnabled));
dispatch('setIsValidatingProjectBilling', false);
resolve();
},
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js
index e39f02d0894..f9e2e2f74fb 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js
@@ -1,3 +1,3 @@
-export const hasProject = state => !!state.selectedProject.projectId;
-export const hasZone = state => !!state.selectedZone;
-export const hasMachineType = state => !!state.selectedMachineType;
+export const hasProject = state => Boolean(state.selectedProject.projectId);
+export const hasZone = state => Boolean(state.selectedZone);
+export const hasMachineType = state => Boolean(state.selectedMachineType);
diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js
index 1ac699c538f..8ace6657ad1 100644
--- a/app/assets/javascripts/registry/stores/mutations.js
+++ b/app/assets/javascripts/registry/stores/mutations.js
@@ -9,7 +9,7 @@ export default {
[types.SET_REPOS_LIST](state, list) {
Object.assign(state, {
repos: list.map(el => ({
- canDelete: !!el.destroy_path,
+ canDelete: Boolean(el.destroy_path),
destroyPath: el.destroy_path,
id: el.id,
isLoading: false,
@@ -42,7 +42,7 @@ export default {
location: element.location,
createdAt: element.created_at,
destroyPath: element.destroy_path,
- canDelete: !!element.destroy_path,
+ canDelete: Boolean(element.destroy_path),
}));
},
diff --git a/app/assets/javascripts/reports/components/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue
index 50f2910e02d..ee07efea3b0 100644
--- a/app/assets/javascripts/reports/components/issues_list.vue
+++ b/app/assets/javascripts/reports/components/issues_list.vue
@@ -52,11 +52,21 @@ export default {
required: false,
default: '',
},
- showReportSectionStatus: {
+ showReportSectionStatusIcon: {
type: Boolean,
required: false,
default: true,
},
+ issuesUlElementClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ issueItemClass: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
issuesWithState() {
@@ -67,6 +77,9 @@ export default {
...this.resolvedIssues.map(wrapIssueWithState(STATUS_SUCCESS)),
];
},
+ wclass() {
+ return `report-block-list ${this.issuesUlElementClass}`;
+ },
},
};
</script>
@@ -77,7 +90,7 @@ export default {
:size="$options.typicalReportItemHeight"
class="report-block-container"
wtag="ul"
- wclass="report-block-list"
+ :wclass="wclass"
>
<report-item
v-for="(wrapped, index) in issuesWithState"
@@ -86,7 +99,8 @@ export default {
:status="wrapped.status"
:component="component"
:is-new="wrapped.isNew"
- :show-report-section-status="showReportSectionStatus"
+ :show-report-section-status-icon="showReportSectionStatusIcon"
+ :class="issueItemClass"
/>
</smart-virtual-list>
</template>
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index 241185e3126..3d576caaf8f 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -3,10 +3,7 @@ import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import Popover from '~/vue_shared/components/help_popover.vue';
import IssuesList from './issues_list.vue';
-
-const LOADING = 'LOADING';
-const ERROR = 'ERROR';
-const SUCCESS = 'SUCCESS';
+import { status } from '../constants';
export default {
name: 'ReportSection',
@@ -42,7 +39,8 @@ export default {
},
successText: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
unresolvedIssues: {
type: Array,
@@ -78,6 +76,21 @@ export default {
required: false,
default: true,
},
+ issuesUlElementClass: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ issuesListContainerClass: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
+ issueItemClass: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
},
data() {
@@ -91,13 +104,13 @@ export default {
return this.isCollapsed ? __('Expand') : __('Collapse');
},
isLoading() {
- return this.status === LOADING;
+ return this.status === status.LOADING;
},
loadingFailed() {
- return this.status === ERROR;
+ return this.status === status.ERROR;
},
isSuccess() {
- return this.status === SUCCESS;
+ return this.status === status.SUCCESS;
},
isCollapsible() {
return !this.alwaysOpen && this.hasIssues;
@@ -132,6 +145,15 @@ export default {
hasPopover() {
return Object.keys(this.popoverOptions).length > 0;
},
+ slotName() {
+ if (this.isSuccess) {
+ return 'success';
+ } else if (this.isLoading) {
+ return 'loading';
+ }
+
+ return 'error';
+ },
},
methods: {
toggleCollapsed() {
@@ -147,6 +169,7 @@ export default {
<div class="media-body d-flex flex-align-self-center">
<span class="js-code-text code-text">
{{ headerText }}
+ <slot :name="slotName"></slot>
<popover v-if="hasPopover" :options="popoverOptions" class="prepend-left-5" />
</span>
@@ -172,6 +195,9 @@ export default {
:neutral-issues="neutralIssues"
:component="component"
:show-report-section-status-icon="showReportSectionStatusIcon"
+ :issues-ul-element-class="issuesUlElementClass"
+ :class="issuesListContainerClass"
+ :issue-item-class="issueItemClass"
/>
</slot>
</div>
diff --git a/app/assets/javascripts/reports/constants.js b/app/assets/javascripts/reports/constants.js
index c323dc543f3..66ac1af062b 100644
--- a/app/assets/javascripts/reports/constants.js
+++ b/app/assets/javascripts/reports/constants.js
@@ -16,3 +16,9 @@ export const STATUS_NEUTRAL = 'neutral';
export const ICON_WARNING = 'warning';
export const ICON_SUCCESS = 'success';
export const ICON_NOTFOUND = 'notfound';
+
+export const status = {
+ LOADING: 'LOADING',
+ ERROR: 'ERROR',
+ SUCCESS: 'SUCCESS',
+};
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
new file mode 100644
index 00000000000..6eca015036f
--- /dev/null
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -0,0 +1,61 @@
+<script>
+import getRefMixin from '../mixins/get_ref';
+import getProjectShortPath from '../queries/getProjectShortPath.graphql';
+
+export default {
+ apollo: {
+ projectShortPath: {
+ query: getProjectShortPath,
+ },
+ },
+ mixins: [getRefMixin],
+ props: {
+ currentPath: {
+ type: String,
+ required: false,
+ default: '/',
+ },
+ },
+ data() {
+ return {
+ projectShortPath: '',
+ };
+ },
+ computed: {
+ pathLinks() {
+ return this.currentPath
+ .split('/')
+ .filter(p => p !== '')
+ .reduce(
+ (acc, name, i) => {
+ const path = `${i > 0 ? acc[i].path : ''}/${name}`;
+
+ return acc.concat({
+ name,
+ path,
+ to: `/tree/${this.ref}${path}`,
+ });
+ },
+ [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}` }],
+ );
+ },
+ },
+ methods: {
+ isLast(i) {
+ return i === this.pathLinks.length - 1;
+ },
+ },
+};
+</script>
+
+<template>
+ <nav :aria-label="__('Files breadcrumb')">
+ <ol class="breadcrumb repo-breadcrumb">
+ <li v-for="(link, i) in pathLinks" :key="i" class="breadcrumb-item">
+ <router-link :to="link.to" :aria-current="isLast(i) ? 'page' : null">
+ {{ link.name }}
+ </router-link>
+ </li>
+ </ol>
+ </nav>
+</template>
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index ad3d8f9329d..0357a0e44c3 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -1,27 +1,27 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
+import createFlash from '~/flash';
import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref';
import getFiles from '../../queries/getFiles.graphql';
+import getProjectPath from '../../queries/getProjectPath.graphql';
import TableHeader from './header.vue';
import TableRow from './row.vue';
+import ParentRow from './parent_row.vue';
+
+const PAGE_SIZE = 100;
export default {
components: {
GlLoadingIcon,
TableHeader,
TableRow,
+ ParentRow,
},
mixins: [getRefMixin],
apollo: {
- files: {
- query: getFiles,
- variables() {
- return {
- ref: this.ref,
- path: this.path,
- };
- },
+ projectPath: {
+ query: getProjectPath,
},
},
props: {
@@ -32,7 +32,14 @@ export default {
},
data() {
return {
- files: [],
+ projectPath: '',
+ nextPageCursor: '',
+ entries: {
+ trees: [],
+ submodules: [],
+ blobs: [],
+ },
+ isLoadingFiles: false,
};
},
computed: {
@@ -42,8 +49,66 @@ export default {
{ path: this.path, ref: this.ref },
);
},
- isLoadingFiles() {
- return this.$apollo.queries.files.loading;
+ showParentRow() {
+ return !this.isLoadingFiles && ['', '/'].indexOf(this.path) === -1;
+ },
+ },
+ watch: {
+ $route: function routeChange() {
+ this.entries.trees = [];
+ this.entries.submodules = [];
+ this.entries.blobs = [];
+ this.nextPageCursor = '';
+ this.fetchFiles();
+ },
+ },
+ mounted() {
+ // We need to wait for `ref` and `projectPath` to be set
+ this.$nextTick(() => this.fetchFiles());
+ },
+ methods: {
+ fetchFiles() {
+ this.isLoadingFiles = true;
+
+ return this.$apollo
+ .query({
+ query: getFiles,
+ variables: {
+ projectPath: this.projectPath,
+ ref: this.ref,
+ path: this.path,
+ nextPageCursor: this.nextPageCursor,
+ pageSize: PAGE_SIZE,
+ },
+ })
+ .then(({ data }) => {
+ if (!data) return;
+
+ const pageInfo = this.hasNextPage(data.project.repository.tree);
+
+ this.isLoadingFiles = false;
+ this.entries = Object.keys(this.entries).reduce(
+ (acc, key) => ({
+ ...acc,
+ [key]: this.normalizeData(key, data.project.repository.tree[key].edges),
+ }),
+ {},
+ );
+
+ if (pageInfo && pageInfo.hasNextPage) {
+ this.nextPageCursor = pageInfo.endCursor;
+ this.fetchFiles();
+ }
+ })
+ .catch(() => createFlash(__('An error occurred while fetching folder content.')));
+ },
+ normalizeData(key, data) {
+ return this.entries[key].concat(data.map(({ node }) => node));
+ },
+ hasNextPage(data) {
+ return []
+ .concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
+ .find(({ hasNextPage }) => hasNextPage);
},
},
};
@@ -58,18 +123,24 @@ export default {
tableCaption
}}
</caption>
- <table-header />
+ <table-header v-once />
<tbody>
- <table-row
- v-for="entry in files"
- :id="entry.id"
- :key="entry.id"
- :path="entry.flatPath"
- :type="entry.type"
- />
+ <parent-row v-show="showParentRow" :commit-ref="ref" :path="path" />
+ <template v-for="val in entries">
+ <table-row
+ v-for="entry in val"
+ :id="entry.id"
+ :key="`${entry.flatPath}-${entry.id}`"
+ :current-path="path"
+ :path="entry.flatPath"
+ :type="entry.type"
+ :url="entry.webUrl"
+ :lfs-oid="entry.lfsOid"
+ />
+ </template>
</tbody>
</table>
- <gl-loading-icon v-if="isLoadingFiles" class="my-3" size="md" />
+ <gl-loading-icon v-show="isLoadingFiles" class="my-3" size="md" />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/repository/components/table/parent_row.vue b/app/assets/javascripts/repository/components/table/parent_row.vue
new file mode 100644
index 00000000000..3c39f404226
--- /dev/null
+++ b/app/assets/javascripts/repository/components/table/parent_row.vue
@@ -0,0 +1,37 @@
+<script>
+export default {
+ props: {
+ commitRef: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ parentRoute() {
+ const splitArray = this.path.split('/');
+ splitArray.pop();
+
+ return { path: `/tree/${this.commitRef}/${splitArray.join('/')}` };
+ },
+ },
+ methods: {
+ clickRow() {
+ this.$router.push(this.parentRoute);
+ },
+ },
+};
+</script>
+
+<template>
+ <tr class="tree-item">
+ <td colspan="3" class="tree-item-file-name" @click.self="clickRow">
+ <router-link :to="parentRoute" :aria-label="__('Go to parent')">
+ ..
+ </router-link>
+ </td>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 0ad0fdbd605..e24a5e2c447 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -1,12 +1,20 @@
<script>
+import { GlBadge } from '@gitlab/ui';
import { getIconName } from '../../utils/icon';
import getRefMixin from '../../mixins/get_ref';
export default {
+ components: {
+ GlBadge,
+ },
mixins: [getRefMixin],
props: {
id: {
- type: Number,
+ type: String,
+ required: true,
+ },
+ currentPath: {
+ type: String,
required: true,
},
path: {
@@ -17,6 +25,16 @@ export default {
type: String,
required: true,
},
+ url: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ lfsOid: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
routerLinkTo() {
@@ -26,7 +44,7 @@ export default {
return `fa-${getIconName(this.type, this.path)}`;
},
isFolder() {
- return this.type === 'folder';
+ return this.type === 'tree';
},
isSubmodule() {
return this.type === 'commit';
@@ -34,6 +52,12 @@ export default {
linkComponent() {
return this.isFolder ? 'router-link' : 'a';
},
+ fullPath() {
+ return this.path.replace(new RegExp(`^${this.currentPath}/`), '');
+ },
+ shortSha() {
+ return this.id.slice(0, 8);
+ },
},
methods: {
openRow() {
@@ -49,9 +73,14 @@ export default {
<tr v-once :class="`file_${id}`" class="tree-item" @click="openRow">
<td class="tree-item-file-name">
<i :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i>
- <component :is="linkComponent" :to="routerLinkTo" class="str-truncated">{{ path }}</component>
+ <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>
<template v-if="isSubmodule">
- @ <a href="#" class="commit-sha">{{ id }}</a>
+ @ <a href="#" class="commit-sha">{{ shortSha }}</a>
</template>
</td>
<td class="d-none d-sm-table-cell tree-commit"></td>
diff --git a/app/assets/javascripts/repository/fragmentTypes.json b/app/assets/javascripts/repository/fragmentTypes.json
new file mode 100644
index 00000000000..949ebca432b
--- /dev/null
+++ b/app/assets/javascripts/repository/fragmentTypes.json
@@ -0,0 +1 @@
+{"__schema":{"types":[{"kind":"INTERFACE","name":"Entry","possibleTypes":[{"name":"Blob"},{"name":"Submodule"},{"name":"TreeEntry"}]}]}}
diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js
index c85db5c01e5..c64d16ef02a 100644
--- a/app/assets/javascripts/repository/graphql.js
+++ b/app/assets/javascripts/repository/graphql.js
@@ -1,45 +1,42 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql';
+import introspectionQueryResultData from './fragmentTypes.json';
Vue.use(VueApollo);
-const defaultClient = createDefaultClient({
- Query: {
- files() {
- return [
- {
- __typename: 'file',
- id: 1,
- name: 'app',
- flatPath: 'app',
- type: 'folder',
- },
- {
- __typename: 'file',
- id: 2,
- name: 'gitlab-svg',
- flatPath: 'gitlab-svg',
- type: 'commit',
- },
- {
- __typename: 'file',
- id: 3,
- name: 'index.js',
- flatPath: 'index.js',
- type: 'blob',
- },
- {
- __typename: 'file',
- id: 4,
- name: 'test.pdf',
- flatPath: 'fixtures/test.pdf',
- type: 'blob',
- },
- ];
+// We create a fragment matcher so that we can create a fragment from an interface
+// Without this, Apollo throws a heuristic fragment matcher warning
+const fragmentMatcher = new IntrospectionFragmentMatcher({
+ introspectionQueryResultData,
+});
+
+const defaultClient = createDefaultClient(
+ {},
+ {
+ cacheConfig: {
+ fragmentMatcher,
+ dataIdFromObject: obj => {
+ // eslint-disable-next-line no-underscore-dangle
+ switch (obj.__typename) {
+ // We need to create a dynamic ID for each entry
+ // Each entry can have the same ID as the ID is a commit ID
+ // So we create a unique cache ID with the path and the ID
+ case 'TreeEntry':
+ case 'Submodule':
+ case 'Blob':
+ return `${obj.flatPath}-${obj.id}`;
+ default:
+ // If the type doesn't match any of the above we fallback
+ // to using the default Apollo ID
+ // eslint-disable-next-line no-underscore-dangle
+ return obj.id || obj._id;
+ }
+ },
},
},
-});
+);
export default new VueApollo({
defaultClient,
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 00b69362312..52f53be045b 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -1,22 +1,56 @@
import Vue from 'vue';
import createRouter from './router';
import App from './components/app.vue';
+import Breadcrumbs from './components/breadcrumbs.vue';
import apolloProvider from './graphql';
+import { setTitle } from './utils/title';
export default function setupVueRepositoryList() {
const el = document.getElementById('js-tree-list');
- const { projectPath, ref } = el.dataset;
+ const { projectPath, projectShortPath, ref, fullName } = el.dataset;
+ const router = createRouter(projectPath, ref);
apolloProvider.clients.defaultClient.cache.writeData({
data: {
projectPath,
+ projectShortPath,
ref,
},
});
+ router.afterEach(({ params: { pathMatch } }) => {
+ const isRoot = pathMatch === undefined || pathMatch === '/';
+
+ setTitle(pathMatch, ref, fullName);
+
+ if (!isRoot) {
+ document
+ .querySelectorAll('.js-keep-hidden-on-navigation')
+ .forEach(elem => elem.classList.add('hidden'));
+ }
+
+ document
+ .querySelectorAll('.js-hide-on-navigation')
+ .forEach(elem => elem.classList.toggle('hidden', !isRoot));
+ });
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: document.getElementById('js-repo-breadcrumb'),
+ router,
+ apolloProvider,
+ render(h) {
+ return h(Breadcrumbs, {
+ props: {
+ currentPath: this.$route.params.pathMatch,
+ },
+ });
+ },
+ });
+
return new Vue({
el,
- router: createRouter(projectPath, ref),
+ router,
apolloProvider,
render(h) {
return h(App);
diff --git a/app/assets/javascripts/repository/queries/getFiles.graphql b/app/assets/javascripts/repository/queries/getFiles.graphql
index fb446780ed1..ef924fde556 100644
--- a/app/assets/javascripts/repository/queries/getFiles.graphql
+++ b/app/assets/javascripts/repository/queries/getFiles.graphql
@@ -1,7 +1,58 @@
-query getFiles($path: String!, $ref: String!) {
- files(path: $path, ref: $ref) @client {
- id
- flatPath
- type
+fragment TreeEntry on Entry {
+ id
+ flatPath
+ type
+}
+
+fragment PageInfo on PageInfo {
+ hasNextPage
+ endCursor
+}
+
+query getFiles(
+ $projectPath: ID!
+ $path: String
+ $ref: String!
+ $pageSize: Int!
+ $nextPageCursor: String
+) {
+ project(fullPath: $projectPath) {
+ repository {
+ tree(path: $path, ref: $ref) {
+ trees(first: $pageSize, after: $nextPageCursor) {
+ edges {
+ node {
+ ...TreeEntry
+ webUrl
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ submodules(first: $pageSize, after: $nextPageCursor) {
+ edges {
+ node {
+ ...TreeEntry
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ blobs(first: $pageSize, after: $nextPageCursor) {
+ edges {
+ node {
+ ...TreeEntry
+ webUrl
+ lfsOid
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ }
+ }
}
}
diff --git a/app/assets/javascripts/repository/queries/getProjectPath.graphql b/app/assets/javascripts/repository/queries/getProjectPath.graphql
new file mode 100644
index 00000000000..74e73e07577
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getProjectPath.graphql
@@ -0,0 +1,3 @@
+query getProjectPath {
+ projectPath
+}
diff --git a/app/assets/javascripts/repository/queries/getProjectShortPath.graphql b/app/assets/javascripts/repository/queries/getProjectShortPath.graphql
new file mode 100644
index 00000000000..34eb26598c2
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getProjectShortPath.graphql
@@ -0,0 +1,3 @@
+query getProjectShortPath {
+ projectShortPath @client
+}
diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js
index b42a96a4ee2..9322c81ab97 100644
--- a/app/assets/javascripts/repository/router.js
+++ b/app/assets/javascripts/repository/router.js
@@ -12,24 +12,17 @@ export default function createRouter(base, baseRef) {
base: joinPaths(gon.relative_url_root || '', base),
routes: [
{
- path: '/',
- name: 'projectRoot',
- component: IndexPage,
- },
- {
path: `/tree/${baseRef}(/.*)?`,
name: 'treePath',
component: TreePage,
props: route => ({
- path: route.params.pathMatch,
+ path: route.params.pathMatch && route.params.pathMatch.replace(/^\//, ''),
}),
- beforeEnter(to, from, next) {
- document
- .querySelectorAll('.js-hide-on-navigation')
- .forEach(el => el.classList.add('hidden'));
-
- next();
- },
+ },
+ {
+ path: '/',
+ name: 'projectRoot',
+ component: IndexPage,
},
],
});
diff --git a/app/assets/javascripts/repository/utils/icon.js b/app/assets/javascripts/repository/utils/icon.js
index 3e93ff0ec39..661ebb6edfc 100644
--- a/app/assets/javascripts/repository/utils/icon.js
+++ b/app/assets/javascripts/repository/utils/icon.js
@@ -1,5 +1,5 @@
const entryTypeIcons = {
- folder: 'folder',
+ tree: 'folder',
commit: 'archive',
};
diff --git a/app/assets/javascripts/repository/utils/title.js b/app/assets/javascripts/repository/utils/title.js
new file mode 100644
index 00000000000..4e194640e92
--- /dev/null
+++ b/app/assets/javascripts/repository/utils/title.js
@@ -0,0 +1,9 @@
+// eslint-disable-next-line import/prefer-default-export
+export const setTitle = (pathMatch, ref, project) => {
+ if (!pathMatch) return;
+
+ const path = pathMatch.replace(/^\//, '');
+ const isEmpty = path === '';
+
+ document.title = `${isEmpty ? 'Files' : path} · ${ref} · ${project}`;
+};
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 72e061df573..930c0d5e958 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -82,9 +82,9 @@ Sidebar.prototype.toggleTodo = function(e) {
ajaxType = $this.data('deletePath') ? 'delete' : 'post';
if ($this.data('deletePath')) {
- url = '' + $this.data('deletePath');
+ url = String($this.data('deletePath'));
} else {
- url = '' + $this.data('createPath');
+ url = String($this.data('createPath'));
}
$this.tooltip('hide');
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index ab43c2139bf..6aca4067ba7 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -379,7 +379,7 @@ export class SearchAutocomplete {
}
}
}
- this.wrap.toggleClass('has-value', !!e.target.value);
+ this.wrap.toggleClass('has-value', Boolean(e.target.value));
}
onSearchInputFocus() {
@@ -396,7 +396,7 @@ export class SearchAutocomplete {
onClearInputClick(e) {
e.preventDefault();
- this.wrap.toggleClass('has-value', !!e.target.value);
+ this.wrap.toggleClass('has-value', Boolean(e.target.value));
return this.searchInput.val('').focus();
}
@@ -405,7 +405,7 @@ export class SearchAutocomplete {
this.wrap.removeClass('search-active');
// If input is blank then restore state
if (this.searchInput.val() === '') {
- return this.restoreOriginalState();
+ this.restoreOriginalState();
}
this.dropdownMenu.removeClass('show');
}
diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue
index f9b4e789563..94341050b86 100644
--- a/app/assets/javascripts/serverless/components/functions.vue
+++ b/app/assets/javascripts/serverless/components/functions.vue
@@ -4,6 +4,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
import FunctionRow from './function_row.vue';
import EnvironmentRow from './environment_row.vue';
import EmptyState from './empty_state.vue';
+import { CHECKING_INSTALLED } from '../constants';
export default {
components: {
@@ -13,10 +14,6 @@ export default {
GlLoadingIcon,
},
props: {
- installed: {
- type: Boolean,
- required: true,
- },
clustersPath: {
type: String,
required: true,
@@ -31,8 +28,15 @@ export default {
},
},
computed: {
- ...mapState(['isLoading', 'hasFunctionData']),
+ ...mapState(['installed', 'isLoading', 'hasFunctionData']),
...mapGetters(['getFunctions']),
+
+ checkingInstalled() {
+ return this.installed === CHECKING_INSTALLED;
+ },
+ isInstalled() {
+ return this.installed === true;
+ },
},
created() {
this.fetchFunctions({
@@ -47,15 +51,16 @@ export default {
<template>
<section id="serverless-functions">
- <div v-if="installed">
+ <gl-loading-icon
+ v-if="checkingInstalled"
+ :size="2"
+ class="prepend-top-default append-bottom-default"
+ />
+
+ <div v-else-if="isInstalled">
<div v-if="hasFunctionData">
- <gl-loading-icon
- v-if="isLoading"
- :size="2"
- class="prepend-top-default append-bottom-default"
- />
- <template v-else>
- <div class="groups-list-tree-container">
+ <template>
+ <div class="groups-list-tree-container js-functions-wrapper">
<ul class="content-list group-list-tree">
<environment-row
v-for="(env, index) in getFunctions"
@@ -66,6 +71,11 @@ export default {
</ul>
</div>
</template>
+ <gl-loading-icon
+ v-if="isLoading"
+ :size="2"
+ class="prepend-top-default append-bottom-default js-functions-loader"
+ />
</div>
<div v-else class="empty-state js-empty-state">
<div class="text-content">
diff --git a/app/assets/javascripts/serverless/constants.js b/app/assets/javascripts/serverless/constants.js
index 35f77205f2c..2fa15e56ccb 100644
--- a/app/assets/javascripts/serverless/constants.js
+++ b/app/assets/javascripts/serverless/constants.js
@@ -1,3 +1,7 @@
export const MAX_REQUESTS = 3; // max number of times to retry
export const X_INTERVAL = 5; // Reflects the number of verticle bars on the x-axis
+
+export const CHECKING_INSTALLED = 'checking'; // The backend is still determining whether or not Knative is installed
+
+export const TIMEOUT = 'timeout';
diff --git a/app/assets/javascripts/serverless/serverless_bundle.js b/app/assets/javascripts/serverless/serverless_bundle.js
index 2d3f086ffee..ed3b633d766 100644
--- a/app/assets/javascripts/serverless/serverless_bundle.js
+++ b/app/assets/javascripts/serverless/serverless_bundle.js
@@ -45,7 +45,7 @@ export default class Serverless {
},
});
} else {
- const { statusPath, clustersPath, helpPath, installed } = document.querySelector(
+ const { statusPath, clustersPath, helpPath } = document.querySelector(
'.js-serverless-functions-page',
).dataset;
@@ -56,7 +56,6 @@ export default class Serverless {
render(createElement) {
return createElement(Functions, {
props: {
- installed: installed !== undefined,
clustersPath,
helpPath,
statusPath,
diff --git a/app/assets/javascripts/serverless/store/actions.js b/app/assets/javascripts/serverless/store/actions.js
index 826501c9022..a0a9fdf7ace 100644
--- a/app/assets/javascripts/serverless/store/actions.js
+++ b/app/assets/javascripts/serverless/store/actions.js
@@ -3,13 +3,18 @@ import axios from '~/lib/utils/axios_utils';
import statusCodes from '~/lib/utils/http_status';
import { backOff } from '~/lib/utils/common_utils';
import createFlash from '~/flash';
-import { MAX_REQUESTS } from '../constants';
+import { __ } from '~/locale';
+import { MAX_REQUESTS, CHECKING_INSTALLED, TIMEOUT } from '../constants';
export const requestFunctionsLoading = ({ commit }) => commit(types.REQUEST_FUNCTIONS_LOADING);
export const receiveFunctionsSuccess = ({ commit }, data) =>
commit(types.RECEIVE_FUNCTIONS_SUCCESS, data);
-export const receiveFunctionsNoDataSuccess = ({ commit }) =>
- commit(types.RECEIVE_FUNCTIONS_NODATA_SUCCESS);
+export const receiveFunctionsPartial = ({ commit }, data) =>
+ commit(types.RECEIVE_FUNCTIONS_PARTIAL, data);
+export const receiveFunctionsTimeout = ({ commit }, data) =>
+ commit(types.RECEIVE_FUNCTIONS_TIMEOUT, data);
+export const receiveFunctionsNoDataSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_FUNCTIONS_NODATA_SUCCESS, data);
export const receiveFunctionsError = ({ commit }, error) =>
commit(types.RECEIVE_FUNCTIONS_ERROR, error);
@@ -25,18 +30,25 @@ export const receiveMetricsError = ({ commit }, error) =>
export const fetchFunctions = ({ dispatch }, { functionsPath }) => {
let retryCount = 0;
+ const functionsPartiallyFetched = data => {
+ if (data.functions !== null && data.functions.length) {
+ dispatch('receiveFunctionsPartial', data);
+ }
+ };
+
dispatch('requestFunctionsLoading');
backOff((next, stop) => {
axios
.get(functionsPath)
.then(response => {
- if (response.status === statusCodes.NO_CONTENT) {
+ if (response.data.knative_installed === CHECKING_INSTALLED) {
retryCount += 1;
if (retryCount < MAX_REQUESTS) {
+ functionsPartiallyFetched(response.data);
next();
} else {
- stop(null);
+ stop(TIMEOUT);
}
} else {
stop(response.data);
@@ -45,10 +57,13 @@ export const fetchFunctions = ({ dispatch }, { functionsPath }) => {
.catch(stop);
})
.then(data => {
- if (data !== null) {
+ if (data === TIMEOUT) {
+ dispatch('receiveFunctionsTimeout');
+ createFlash(__('Loading functions timed out. Please reload the page to try again.'));
+ } else if (data.functions !== null && data.functions.length) {
dispatch('receiveFunctionsSuccess', data);
} else {
- dispatch('receiveFunctionsNoDataSuccess');
+ dispatch('receiveFunctionsNoDataSuccess', data);
}
})
.catch(error => {
diff --git a/app/assets/javascripts/serverless/store/mutation_types.js b/app/assets/javascripts/serverless/store/mutation_types.js
index 25b2f7ac38a..b8fa9ea1a01 100644
--- a/app/assets/javascripts/serverless/store/mutation_types.js
+++ b/app/assets/javascripts/serverless/store/mutation_types.js
@@ -1,5 +1,7 @@
export const REQUEST_FUNCTIONS_LOADING = 'REQUEST_FUNCTIONS_LOADING';
export const RECEIVE_FUNCTIONS_SUCCESS = 'RECEIVE_FUNCTIONS_SUCCESS';
+export const RECEIVE_FUNCTIONS_PARTIAL = 'RECEIVE_FUNCTIONS_PARTIAL';
+export const RECEIVE_FUNCTIONS_TIMEOUT = 'RECEIVE_FUNCTIONS_TIMEOUT';
export const RECEIVE_FUNCTIONS_NODATA_SUCCESS = 'RECEIVE_FUNCTIONS_NODATA_SUCCESS';
export const RECEIVE_FUNCTIONS_ERROR = 'RECEIVE_FUNCTIONS_ERROR';
diff --git a/app/assets/javascripts/serverless/store/mutations.js b/app/assets/javascripts/serverless/store/mutations.js
index 991f32a275d..2685a5b11ff 100644
--- a/app/assets/javascripts/serverless/store/mutations.js
+++ b/app/assets/javascripts/serverless/store/mutations.js
@@ -5,12 +5,23 @@ export default {
state.isLoading = true;
},
[types.RECEIVE_FUNCTIONS_SUCCESS](state, data) {
- state.functions = data;
+ state.functions = data.functions;
+ state.installed = data.knative_installed;
state.isLoading = false;
state.hasFunctionData = true;
},
- [types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state) {
+ [types.RECEIVE_FUNCTIONS_PARTIAL](state, data) {
+ state.functions = data.functions;
+ state.installed = true;
+ state.isLoading = true;
+ state.hasFunctionData = true;
+ },
+ [types.RECEIVE_FUNCTIONS_TIMEOUT](state) {
+ state.isLoading = false;
+ },
+ [types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state, data) {
state.isLoading = false;
+ state.installed = data.knative_installed;
state.hasFunctionData = false;
},
[types.RECEIVE_FUNCTIONS_ERROR](state, error) {
diff --git a/app/assets/javascripts/serverless/store/state.js b/app/assets/javascripts/serverless/store/state.js
index afc3f37d7ba..fdd29299749 100644
--- a/app/assets/javascripts/serverless/store/state.js
+++ b/app/assets/javascripts/serverless/store/state.js
@@ -1,5 +1,6 @@
export default () => ({
error: null,
+ installed: 'checking',
isLoading: true,
// functions
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index 10e2c8453e2..35eba266625 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -194,9 +194,9 @@ export default {
v-show="noEmoji"
class="js-no-emoji-placeholder no-emoji-placeholder position-relative"
>
- <icon name="emoji_slightly_smiling_face" css-classes="award-control-icon-neutral" />
- <icon name="emoji_smiley" css-classes="award-control-icon-positive" />
- <icon name="emoji_smile" css-classes="award-control-icon-super-positive" />
+ <icon name="slight-smile" css-classes="award-control-icon-neutral" />
+ <icon name="smiley" css-classes="award-control-icon-positive" />
+ <icon name="smile" css-classes="award-control-icon-super-positive" />
</span>
</button>
</span>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index c03b2a68c78..d84d5344935 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -49,10 +49,10 @@ export default {
},
computed: {
hasTimeSpent() {
- return !!this.timeSpent;
+ return Boolean(this.timeSpent);
},
hasTimeEstimate() {
- return !!this.timeEstimate;
+ return Boolean(this.timeEstimate);
},
showComparisonState() {
return this.hasTimeEstimate && this.hasTimeSpent;
@@ -67,7 +67,7 @@ export default {
return !this.hasTimeEstimate && !this.hasTimeSpent;
},
showHelpState() {
- return !!this.showHelp;
+ return Boolean(this.showHelp);
},
},
created() {
diff --git a/app/assets/javascripts/test_utils/index.js b/app/assets/javascripts/test_utils/index.js
index a55a338eea8..1e75ee60671 100644
--- a/app/assets/javascripts/test_utils/index.js
+++ b/app/assets/javascripts/test_utils/index.js
@@ -1,5 +1,5 @@
-import 'core-js/es6/map';
-import 'core-js/es6/set';
+import 'core-js/es/map';
+import 'core-js/es/set';
import simulateDrag from './simulate_drag';
import simulateInput from './simulate_input';
diff --git a/app/assets/javascripts/usage_ping_consent.js b/app/assets/javascripts/usage_ping_consent.js
index d3d745a3c11..1e7a5fb19c2 100644
--- a/app/assets/javascripts/usage_ping_consent.js
+++ b/app/assets/javascripts/usage_ping_consent.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import Flash, { hideFlash } from './flash';
import { parseBoolean } from './lib/utils/common_utils';
+import { __ } from './locale';
export default () => {
$('body').on('click', '.js-usage-consent-action', e => {
@@ -25,7 +26,7 @@ export default () => {
})
.catch(() => {
hideConsentMessage();
- Flash('Something went wrong. Try again later.');
+ Flash(__('Something went wrong. Try again later.'));
});
});
};
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 8c71615dff2..7e6f02b10af 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -5,7 +5,7 @@
import $ from 'jquery';
import _ from 'underscore';
import axios from './lib/utils/axios_utils';
-import { __ } from './locale';
+import { s__, __, sprintf } from './locale';
import ModalStore from './boards/stores/modal_store';
// TODO: remove eventHub hack after code splitting refactor
@@ -157,14 +157,20 @@ function UsersSelect(currentUser, els, options = {}) {
.get(0);
if (selectedUsers.length === 0) {
- return 'Unassigned';
+ return s__('UsersSelect|Unassigned');
} else if (selectedUsers.length === 1) {
return firstUser.name;
} else if (isSelected) {
const otherSelected = selectedUsers.filter(s => s !== selectedUser.id);
- return `${selectedUser.name} + ${otherSelected.length} more`;
+ return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
+ name: selectedUser.name,
+ length: otherSelected.length,
+ });
} else {
- return `${firstUser.name} + ${selectedUsers.length - 1} more`;
+ return sprintf(s__('UsersSelect|%{name} + %{length} more'), {
+ name: firstUser.name,
+ length: selectedUsers.length - 1,
+ });
}
};
@@ -218,11 +224,11 @@ function UsersSelect(currentUser, els, options = {}) {
tooltipTitle = _.escape(user.name);
} else {
user = {
- name: 'Unassigned',
+ name: s__('UsersSelect|Unassigned'),
username: '',
avatar: '',
};
- tooltipTitle = __('Assignee');
+ tooltipTitle = s__('UsersSelect|Assignee');
}
$value.html(assigneeTemplate(user));
$collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle');
@@ -233,7 +239,11 @@ function UsersSelect(currentUser, els, options = {}) {
'<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>',
);
assigneeTemplate = _.template(
- '<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>',
+ `<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself">
+ ${sprintf(s__('UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}'), {
+ openingTag: '<a href="#" class="js-assign-yourself">',
+ closingTag: '</a>',
+ })}</span> <% } %>`,
);
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
@@ -302,7 +312,7 @@ function UsersSelect(currentUser, els, options = {}) {
showDivider += 1;
users.unshift({
beforeDivider: true,
- name: 'Unassigned',
+ name: s__('UsersSelect|Unassigned'),
id: 0,
});
}
@@ -310,7 +320,7 @@ function UsersSelect(currentUser, els, options = {}) {
showDivider += 1;
name = showAnyUser;
if (name === true) {
- name = 'Any User';
+ name = s__('UsersSelect|Any User');
}
anyUser = {
beforeDivider: true,
@@ -596,7 +606,7 @@ function UsersSelect(currentUser, els, options = {}) {
showEmailUser = $(select).data('emailUser');
firstUser = $(select).data('firstUser');
return $(select).select2({
- placeholder: 'Search for a user',
+ placeholder: __('Search for a user'),
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query: function(query) {
@@ -621,7 +631,7 @@ function UsersSelect(currentUser, els, options = {}) {
}
if (showNullUser) {
nullUser = {
- name: 'Unassigned',
+ name: s__('UsersSelect|Unassigned'),
id: 0,
};
data.results.unshift(nullUser);
@@ -629,7 +639,7 @@ function UsersSelect(currentUser, els, options = {}) {
if (showAnyUser) {
name = showAnyUser;
if (name === true) {
- name = 'Any User';
+ name = s__('UsersSelect|Any User');
}
anyUser = {
name: name,
@@ -645,7 +655,7 @@ function UsersSelect(currentUser, els, options = {}) {
) {
var trimmed = query.term.trim();
emailUser = {
- name: 'Invite "' + trimmed + '" by email',
+ name: sprintf(__('Invite "%{trimmed}" by email'), { trimmed }),
username: trimmed,
id: trimmed,
invite: true,
@@ -688,7 +698,7 @@ UsersSelect.prototype.initSelection = function(element, callback) {
id = $(element).val();
if (id === '0') {
nullUser = {
- name: 'Unassigned',
+ name: s__('UsersSelect|Unassigned'),
};
return callback(nullUser);
} else if (id !== '') {
diff --git a/app/assets/javascripts/validators/input_validator.js b/app/assets/javascripts/validators/input_validator.js
new file mode 100644
index 00000000000..f37373977b8
--- /dev/null
+++ b/app/assets/javascripts/validators/input_validator.js
@@ -0,0 +1,34 @@
+const invalidInputClass = 'gl-field-error-outline';
+
+export default class InputValidator {
+ constructor() {
+ this.inputDomElement = {};
+ this.inputErrorMessage = {};
+ this.errorMessage = null;
+ this.invalidInput = null;
+ }
+
+ setValidationStateAndMessage() {
+ this.setValidationMessage();
+
+ const isInvalidInput = !this.inputDomElement.checkValidity();
+ this.inputDomElement.classList.toggle(invalidInputClass, isInvalidInput);
+ this.inputErrorMessage.classList.toggle('hide', !isInvalidInput);
+ }
+
+ setValidationMessage() {
+ if (this.invalidInput) {
+ this.inputDomElement.setCustomValidity(this.errorMessage);
+ this.inputErrorMessage.innerHTML = this.errorMessage;
+ } else {
+ this.resetValidationMessage();
+ }
+ }
+
+ resetValidationMessage() {
+ if (this.inputDomElement.validationMessage === this.errorMessage) {
+ this.inputDomElement.setCustomValidity('');
+ this.inputErrorMessage.innerHTML = this.inputDomElement.title;
+ }
+ }
+}
diff --git a/app/assets/javascripts/visual_review_toolbar/index.js b/app/assets/javascripts/visual_review_toolbar/index.js
new file mode 100644
index 00000000000..91d0382feac
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/index.js
@@ -0,0 +1,2 @@
+import './styles/toolbar.css';
+import 'vendor/visual_review_toolbar';
diff --git a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
new file mode 100644
index 00000000000..342b3599a44
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
@@ -0,0 +1,149 @@
+/*
+ As a standalone script, the toolbar has its own css
+ */
+
+#gitlab-collapse > * {
+ pointer-events: none;
+}
+
+#gitlab-form-wrapper {
+ display: flex;
+ flex-direction: column;
+ width: 100%
+}
+
+#gitlab-review-container {
+ max-width: 22rem;
+ max-height: 22rem;
+ overflow: scroll;
+ position: fixed;
+ bottom: 1rem;
+ right: 1rem;
+ display: flex;
+ flex-direction: row-reverse;
+ padding: 1rem;
+ background-color: #fff;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
+ 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
+ 'Noto Color Emoji';
+ font-size: .8rem;
+ font-weight: 400;
+ color: #2e2e2e;
+}
+
+.gitlab-open-wrapper {
+ max-width: 22rem;
+ max-height: 22rem;
+}
+
+.gitlab-closed-wrapper {
+ max-width: 3.4rem;
+ max-height: 3.4rem;
+}
+
+.gitlab-button {
+ cursor: pointer;
+ transition: background-color 100ms linear, border-color 100ms linear, color 100ms linear, box-shadow 100ms linear;
+}
+
+.gitlab-button-secondary {
+ background: none #fff;
+ margin: 0 .5rem;
+ border: 1px solid #e3e3e3;
+}
+
+.gitlab-button-secondary:hover {
+ background-color: #f0f0f0;
+ border-color: #e3e3e3;
+ color: #2e2e2e;
+}
+
+.gitlab-button-secondary:active {
+ color: #2e2e2e;
+ background-color: #e1e1e1;
+ border-color: #dadada;
+}
+
+.gitlab-button-success:hover {
+ color: #fff;
+ background-color: #137e3f;
+ border-color: #127339;
+}
+
+.gitlab-button-success:active {
+ background-color: #168f48;
+ border-color: #12753a;
+ color: #fff;
+}
+
+.gitlab-button-success {
+ background-color: #1aaa55;
+ border: 1px solid #168f48;
+ color: #fff;
+}
+
+.gitlab-button-wide {
+ width: 100%;
+}
+
+.gitlab-button-wrapper {
+ margin-top: 1rem;
+ display: flex;
+ align-items: baseline;
+ justify-content: flex-end;
+}
+
+.gitlab-collapse {
+ width: 2.4rem;
+ height: 2.2rem;
+ margin-left: 1rem;
+ padding: .5rem;
+}
+
+.gitlab-collapse-closed {
+ align-self: center;
+}
+
+.gitlab-checkbox-label {
+ padding: 0 .2rem;
+}
+
+.gitlab-checkbox-wrapper {
+ display: flex;
+ align-items: baseline;
+}
+
+.gitlab-label {
+ font-weight: 600;
+ display: inline-block;
+ width: 100%;
+}
+
+.gitlab-link {
+ color: #1b69b6;
+ text-decoration: none;
+ background-color: transparent;
+ background-image: none;
+}
+
+.gitlab-message {
+ padding: .25rem 0;
+ margin: 0;
+ line-height: 1.2rem;
+}
+
+.gitlab-metadata-note {
+ font-size: .7rem;
+ line-height: 1rem;
+ color: #666;
+ margin-bottom: 0;
+}
+
+.gitlab-input {
+ width: 100%;
+ border: 1px solid #dfdfdf;
+ border-radius: 4px;
+ padding: .1rem .2rem;
+ min-height: 2rem;
+ max-width: 17rem;
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index 8f4cae8ae58..abe5bdd2901 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -49,7 +49,7 @@ export default {
required: false,
default: () => ({
sourceProjectId: '',
- issueId: '',
+ mergeRequestId: '',
appUrl: '',
}),
},
@@ -77,16 +77,16 @@ export default {
return this.deployment.external_url;
},
hasExternalUrls() {
- return !!(this.deployment.external_url && this.deployment.external_url_formatted);
+ return Boolean(this.deployment.external_url && this.deployment.external_url_formatted);
},
hasDeploymentTime() {
- return !!(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
+ return Boolean(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
},
hasDeploymentMeta() {
- return !!(this.deployment.url && this.deployment.name);
+ return Boolean(this.deployment.url && this.deployment.name);
},
hasMetrics() {
- return !!this.deployment.metrics_url;
+ return Boolean(this.deployment.metrics_url);
},
deployedText() {
return this.$options.deployedTextMap[this.deployment.status];
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue
index 040315b3c66..19a222462b3 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue
@@ -37,7 +37,7 @@ export default {
</script>
<template>
- <div class="m-3 ml-5" :class="messageClass">
+ <div class="m-3 ml-7" :class="messageClass">
<slot></slot>
<gl-link v-if="helpPath" :href="helpPath" target="_blank">
<icon :size="16" name="question-o" class="align-middle" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 361441640e1..e20a16900d4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -128,7 +128,7 @@ export default {
:disabled="mr.sourceBranchRemoved"
data-target="#modal_merge_info"
data-toggle="modal"
- class="btn btn-default js-check-out-branch append-right-default"
+ class="btn btn-default js-check-out-branch append-right-8"
type="button"
>
{{ s__('mrWidget|Check out branch') }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index f5fa68308bc..c377c16fb13 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -5,6 +5,7 @@ import { sprintf, __ } from '~/locale';
import PipelineStage from '~/pipelines/components/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
+import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline';
@@ -16,6 +17,7 @@ export default {
Icon,
TooltipOnTruncate,
GlLink,
+ PipelineLink,
LinkedPipelinesMiniList: () =>
import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
},
@@ -112,9 +114,12 @@ export default {
<div class="media-body">
<div class="font-weight-bold js-pipeline-info-container">
{{ s__('Pipeline|Pipeline') }}
- <gl-link :href="pipeline.path" class="pipeline-id font-weight-normal pipeline-number"
- >#{{ pipeline.id }}</gl-link
- >
+ <pipeline-link
+ :href="pipeline.path"
+ :pipeline-id="pipeline.id"
+ :pipeline-iid="pipeline.iid"
+ class="pipeline-id pipeline-iid font-weight-normal"
+ />
{{ pipeline.details.status.label }}
<template v-if="hasCommitInfo">
{{ s__('Pipeline|for') }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
index b9f5f602117..03a15ba81ed 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
@@ -48,7 +48,7 @@ export default {
visualReviewAppMeta() {
return {
appUrl: this.mr.appUrl,
- issueId: this.mr.iid,
+ mergeRequestId: this.mr.iid,
sourceProjectId: this.mr.sourceProjectId,
};
},
@@ -56,7 +56,7 @@ export default {
return this.isPostMerge ? this.mr.mergePipeline : this.mr.pipeline;
},
showVisualReviewAppLink() {
- return !!(this.mr.visualReviewFF && this.mr.visualReviewAppAvailable);
+ return Boolean(this.mr.visualReviewFF && this.mr.visualReviewAppAvailable);
},
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
index 780ecdcdac4..6aad2a26a53 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
@@ -14,7 +14,7 @@ export default {
</script>
<template>
- <p v-once class="mr-info-list mr-links source-branch-removal-status append-bottom-0">
+ <p v-once class="mr-info-list mr-links append-bottom-0">
<span class="status-text" v-html="removesBranchText"> </span>
<i v-tooltip :title="tooltipTitle" :aria-label="tooltipTitle" class="fa fa-question-circle">
</i>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
index a3a44dd8e99..83e7d6db9fa 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -35,9 +35,7 @@ export default {
<status-icon status="warning" />
<div class="media-body space-children">
<span class="bold">
- <template v-if="mr.mergeError"
- >{{ mr.mergeError }}.</template
- >
+ <template v-if="mr.mergeError">{{ mr.mergeError }}</template>
{{ s__('mrWidget|This merge request failed to be merged automatically') }}
</span>
<button
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
index 1b3af2fccf2..88e1ccbaf35 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
@@ -57,7 +57,7 @@ export default {
removeSourceBranch() {
const options = {
sha: this.mr.sha,
- merge_when_pipeline_succeeds: true,
+ auto_merge_strategy: 'merge_when_pipeline_succeeds',
should_remove_source_branch: true,
};
@@ -85,7 +85,7 @@ export default {
<h4 class="d-flex align-items-start">
<span class="append-right-10">
{{ s__('mrWidget|Set by') }}
- <mr-widget-author :author="mr.setToMWPSBy" />
+ <mr-widget-author :author="mr.setToAutoMergeBy" />
{{ s__('mrWidget|to be merged automatically when the pipeline succeeds') }}
</span>
<a
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 851939d5d4e..615d59a7b8e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -31,7 +31,7 @@ export default {
return {
removeSourceBranch: this.mr.shouldRemoveSourceBranch,
mergeWhenBuildSucceeds: false,
- setToMergeWhenPipelineSucceeds: false,
+ autoMergeStrategy: undefined,
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
@@ -42,7 +42,7 @@ export default {
};
},
computed: {
- shouldShowMergeWhenPipelineSucceedsText() {
+ shouldShowAutoMergeText() {
return this.mr.isPipelineActive;
},
status() {
@@ -87,7 +87,7 @@ export default {
mergeButtonText() {
if (this.isMergingImmediately) {
return __('Merge in progress');
- } else if (this.shouldShowMergeWhenPipelineSucceedsText) {
+ } else if (this.shouldShowAutoMergeText) {
return __('Merge when pipeline succeeds');
}
@@ -104,7 +104,7 @@ export default {
return enableSquashBeforeMerge && commitsCount > 1;
},
shouldShowMergeControls() {
- return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
+ return this.mr.isMergeAllowed || this.shouldShowAutoMergeText;
},
shouldShowSquashEdit() {
return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge;
@@ -126,12 +126,12 @@ export default {
this.isMergingImmediately = true;
}
- this.setToMergeWhenPipelineSucceeds = mergeWhenBuildSucceeds === true;
+ this.autoMergeStrategy = mergeWhenBuildSucceeds ? 'merge_when_pipeline_succeeds' : undefined;
const options = {
sha: this.mr.sha,
commit_message: this.commitMessage,
- merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
+ auto_merge_strategy: this.autoMergeStrategy,
should_remove_source_branch: this.removeSourceBranch === true,
squash: this.squashBeforeMerge,
squash_commit_message: this.squashCommitMessage,
@@ -330,6 +330,7 @@ export default {
:commits-count="mr.commitsCount"
:target-branch="mr.targetBranch"
:is-fast-forward-enabled="mr.ffOnlyEnabled"
+ :class="{ 'border-bottom': mr.mergeError }"
>
<ul class="border-top content-list commits-list flex-list">
<commit-edit
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
index b1f5655a15a..accb9d9fef1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
@@ -29,8 +29,8 @@ export default {
</script>
<template>
- <div class="accept-control inline">
- <label class="merge-param-checkbox">
+ <div class="inline">
+ <label>
<input
:checked="value"
:disabled="isDisabled"
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 705ee05e29f..d02bb2f341d 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
@@ -1,6 +1,6 @@
<script>
import _ from 'underscore';
-import { __ } from '~/locale';
+import { sprintf, s__, __ } from '~/locale';
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
@@ -97,7 +97,7 @@ export default {
return this.mr.hasCI;
},
shouldRenderRelatedLinks() {
- return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
+ return Boolean(this.mr.relatedLinks) && !this.mr.isNothingToMergeState;
},
shouldRenderSourceBranchRemovalStatus() {
return (
@@ -125,6 +125,11 @@ export default {
this.mr.pipeline.target_sha !== this.mr.targetBranchSha,
);
},
+ mergeError() {
+ return sprintf(s__('mrWidget|Merge failed: %{mergeError}. Please try again.'), {
+ mergeError: this.mr.mergeError,
+ });
+ },
},
watch: {
state(newVal, oldVal) {
@@ -333,41 +338,49 @@ export default {
<div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" />
- <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
- {{ s__('mrWidget|Allows commits from members who can merge to the target branch') }}
- </section>
+ <div class="mr-widget-info">
+ <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
+ <p>
+ {{ s__('mrWidget|Allows commits from members who can merge to the target branch') }}
+ </p>
+ </section>
+
+ <mr-widget-related-links
+ v-if="shouldRenderRelatedLinks"
+ :state="mr.state"
+ :related-links="mr.relatedLinks"
+ />
- <mr-widget-related-links
- v-if="shouldRenderRelatedLinks"
- :state="mr.state"
- :related-links="mr.relatedLinks"
- />
+ <mr-widget-alert-message
+ v-if="showMergePipelineForkWarning"
+ type="warning"
+ :help-path="mr.mergeRequestPipelinesHelpPath"
+ >
+ {{
+ s__(
+ 'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result',
+ )
+ }}
+ </mr-widget-alert-message>
- <mr-widget-alert-message
- v-if="showMergePipelineForkWarning"
- type="warning"
- :help-path="mr.mergeRequestPipelinesHelpPath"
- >
- {{
- s__(
- 'mrWidget|Fork merge requests do not create merge request pipelines which validate a post merge result',
- )
- }}
- </mr-widget-alert-message>
+ <mr-widget-alert-message
+ v-if="showTargetBranchAdvancedError"
+ type="danger"
+ :help-path="mr.mergeRequestPipelinesHelpPath"
+ >
+ {{
+ s__(
+ 'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging',
+ )
+ }}
+ </mr-widget-alert-message>
- <mr-widget-alert-message
- v-if="showTargetBranchAdvancedError"
- type="danger"
- :help-path="mr.mergeRequestPipelinesHelpPath"
- >
- {{
- s__(
- 'mrWidget|The target branch has advanced, which invalidates the merge request pipeline. Please update the source branch and retry merging',
- )
- }}
- </mr-widget-alert-message>
+ <mr-widget-alert-message v-if="mr.mergeError" type="danger">
+ {{ mergeError }}
+ </mr-widget-alert-message>
- <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
+ <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
+ </div>
</div>
<div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
index 0cc4fd59f5e..3ab229567f6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
@@ -23,8 +23,8 @@ export default function deviseState(data) {
return stateKey.pipelineBlocked;
} else if (this.isSHAMismatch) {
return stateKey.shaMismatch;
- } else if (this.mergeWhenPipelineSucceeds) {
- return this.mergeError ? stateKey.autoMergeFailed : stateKey.mergeWhenPipelineSucceeds;
+ } else if (this.autoMergeEnabled) {
+ return this.mergeError ? stateKey.autoMergeFailed : stateKey.autoMergeEnabled;
} else if (!this.canMerge) {
return stateKey.notAllowedToMerge;
} else if (this.canBeMerged) {
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 45708d78886..32badb0fb08 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
@@ -61,7 +61,7 @@ export default class MergeRequestStore {
this.updatedAt = data.updated_at;
this.metrics = MergeRequestStore.buildMetrics(data.metrics);
- this.setToMWPSBy = MergeRequestStore.formatUserObject(data.merge_user || {});
+ this.setToAutoMergeBy = MergeRequestStore.formatUserObject(data.merge_user || {});
this.mergeUserId = data.merge_user_id;
this.currentUserId = gon.current_user_id;
this.sourceBranchPath = data.source_branch_path;
@@ -70,15 +70,16 @@ export default class MergeRequestStore {
this.targetBranchPath = data.target_branch_commits_path;
this.targetBranchTreePath = data.target_branch_tree_path;
this.conflictResolutionPath = data.conflict_resolution_path;
- this.cancelAutoMergePath = data.cancel_merge_when_pipeline_succeeds_path;
+ this.cancelAutoMergePath = data.cancel_auto_merge_path;
this.removeWIPPath = data.remove_wip_path;
this.sourceBranchRemoved = !data.source_branch_exists;
this.shouldRemoveSourceBranch = data.remove_source_branch || false;
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
- this.mergeWhenPipelineSucceeds = data.merge_when_pipeline_succeeds || false;
+ this.autoMergeEnabled = Boolean(data.auto_merge_enabled);
+ this.autoMergeStrategy = data.auto_merge_strategy;
this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
- this.shouldBeRebased = !!data.should_be_rebased;
+ this.shouldBeRebased = Boolean(data.should_be_rebased);
this.statusPath = data.status_path;
this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path;
@@ -91,9 +92,9 @@ export default class MergeRequestStore {
this.isOpen = data.state === 'opened';
this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false;
this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false;
- this.canMerge = !!data.merge_path;
+ this.canMerge = Boolean(data.merge_path);
this.canCreateIssue = currentUser.can_create_issue || false;
- this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
+ this.canCancelAutomaticMerge = Boolean(data.cancel_auto_merge_path);
this.isSHAMismatch = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
this.isMergeAllowed = data.mergeable || false;
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
index e080ce5c229..48bc6a867f4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js
@@ -13,7 +13,7 @@ const stateToComponentMap = {
unresolvedDiscussions: 'mr-widget-unresolved-discussions',
pipelineBlocked: 'mr-widget-pipeline-blocked',
pipelineFailed: 'mr-widget-pipeline-failed',
- mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds',
+ autoMergeEnabled: 'mr-widget-merge-when-pipeline-succeeds',
failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed',
shaMismatch: 'sha-mismatch',
@@ -45,7 +45,7 @@ export const stateKey = {
pipelineBlocked: 'pipelineBlocked',
shaMismatch: 'shaMismatch',
autoMergeFailed: 'autoMergeFailed',
- mergeWhenPipelineSucceeds: 'mergeWhenPipelineSucceeds',
+ autoMergeEnabled: 'autoMergeEnabled',
notAllowedToMerge: 'notAllowedToMerge',
readyToMerge: 'readyToMerge',
rebase: 'rebase',
diff --git a/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue b/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue
new file mode 100644
index 00000000000..eae4c06467c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue
@@ -0,0 +1,32 @@
+<script>
+import { GlLink, GlTooltipDirective } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ href: {
+ type: String,
+ required: true,
+ },
+ pipelineId: {
+ type: Number,
+ required: true,
+ },
+ pipelineIid: {
+ type: Number,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <gl-link v-gl-tooltip :href="href" :title="__('Pipeline ID (IID)')">
+ <span class="pipeline-id">#{{ pipelineId }}</span>
+ <span class="pipeline-iid">(#{{ pipelineIid }})</span>
+ </gl-link>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index 3ba946e6447..a1168fa0f1e 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -1,6 +1,7 @@
<script>
import _ from 'underscore';
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from './user_avatar/user_avatar_link.vue';
import Icon from '../../vue_shared/components/icon.vue';
@@ -12,6 +13,7 @@ export default {
UserAvatarLink,
Icon,
GlLink,
+ TooltipOnTruncate,
},
props: {
/**
@@ -165,7 +167,7 @@ export default {
<gl-link :href="commitUrl" class="commit-sha mr-0"> {{ shortSha }} </gl-link>
<div class="commit-title flex-truncate-parent">
- <span v-if="title" class="flex-truncate-child">
+ <tooltip-on-truncate v-if="title" class="flex-truncate-child" :title="title">
<user-avatar-link
v-if="hasAuthor"
:link-href="author.path"
@@ -174,8 +176,10 @@ export default {
:tooltip-text="author.username"
class="avatar-image-container"
/>
- <gl-link :href="commitUrl" class="commit-row-message cgray"> {{ title }} </gl-link>
- </span>
+ <gl-link :href="commitUrl" class="commit-row-message cgray">
+ {{ title }}
+ </gl-link>
+ </tooltip-on-truncate>
<span v-else> Can't find HEAD commit for this branch </span>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue b/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue
new file mode 100644
index 00000000000..7d49c87271d
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/droplab_dropdown_button.vue
@@ -0,0 +1,89 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import Icon from './icon.vue';
+
+export default {
+ components: {
+ Icon,
+ GlButton,
+ },
+ props: {
+ size: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ primaryButtonClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ dropdownClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ actions: {
+ type: Array,
+ required: true,
+ },
+ defaultAction: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ selectedAction: this.defaultAction,
+ };
+ },
+ computed: {
+ selectedActionTitle() {
+ return this.actions[this.selectedAction].title;
+ },
+ buttonSizeClass() {
+ return `btn-${this.size}`;
+ },
+ },
+ methods: {
+ handlePrimaryActionClick() {
+ this.$emit('onActionClick', this.actions[this.selectedAction]);
+ },
+ handleActionClick(selectedAction) {
+ this.selectedAction = selectedAction;
+ this.$emit('onActionSelect', selectedAction);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="btn-group droplab-dropdown comment-type-dropdown">
+ <gl-button :class="primaryButtonClass" :size="size" @click.prevent="handlePrimaryActionClick">
+ {{ selectedActionTitle }}
+ </gl-button>
+ <button
+ :class="buttonSizeClass"
+ type="button"
+ class="btn dropdown-toggle pl-2 pr-2"
+ data-display="static"
+ data-toggle="dropdown"
+ >
+ <icon name="arrow-down" aria-label="toggle dropdown" />
+ </button>
+ <ul :class="dropdownClass" class="dropdown-menu dropdown-open-top">
+ <template v-for="(action, index) in actions">
+ <li :key="index" :class="{ 'droplab-item-selected': selectedAction === index }">
+ <gl-button class="btn-transparent" @click.prevent="handleActionClick(index)">
+ <i aria-hidden="true" class="fa fa-check icon"> </i>
+ <div class="description">
+ <strong>{{ action.title }}</strong>
+ <p>{{ action.description }}</p>
+ </div>
+ </gl-button>
+ </li>
+ <li v-if="index === 0" :key="`${index}-separator`" class="divider droplab-item-ignore"></li>
+ </template>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 3f45dc7853b..0bac63b1062 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -37,6 +37,16 @@ export default {
type: Number,
required: true,
},
+ itemIid: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ itemIdTooltip: {
+ type: String,
+ required: false,
+ default: '',
+ },
time: {
type: String,
required: true,
@@ -85,7 +95,12 @@ export default {
<section class="header-main-content">
<ci-icon-badge :status="status" />
- <strong> {{ itemName }} #{{ itemId }} </strong>
+ <strong v-gl-tooltip :title="itemIdTooltip">
+ {{ itemName }} #{{ itemId }}
+ <template v-if="itemIid"
+ >(#{{ itemIid }})</template
+ >
+ </strong>
<template v-if="shouldRenderTriggeredLabel">
triggered
@@ -96,9 +111,8 @@ export default {
<timeago-tooltip :time="time" />
- by
-
<template v-if="user">
+ by
<gl-link
v-gl-tooltip
:href="user.path"
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
index 7e79e63aa1e..715cf97f0ac 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
@@ -62,6 +62,15 @@ export default {
assigneeName: assignee.name,
});
},
+ // This method is for backward compat
+ // since Graph query would return camelCase
+ // props while Rails would return snake_case
+ webUrl(assignee) {
+ return assignee.web_url || assignee.webUrl;
+ },
+ avatarUrl(assignee) {
+ return assignee.avatar_url || assignee.avatarUrl;
+ },
},
};
</script>
@@ -70,9 +79,9 @@ export default {
<user-avatar-link
v-for="assignee in assigneesToShow"
:key="assignee.id"
- :link-href="assignee.web_url"
+ :link-href="webUrl(assignee)"
:img-alt="avatarUrlTitle(assignee)"
- :img-src="assignee.avatar_url"
+ :img-src="avatarUrl(assignee)"
:img-size="24"
class="js-no-trigger"
tooltip-placement="bottom"
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
index 53e6efa6ea3..9b2ee5062b1 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
@@ -19,10 +19,14 @@ export default {
},
computed: {
milestoneDue() {
- return this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null;
+ const dueDate = this.milestone.due_date || this.milestone.dueDate;
+
+ return dueDate ? parsePikadayDate(dueDate) : null;
},
milestoneStart() {
- return this.milestone.start_date ? parsePikadayDate(this.milestone.start_date) : null;
+ const startDate = this.milestone.start_date || this.milestone.startDate;
+
+ return startDate ? parsePikadayDate(startDate) : null;
},
isMilestoneStarted() {
if (!this.milestoneStart) {
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
index e92babc499b..e438ff16a41 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
@@ -1,9 +1,17 @@
<script>
+import { GlLink } from '@gitlab/ui';
+import _ from 'underscore';
+import { sprintf } from '~/locale';
import icon from '../../../vue_shared/components/icon.vue';
+function buildDocsLinkStart(path) {
+ return `<a href="${_.escape(path)}" target="_blank" rel="noopener noreferrer">`;
+}
+
export default {
components: {
icon,
+ GlLink,
},
props: {
isLocked: {
@@ -16,6 +24,16 @@ export default {
default: false,
required: false,
},
+ lockedIssueDocsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ confidentialIssueDocsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
warningIcon() {
@@ -27,6 +45,17 @@ export default {
isLockedAndConfidential() {
return this.isConfidential && this.isLocked;
},
+ confidentialAndLockedDiscussionText() {
+ return sprintf(
+ 'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
+ {
+ confidentialLinkStart: buildDocsLinkStart(this.confidentialIssueDocsPath),
+ lockedLinkStart: buildDocsLinkStart(this.lockedIssueDocsPath),
+ linkEnd: '</a>',
+ },
+ false,
+ );
+ },
},
};
</script>
@@ -35,20 +64,26 @@ export default {
<icon v-if="!isLockedAndConfidential" :name="warningIcon" :size="16" class="icon inline" />
<span v-if="isLockedAndConfidential">
- {{ __('This issue is confidential and locked.') }}
+ <span v-html="confidentialAndLockedDiscussionText"></span>
{{
- __(`People without permission will never
-get a notification and won't be able to comment.`)
+ __(`People without permission will never get a notification and won't be able to comment.`)
}}
</span>
<span v-else-if="isConfidential">
{{ __('This is a confidential issue.') }}
- {{ __('Your comment will not be visible to the public.') }}
+ {{ __('People without permission will never get a notification.') }}
+ <gl-link :href="confidentialIssueDocsPath" target="_blank">
+ {{ __('Learn more') }}
+ </gl-link>
</span>
<span v-else-if="isLocked">
- {{ __('This issue is locked.') }} {{ __('Only project members can comment.') }}
+ {{ __('This issue is locked.') }}
+ {{ __('Only project members can comment.') }}
+ <gl-link :href="lockedIssueDocsPath" target="_blank">
+ {{ __('Learn more') }}
+ </gl-link>
</span>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
index b807a35b421..05ad7710a62 100644
--- a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue
@@ -24,6 +24,11 @@ export default {
required: false,
default: false,
},
+ greyLinkWhenMerged: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
stateTitle() {
@@ -36,6 +41,11 @@ export default {
},
);
},
+ issueableLinkClass() {
+ return this.greyLinkWhenMerged
+ ? `sortable-link ${this.state === 'merged' ? ' text-secondary' : ''}`
+ : 'sortable-link';
+ },
},
};
</script>
@@ -69,7 +79,7 @@ export default {
class="confidential-icon append-right-4 align-self-baseline align-self-md-auto mt-xl-0"
:aria-label="__('Confidential')"
/>
- <a :href="computedPath" class="sortable-link">{{ title }}</a>
+ <a :href="computedPath" :class="issueableLinkClass">{{ title }}</a>
</div>
<div class="item-meta d-flex flex-wrap mt-xl-0 justify-content-xl-end flex-xl-nowrap">
<div
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index 3b57b5e8da4..d6c398c8946 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -33,37 +33,36 @@ export default {
<div class="comment-toolbar clearfix">
<div class="toolbar-text">
<template v-if="!hasQuickActionsDocsPath && markdownDocsPath">
- <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">
- Markdown is supported
- </gl-link>
+ <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1"
+ >Markdown is supported</gl-link
+ >
</template>
<template v-if="hasQuickActionsDocsPath && markdownDocsPath">
- <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1"> Markdown </gl-link>
- and
- <gl-link :href="quickActionsDocsPath" target="_blank" tabindex="-1">
- quick actions
- </gl-link>
+ <gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">Markdown</gl-link> and
+ <gl-link :href="quickActionsDocsPath" target="_blank" tabindex="-1">quick actions</gl-link>
are supported
</template>
</div>
<span v-if="canAttachFile" class="uploading-container">
<span class="uploading-progress-container hide">
- <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"> </i>
- <span class="attaching-file-message"></span> <span class="uploading-progress">0%</span>
+ <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i>
+ <span class="attaching-file-message"></span>
+ <span class="uploading-progress">0%</span>
<span class="uploading-spinner">
- <i class="fa fa-spinner fa-spin toolbar-button-icon" aria-hidden="true"> </i>
+ <i class="fa fa-spinner fa-spin toolbar-button-icon" aria-hidden="true"></i>
</span>
</span>
<span class="uploading-error-container hide">
<span class="uploading-error-icon">
- <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"> </i>
+ <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i>
</span>
<span class="uploading-error-message"></span>
<button class="retry-uploading-link" type="button">Try again</button> or
<button class="attach-new-file markdown-selector" type="button">attach a new file</button>
</span>
- <button class="markdown-selector button-attach-file" tabindex="-1" type="button">
- <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"> </i> Attach a file
+ <button class="markdown-selector button-attach-file btn-link" tabindex="-1" type="button">
+ <i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i
+ ><span class="text-attach-file">Attach a file</span>
</button>
<button class="btn btn-default btn-sm hide button-cancel-uploading-files" type="button">
Cancel
diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
new file mode 100644
index 00000000000..bf59a6abf3f
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
@@ -0,0 +1,121 @@
+<script>
+import $ from 'jquery';
+import { GlButton, GlTooltipDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import Clipboard from 'clipboard';
+
+export default {
+ components: {
+ GlButton,
+ Icon,
+ },
+
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+
+ props: {
+ text: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ container: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ modalId: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ target: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
+ tooltipContainer: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+
+ copySuccessText: __('Copied'),
+
+ computed: {
+ modalDomId() {
+ return this.modalId ? `#${this.modalId}` : '';
+ },
+ },
+
+ mounted() {
+ this.$nextTick(() => {
+ this.clipboard = new Clipboard(this.$el, {
+ container:
+ document.querySelector(`${this.modalDomId} div.modal-content`) ||
+ document.getElementById(this.container) ||
+ document.body,
+ });
+ this.clipboard
+ .on('success', e => {
+ this.updateTooltip(e.trigger);
+ this.$emit('success', e);
+ // Clear the selection and blur the trigger so it loses its border
+ e.clearSelection();
+ $(e.trigger).blur();
+ })
+ .on('error', e => this.$emit('error', e));
+ });
+ },
+
+ destroyed() {
+ if (this.clipboard) {
+ this.clipboard.destroy();
+ }
+ },
+
+ methods: {
+ updateTooltip(target) {
+ const $target = $(target);
+ const originalTitle = $target.data('originalTitle');
+
+ if ($target.tooltip) {
+ /**
+ * The original tooltip will continue staying there unless we remove it by hand.
+ * $target.tooltip('hide') isn't working.
+ */
+ $('.tooltip').remove();
+ $target.attr('title', this.$options.copySuccessText);
+ $target.tooltip('_fixTitle');
+ $target.tooltip('show');
+ $target.attr('title', originalTitle);
+ $target.tooltip('_fixTitle');
+ }
+ },
+ },
+};
+</script>
+<template>
+ <gl-button
+ v-gl-tooltip="{ placement: tooltipPlacement, container: tooltipContainer }"
+ :data-clipboard-target="target"
+ :data-clipboard-text="text"
+ :title="title"
+ >
+ <slot>
+ <icon name="duplicate" />
+ </slot>
+ </gl-button>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
index a50f49c1279..baed26a157c 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
@@ -51,7 +51,7 @@ export default {
<div class="note-header">
<div class="note-header-info">
<a :href="getUserData.path">
- <span class="d-none d-sm-inline-block">{{ getUserData.name }}</span>
+ <span class="d-none d-sm-inline-block bold">{{ getUserData.name }}</span>
<span class="note-headline-light">@{{ getUserData.username }}</span>
</a>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/pagination/constants.js b/app/assets/javascripts/vue_shared/components/pagination/constants.js
new file mode 100644
index 00000000000..c24b142ac7e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/pagination/constants.js
@@ -0,0 +1,9 @@
+import { s__ } from '~/locale';
+
+export const PAGINATION_UI_BUTTON_LIMIT = 4;
+export const UI_LIMIT = 6;
+export const SPREAD = '...';
+export const PREV = s__('Pagination|Prev');
+export const NEXT = s__('Pagination|Next');
+export const FIRST = s__('Pagination|« First');
+export const LAST = s__('Pagination|Last »');
diff --git a/app/assets/javascripts/vue_shared/components/pagination/graphql_pagination.vue b/app/assets/javascripts/vue_shared/components/pagination/graphql_pagination.vue
new file mode 100644
index 00000000000..53e473432db
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/pagination/graphql_pagination.vue
@@ -0,0 +1,47 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { PREV, NEXT } from '~/vue_shared/components/pagination/constants';
+
+/**
+ * Pagination Component for graphql API
+ */
+export default {
+ name: 'GraphqlPaginationComponent',
+ components: {
+ GlButton,
+ },
+ labels: {
+ prev: PREV,
+ next: NEXT,
+ },
+ props: {
+ hasNextPage: {
+ required: true,
+ type: Boolean,
+ },
+ hasPreviousPage: {
+ required: true,
+ type: Boolean,
+ },
+ },
+};
+</script>
+<template>
+ <div class="justify-content-center d-flex prepend-top-default">
+ <div class="btn-group">
+ <gl-button
+ class="js-prev-btn page-link"
+ :disabled="!hasPreviousPage"
+ @click="$emit('previousClicked')"
+ >{{ $options.labels.prev }}</gl-button
+ >
+
+ <gl-button
+ class="js-next-btn page-link"
+ :disabled="!hasNextPage"
+ @click="$emit('nextClicked')"
+ >{{ $options.labels.next }}</gl-button
+ >
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue
index fa502b9beb9..8104d919bf6 100644
--- a/app/assets/javascripts/vue_shared/components/pikaday.vue
+++ b/app/assets/javascripts/vue_shared/components/pikaday.vue
@@ -34,7 +34,7 @@ export default {
format: 'yyyy-mm-dd',
container: this.$el,
defaultDate: this.selectedDate,
- setDefaultDate: !!this.selectedDate,
+ setDefaultDate: Boolean(this.selectedDate),
minDate: this.minDate,
maxDate: this.maxDate,
parse: dateString => parsePikadayDate(dateString),
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 8e0b08032f7..1e2d4ffa7e3 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -1,13 +1,13 @@
<script>
-import { s__ } from '../../locale';
-
-const PAGINATION_UI_BUTTON_LIMIT = 4;
-const UI_LIMIT = 6;
-const SPREAD = '...';
-const PREV = s__('Pagination|Prev');
-const NEXT = s__('Pagination|Next');
-const FIRST = s__('Pagination|« First');
-const LAST = s__('Pagination|Last »');
+import {
+ PAGINATION_UI_BUTTON_LIMIT,
+ UI_LIMIT,
+ SPREAD,
+ PREV,
+ NEXT,
+ FIRST,
+ LAST,
+} from '~/vue_shared/components/pagination/constants';
export default {
props: {
@@ -121,7 +121,7 @@ export default {
this.change(1);
break;
default:
- this.change(+text);
+ this.change(Number(text));
break;
}
},
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index 4dbfd1ba6f4..a60d5eb491e 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -71,11 +71,15 @@ export default {
</div>
<div class="text-secondary">
<div v-if="user.bio" class="js-bio d-flex mb-1">
- <icon name="profile" css-classes="category-icon" />
+ <icon name="profile" css-classes="category-icon flex-shrink-0" />
<span class="ml-1">{{ user.bio }}</span>
</div>
<div v-if="user.organization" class="js-organization d-flex mb-1">
- <icon v-show="!jobInfoIsLoading" name="work" css-classes="category-icon" />
+ <icon
+ v-show="!jobInfoIsLoading"
+ name="work"
+ css-classes="category-icon flex-shrink-0"
+ />
<span class="ml-1">{{ user.organization }}</span>
</div>
<gl-skeleton-loading
@@ -88,7 +92,7 @@ export default {
<icon
v-show="!locationIsLoading && user.location"
name="location"
- css-classes="category-icon"
+ css-classes="category-icon flex-shrink-0"
/>
<span class="ml-1">{{ user.location }}</span>
<gl-skeleton-loading
diff --git a/app/assets/javascripts/vue_shared/models/label.js b/app/assets/javascripts/vue_shared/models/label.js
deleted file mode 100644
index 2d2732d0661..00000000000
--- a/app/assets/javascripts/vue_shared/models/label.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default class ListLabel {
- constructor(obj) {
- this.id = obj.id;
- this.title = obj.title;
- this.type = obj.type;
- this.color = obj.color;
- this.textColor = obj.text_color;
- this.description = obj.description;
- this.priority = obj.priority !== null ? obj.priority : Infinity;
- }
-}
-
-window.ListLabel = ListLabel;
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index 93377b8dd91..7f6384f4eea 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -22,7 +22,9 @@ body,
.form-control,
.search form {
// Override default font size used in non-csslab UI
- font-size: 14px;
+ // Use rem to keep default font-size at 14px on body so 1rem still
+ // fits 8px grid, but also allow users to change browser font size
+ font-size: .875rem;
}
legend {
diff --git a/app/assets/stylesheets/components/avatar.scss b/app/assets/stylesheets/components/avatar.scss
new file mode 100644
index 00000000000..8e9650cdf34
--- /dev/null
+++ b/app/assets/stylesheets/components/avatar.scss
@@ -0,0 +1,195 @@
+$avatar-sizes: (
+ 16: (
+ font-size: 10px,
+ line-height: 16px,
+ border-radius: $border-radius-small
+ ),
+ 18: (
+ border-radius: $border-radius-small
+ ),
+ 20: (
+ border-radius: $border-radius-small
+ ),
+ 24: (
+ font-size: 12px,
+ line-height: 24px,
+ border-radius: $border-radius-default
+ ),
+ 26: (
+ font-size: 20px,
+ line-height: 1.33,
+ border-radius: $border-radius-default
+ ),
+ 32: (
+ font-size: 14px,
+ line-height: 32px,
+ border-radius: $border-radius-default
+ ),
+ 40: (
+ font-size: 16px,
+ line-height: 38px,
+ border-radius: $border-radius-default
+ ),
+ 48: (
+ font-size: 20px,
+ line-height: 48px,
+ border-radius: $border-radius-large
+ ),
+ 60: (
+ font-size: 32px,
+ line-height: 58px,
+ border-radius: $border-radius-large
+ ),
+ 64: (
+ font-size: 28px,
+ line-height: 64px,
+ border-radius: $border-radius-large
+ ),
+ 90: (
+ font-size: 36px,
+ line-height: 88px,
+ border-radius: $border-radius-large
+ ),
+ 100: (
+ font-size: 36px,
+ line-height: 98px,
+ border-radius: $border-radius-large
+ ),
+ 160: (
+ font-size: 96px,
+ line-height: 158px,
+ border-radius: $border-radius-large
+ )
+);
+
+$identicon-backgrounds: $identicon-red, $identicon-purple, $identicon-indigo, $identicon-blue, $identicon-teal,
+ $identicon-orange, $gray-darker;
+
+.avatar-circle {
+ float: left;
+ margin-right: $gl-padding;
+ border-radius: $avatar-radius;
+ border: 1px solid $gray-normal;
+
+ @each $size, $size-config in $avatar-sizes {
+ &.s#{$size} {
+ @include avatar-size(#{$size}px, if($size < 48, 8px, 16px));
+ }
+ }
+}
+
+.avatar {
+ @extend .avatar-circle;
+ transition-property: none;
+
+ width: 40px;
+ height: 40px;
+ padding: 0;
+ background: $gray-lightest;
+ overflow: hidden;
+ border-color: rgba($black, $gl-avatar-border-opacity);
+
+ &.avatar-inline {
+ float: none;
+ display: inline-block;
+ margin-left: 2px;
+ flex-shrink: 0;
+
+ &.s16 {
+ margin-right: 4px;
+ }
+
+ &.s24 {
+ margin-right: 4px;
+ }
+ }
+
+ &.center {
+ font-size: 14px;
+ line-height: 1.8em;
+ text-align: center;
+ }
+
+ &.avatar-tile {
+ border-radius: 0;
+ border: 0;
+ }
+
+ &.avatar-placeholder {
+ border: 0;
+ }
+}
+
+.identicon {
+ text-align: center;
+ vertical-align: top;
+ color: $gray-800;
+ background-color: $gray-darker;
+
+ // Sizes
+ @each $size, $size-config in $avatar-sizes {
+ $keys: map-keys($size-config);
+
+ &.s#{$size} {
+ @each $key in $keys {
+ // We don't want `border-radius` to be included here.
+ @if ($key != 'border-radius') {
+ #{$key}: map-get($size-config, #{$key});
+ }
+ }
+ }
+ }
+
+ // Background colors
+ @for $i from 1 through length($identicon-backgrounds) {
+ &.bg#{$i} {
+ background-color: nth($identicon-backgrounds, $i);
+ }
+ }
+}
+
+.avatar-container {
+ @extend .avatar-circle;
+ overflow: hidden;
+ display: flex;
+
+ a {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ text-decoration: none;
+ }
+
+ .avatar {
+ border-radius: 0;
+ border: 0;
+ height: auto;
+ width: 100%;
+ margin: 0;
+ align-self: center;
+ }
+
+ &.s40 {
+ min-width: 40px;
+ min-height: 40px;
+ }
+
+ &.s64 {
+ min-width: 64px;
+ min-height: 64px;
+ }
+}
+
+.rect-avatar {
+ border-radius: $border-radius-small;
+
+ @each $size, $size-config in $avatar-sizes {
+ &.s#{$size} {
+ border-radius: map-get($size-config, 'border-radius');
+ }
+ }
+}
+
+.avatar-counter {
+ @include avatar-counter();
+}
diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss
index d0aa6ec78aa..774be9ef588 100644
--- a/app/assets/stylesheets/components/popover.scss
+++ b/app/assets/stylesheets/components/popover.scss
@@ -39,6 +39,25 @@
}
}
+.onboarding-popover {
+ box-shadow: 0 2px 4px $dropdown-shadow-color;
+
+ .popover-body {
+ font-size: $gl-font-size;
+ line-height: $gl-line-height;
+ padding: $gl-padding;
+ }
+
+ .popover-header {
+ display: none;
+ }
+
+ .accept-mr-label {
+ background-color: $accepting-mr-label-color;
+ color: $white-light;
+ }
+}
+
.onboarding-welcome-page {
.popover {
min-width: auto;
diff --git a/app/assets/stylesheets/errors.scss b/app/assets/stylesheets/errors.scss
index 658e0ff638e..8c32b6c8985 100644
--- a/app/assets/stylesheets/errors.scss
+++ b/app/assets/stylesheets/errors.scss
@@ -17,7 +17,7 @@ body {
text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
- font-size: 14px;
+ font-size: .875rem;
}
h1 {
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index ab9047c54e4..9b0d19b0ef0 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -8,7 +8,6 @@
@import 'framework/animations';
@import 'framework/vue_transitions';
-@import 'framework/avatar';
@import 'framework/asciidoctor';
@import 'framework/banner';
@import 'framework/blocks';
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 257d788873c..6f5a2e561af 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -268,3 +268,27 @@ $skeleton-line-widths: (
@include webkit-prefix(animation-duration, 1s);
transform-origin: 50% 50%;
}
+
+/* ----------------------------------------------
+ * Generated by Animista on 2019-4-26 17:40:41
+ * w: http://animista.net, t: @cssanimista
+ * ---------------------------------------------- */
+@keyframes slide-in-fwd-bottom {
+ 0% {
+ transform: translateZ(-1400px) translateY(800px);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: translateZ(0) translateY(0);
+ opacity: 1;
+ }
+}
+
+.slide-in-fwd-bottom-enter-active {
+ animation: slide-in-fwd-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
+}
+
+.slide-in-fwd-bottom-leave-active {
+ animation: slide-in-fwd-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both reverse;
+}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
deleted file mode 100644
index 37a729c7a63..00000000000
--- a/app/assets/stylesheets/framework/avatar.scss
+++ /dev/null
@@ -1,194 +0,0 @@
-@mixin avatar-size($size, $margin-right) {
- width: $size;
- height: $size;
- margin-right: $margin-right;
-}
-
-.avatar-circle {
- float: left;
- margin-right: 15px;
- border-radius: $avatar-radius;
- border: 1px solid $gray-normal;
- &.s16 { @include avatar-size(16px, 6px); }
- &.s18 { @include avatar-size(18px, 6px); }
- &.s19 { @include avatar-size(19px, 6px); }
- &.s20 { @include avatar-size(20px, 7px); }
- &.s24 { @include avatar-size(24px, 8px); }
- &.s26 { @include avatar-size(26px, 8px); }
- &.s32 { @include avatar-size(32px, 10px); }
- &.s36 { @include avatar-size(36px, 10px); }
- &.s40 { @include avatar-size(40px, 10px); }
- &.s46 { @include avatar-size(46px, 15px); }
- &.s48 { @include avatar-size(48px, 10px); }
- &.s60 { @include avatar-size(60px, 12px); }
- &.s64 { @include avatar-size(64px, 14px); }
- &.s70 { @include avatar-size(70px, 14px); }
- &.s90 { @include avatar-size(90px, 15px); }
- &.s100 { @include avatar-size(100px, 15px); }
- &.s110 { @include avatar-size(110px, 15px); }
- &.s140 { @include avatar-size(140px, 15px); }
- &.s160 { @include avatar-size(160px, 20px); }
-}
-
-.avatar {
- @extend .avatar-circle;
- transition-property: none;
-
- width: 40px;
- height: 40px;
- padding: 0;
- background: $gray-lightest;
- overflow: hidden;
-
- &.avatar-inline {
- float: none;
- display: inline-block;
- margin-left: 2px;
- flex-shrink: 0;
-
- &.s16 { margin-right: 4px; }
- &.s24 { margin-right: 4px; }
- }
-
- &.center {
- font-size: 14px;
- line-height: 1.8em;
- text-align: center;
- }
-
- &.avatar-tile {
- border-radius: 0;
- border: 0;
- }
-
- &.avatar-placeholder {
- border: 0;
- }
-
- &:not([href]):hover {
- border-color: darken($gray-normal, 10%);
- }
-}
-
-.identicon {
- text-align: center;
- vertical-align: top;
- color: $gl-gray-700;
- background-color: $gray-darker;
-
- // Sizes
- &.s16 { font-size: 12px;
- line-height: 1.33; }
-
- &.s24 { font-size: 13px;
- line-height: 1.8; }
-
- &.s26 { font-size: 20px;
- line-height: 1.33; }
-
- &.s32 { font-size: 20px;
- line-height: 30px; }
-
- &.s40 { font-size: 16px;
- line-height: 38px; }
-
- &.s48 { font-size: 20px;
- line-height: 46px; }
-
- &.s60 { font-size: 32px;
- line-height: 58px; }
-
- &.s64 { font-size: 32px;
- line-height: 64px; }
-
- &.s70 { font-size: 34px;
- line-height: 70px; }
-
- &.s90 { font-size: 36px;
- line-height: 88px; }
-
- &.s100 { font-size: 36px;
- line-height: 98px; }
-
- &.s110 { font-size: 40px;
- line-height: 108px;
- font-weight: $gl-font-weight-normal; }
-
- &.s140 { font-size: 72px;
- line-height: 138px; }
-
- &.s160 { font-size: 96px;
- line-height: 158px; }
-
- // Background colors
- &.bg1 { background-color: $identicon-red; }
- &.bg2 { background-color: $identicon-purple; }
- &.bg3 { background-color: $identicon-indigo; }
- &.bg4 { background-color: $identicon-blue; }
- &.bg5 { background-color: $identicon-teal; }
- &.bg6 { background-color: $identicon-orange; }
- &.bg7 { background-color: $gray-darker; }
-}
-
-.avatar-container {
- @extend .avatar-circle;
- overflow: hidden;
- display: flex;
-
- a {
- width: 100%;
- height: 100%;
- display: flex;
- text-decoration: none;
- }
-
- .avatar {
- border-radius: 0;
- border: 0;
- height: auto;
- width: 100%;
- margin: 0;
- align-self: center;
- }
-
- &.s40 { min-width: 40px;
- min-height: 40px; }
-
- &.s64 { min-width: 64px;
- min-height: 64px; }
-}
-
-.rect-avatar {
- border-radius: $border-radius-small;
- &.s16 { border-radius: $border-radius-small; }
- &.s18 { border-radius: $border-radius-small; }
- &.s19 { border-radius: $border-radius-small; }
- &.s20 { border-radius: $border-radius-small; }
- &.s24 { border-radius: $border-radius-default; }
- &.s26 { border-radius: $border-radius-default; }
- &.s32 { border-radius: $border-radius-default; }
- &.s36 { border-radius: $border-radius-default; }
- &.s40 { border-radius: $border-radius-default; }
- &.s46 { border-radius: $border-radius-default; }
- &.s48 { border-radius: $border-radius-large; }
- &.s60 { border-radius: $border-radius-large; }
- &.s64 { border-radius: $border-radius-large; }
- &.s70 { border-radius: $border-radius-large; }
- &.s90 { border-radius: $border-radius-large; }
- &.s96 { border-radius: $border-radius-large; }
- &.s100 { border-radius: $border-radius-large; }
- &.s110 { border-radius: $border-radius-large; }
- &.s140 { border-radius: $border-radius-large; }
- &.s160 { border-radius: $border-radius-large; }
-}
-
-.avatar-counter {
- background-color: $gray-darkest;
- color: $white-light;
- border: 1px solid $gray-normal;
- border-radius: 1em;
- font-family: $regular-font;
- font-size: 9px;
- line-height: 16px;
- text-align: center;
-}
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 7d5bd7a81ad..7760c48cb92 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -151,8 +151,7 @@
outline: 0;
.award-control-icon svg {
- background: $award-emoji-positive-add-bg;
- fill: $award-emoji-positive-add-lines;
+ fill: $blue-500;
}
.award-control-icon-neutral {
@@ -233,9 +232,6 @@
height: $default-icon-size;
width: $default-icon-size;
border-radius: 50%;
- }
-
- path {
fill: $gray-700;
}
}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 3aabb66f7a6..65c0ee74c60 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -199,6 +199,7 @@
&.user-cover-block {
padding: 24px 0 0;
+ border-bottom: 1px solid $border-color;
.nav-links {
width: 100%;
@@ -232,14 +233,6 @@
margin-top: -1px;
}
-.nav-block {
- .controls {
- float: right;
- margin-top: 8px;
- padding-bottom: 8px;
- }
-}
-
.content-block {
padding: $gl-padding 0;
border-bottom: 1px solid $white-dark;
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index ab8f397f3a0..767832e242c 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -1,12 +1,12 @@
@mixin btn-comment-icon {
border-radius: 50%;
background: $white-light;
- padding: 1px 5px;
+ padding: 1px;
font-size: 12px;
color: $blue-500;
+ border: 1px solid $blue-500;
width: 24px;
height: 24px;
- border: 1px solid $blue-500;
&:hover,
&.inverted {
@@ -21,7 +21,7 @@
}
@mixin btn-default {
- border-radius: 3px;
+ border-radius: $border-radius-default;
font-size: $gl-font-size;
font-weight: $gl-font-weight-normal;
padding: $gl-vert-padding $gl-btn-padding;
@@ -37,7 +37,7 @@
@include btn-default;
}
-@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border) {
+@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border, $active-text) {
background-color: $background;
color: $text;
border-color: $border;
@@ -61,13 +61,22 @@
}
}
+ &:focus {
+ box-shadow: 0 0 4px 1px $blue-300;
+ }
+
&:active {
background-color: $active-background;
border-color: $active-border;
- color: $hover-text;
+ box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
+ color: $active-text;
> .icon {
- color: $hover-text;
+ color: $active-text;
+ }
+
+ &:focus {
+ box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
}
}
}
@@ -164,21 +173,21 @@
&.btn-inverted {
&.btn-success {
- @include btn-outline($white-light, $green-600, $green-500, $green-500, $white-light, $green-600, $green-600, $green-700);
+ @include btn-outline($white-light, $green-600, $green-500, $green-100, $green-700, $green-500, $green-200, $green-600, $green-800);
}
&.btn-remove,
&.btn-danger {
- @include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
+ @include btn-outline($white-light, $red-500, $red-500, $red-100, $red-700, $red-500, $red-200, $red-600, $red-800);
}
&.btn-warning {
- @include btn-outline($white-light, $orange-500, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700);
+ @include btn-outline($white-light, $orange-500, $orange-500, $orange-100, $orange-700, $orange-500, $orange-200, $orange-600, $orange-800);
}
&.btn-primary,
&.btn-info {
- @include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
+ @include btn-outline($white-light, $blue-500, $blue-500, $blue-100, $blue-700, $blue-500, $blue-200, $blue-600, $blue-800);
}
}
@@ -193,11 +202,11 @@
&.btn-close,
&.btn-close-color {
- @include btn-outline($white-light, $orange-600, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700);
+ @include btn-outline($white-light, $orange-600, $orange-500, $orange-100, $orange-700, $orange-500, $orange-200, $orange-600, $orange-800);
}
&.btn-spam {
- @include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
+ @include btn-outline($white-light, $red-500, $red-500, $red-100, $red-700, $red-500, $red-200, $red-600, $red-800);
}
&.btn-danger,
@@ -240,7 +249,7 @@
padding: 6px 16px;
border-color: $border-color;
color: $gray-darkest;
- background-color: $gray-light;
+ background-color: $white-light;
&:hover,
&:active,
@@ -249,7 +258,6 @@
box-shadow: none;
border-color: lighten($blue-300, 20%);
color: $gray-darkest;
- background-color: $gray-light;
}
}
@@ -330,6 +338,8 @@
svg {
top: auto;
+ width: 16px;
+ height: 16px;
}
}
@@ -402,7 +412,7 @@
.btn-inverted {
&-secondary {
- @include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
+ @include btn-outline($white-light, $blue-500, $blue-500, $blue-100, $blue-700, $blue-500, $blue-200, $blue-600, $blue-800);
}
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index fc488b85138..db09118ba15 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -489,3 +489,50 @@ img.emoji {
.cursor-pointer {
cursor: pointer;
}
+
+// Make buttons/dropdowns full-width on mobile
+.full-width-mobile {
+ @include media-breakpoint-down(xs) {
+ width: 100%;
+
+ > .dropdown-menu,
+ > .btn {
+ width: 100%;
+ }
+ }
+}
+
+.onboarding-helper-container {
+ bottom: 40px;
+ right: 40px;
+ font-size: $gl-font-size-small;
+ background: $gray-100;
+ width: 200px;
+ border-radius: 24px;
+ box-shadow: 0 2px 4px $issue-boards-card-shadow;
+ z-index: 10000;
+
+ .collapsible {
+ max-height: 0;
+ transition: max-height 0.5s cubic-bezier(0, 1, 0, 1);
+ }
+
+ &.expanded {
+ border-bottom-right-radius: $border-radius-default;
+ border-bottom-left-radius: $border-radius-default;
+
+ .collapsible {
+ max-height: 1000px;
+ transition: max-height 1s ease-in-out;
+ }
+ }
+
+ .avatar {
+ border-color: darken($gray-normal, 10%);
+
+ img {
+ width: 32px;
+ height: 32px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 8fb4027bf97..cd951f67293 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -570,10 +570,10 @@
}
.dropdown-menu-close {
- right: 5px;
+ top: $gl-padding-4;
+ right: $gl-padding-8;
width: 20px;
height: 20px;
- top: -1px;
}
.dropdown-menu-close-icon {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 53d3645cd63..536a26a6ffe 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -241,6 +241,7 @@
*/
&.code {
padding: 0;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
}
.list-inline.previews {
@@ -328,7 +329,7 @@ span.idiff {
background-color: $gray-light;
border-bottom: 1px solid $border-color;
border-top: 1px solid $border-color;
- padding: 5px $gl-padding;
+ padding: $gl-padding-8 $gl-padding;
margin: 0;
border-radius: $border-radius-default $border-radius-default 0 0;
@@ -365,10 +366,6 @@ span.idiff {
color: $gl-text-color;
}
- small {
- margin: 0 10px 0 0;
- }
-
.file-actions .btn {
padding: 0 10px;
font-size: 13px;
@@ -456,6 +453,28 @@ span.idiff {
}
}
+ .note-container {
+ .user-avatar-link.new-comment {
+ position: absolute;
+ margin: 40px $gl-padding 0 116px;
+
+ ~ .note-edit-form form.edit-note {
+ @include media-breakpoint-up(sm) {
+ margin-left: $note-icon-gutter-width;
+ }
+ }
+ }
+ }
+
+ .diff-discussions:not(:last-child) .discussion .discussion-body {
+ padding-bottom: $gl-padding;
+
+ .discussion-reply-holder {
+ border-bottom: 1px solid $gray-100;
+ border-radius: 0;
+ }
+ }
+
.md-previewer {
padding: $gl-padding;
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 5bcfd5d1322..26cbb7f5c13 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -218,7 +218,7 @@
min-width: 200px;
padding-right: 25px;
padding-left: 0;
- height: $input-height;
+ height: $input-height - 2;
line-height: inherit;
border-color: transparent;
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index afa85f0e4ae..e3dd127366d 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -6,6 +6,19 @@
position: relative;
z-index: 1;
+ .flash-notice,
+ .flash-alert,
+ .flash-success,
+ .flash-warning {
+ border-radius: $border-radius-default;
+ color: $white-light;
+
+ .container-fluid,
+ .container-fluid.container-limited {
+ background: transparent;
+ }
+ }
+
.flash-notice {
@extend .alert;
background-color: $blue-500;
@@ -28,7 +41,8 @@
.flash-warning {
@extend .alert;
- background-color: $orange-500;
+ background-color: $orange-100;
+ color: $orange-900;
margin: 0;
}
@@ -60,19 +74,6 @@
margin: 0;
}
- .flash-notice,
- .flash-alert,
- .flash-success,
- .flash-warning {
- border-radius: $border-radius-default;
- color: $white-light;
-
- .container-fluid,
- .container-fluid.container-limited {
- background: transparent;
- }
- }
-
&.flash-container-page {
margin-bottom: 0;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index d0f99c2df7e..2a601afff53 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -27,10 +27,16 @@ input[type='text'].danger {
}
label {
+ font-weight: $gl-font-weight-bold;
+
&.inline-label {
margin: 0;
}
+ &.form-check-label {
+ font-weight: $gl-font-weight-normal;
+ }
+
&.label-bold {
font-weight: $gl-font-weight-bold;
}
@@ -41,14 +47,6 @@ label {
margin: 0;
}
-.form-label {
- @extend label;
-}
-
-.form-control-label {
- @extend .col-md-2;
-}
-
.inline-input-group {
width: 250px;
}
@@ -81,44 +79,14 @@ label {
margin-left: 0;
margin-right: 0;
- .form-control-label {
- font-weight: $gl-font-weight-bold;
- padding-top: 4px;
- }
-
.form-control {
height: 29px;
background: $white-light;
font-family: $monospace-font;
}
- .input-group-prepend .btn,
- .input-group-append .btn {
- padding: 3px $gl-btn-padding;
- background-color: $gray-light;
- border: 1px solid $border-color;
- }
-
- .text-block {
- line-height: 0.8;
- padding-top: 9px;
-
- code {
- line-height: 1.8;
- }
-
- img {
- margin-right: $gl-padding;
- }
- }
-
@include media-breakpoint-down(xs) {
padding: 0 $gl-padding;
-
- .form-control-label,
- .text-block {
- padding-left: 0;
- }
}
}
@@ -140,19 +108,6 @@ label {
}
}
-.select-wrapper {
- position: relative;
-
- .fa-chevron-down {
- position: absolute;
- font-size: 10px;
- right: 10px;
- top: 12px;
- color: $gray-darkest;
- pointer-events: none;
- }
-}
-
.select-control {
padding-left: 10px;
padding-right: 10px;
@@ -175,12 +130,6 @@ label {
margin-top: 35px;
}
-.form-group .form-control-label,
-.form-group .form-control-label-full-width {
- font-weight: $gl-font-weight-normal;
-}
-
-
.form-control::placeholder {
color: $gl-text-color-tertiary;
}
@@ -224,7 +173,8 @@ label {
border: 1px solid $green-600;
&:focus {
- box-shadow: 0 0 0 1px $green-600 inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $green-600;
+ box-shadow: 0 0 0 1px $green-600 inset, 0 1px 1px $gl-field-focus-shadow inset,
+ 0 0 4px 0 $green-600;
border: 0 none;
}
}
@@ -233,7 +183,8 @@ label {
border: 1px solid $red-500;
&:focus {
- box-shadow: 0 0 0 1px $red-500 inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $gl-field-focus-shadow-error;
+ box-shadow: 0 0 0 1px $red-500 inset, 0 1px 1px $gl-field-focus-shadow inset,
+ 0 0 4px 0 $gl-field-focus-shadow-error;
border: 0 none;
}
}
@@ -259,16 +210,26 @@ label {
}
}
-.input-icon-wrapper {
+.input-icon-wrapper,
+.select-wrapper {
position: relative;
+}
- .input-icon-right {
- position: absolute;
- right: 0.8em;
- top: 50%;
- transform: translateY(-50%);
- color: $gray-600;
- }
+.select-wrapper > .fa-chevron-down {
+ position: absolute;
+ font-size: 10px;
+ right: 10px;
+ top: 12px;
+ color: $gray-darkest;
+ pointer-events: none;
+}
+
+.input-icon-wrapper > .input-icon-right {
+ position: absolute;
+ right: 0.8em;
+ top: 50%;
+ transform: translateY(-50%);
+ color: $gray-600;
}
.input-md {
@@ -280,3 +241,21 @@ label {
max-width: $input-lg-width;
width: 100%;
}
+
+.input-group-text {
+ max-height: $input-height;
+}
+
+.gl-form-checkbox {
+ align-items: baseline;
+
+ &.form-check-inline .form-check-input {
+ align-self: flex-start;
+ margin-right: $gl-padding-8;
+ height: 1.5 * $gl-font-size;
+ }
+
+ .help-text {
+ margin-bottom: 0;
+ }
+}
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 946f575ac13..741f92110c3 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -8,7 +8,7 @@
pre {
padding: 10px 0;
border: 0;
- border-radius: 0;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
font-family: $monospace-font;
font-size: $code-font-size;
line-height: 19px;
@@ -42,6 +42,7 @@
padding: 10px;
text-align: right;
float: left;
+ border-bottom-left-radius: $border-radius-default;
a {
font-family: $monospace-font;
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index e51f230a680..1a38f3ccce4 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -16,10 +16,10 @@
margin-top: 5px;
}
- border-radius: 3px;
+ border-radius: $border-radius-default;
display: block;
float: left;
- margin-right: 10px;
+ margin-right: $gl-padding-8;
color: $white-light;
font-size: $gl-font-size;
line-height: $gl-line-height-24;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 298610a0631..555a3fe0dc7 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -177,14 +177,6 @@ ul.content-list {
}
}
- .member-controls {
- float: none;
-
- @include media-breakpoint-up(sm) {
- float: right;
- }
- }
-
// When dragging a list item
&.ui-sortable-helper {
border-bottom: 0;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 97de0c98325..ad5096761cd 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -218,16 +218,22 @@
}
}
-@mixin build-trace-top-bar($height) {
+// Used in EE for Web Terminal
+@mixin build-trace-bar($height) {
height: $height;
min-height: $height;
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
+ padding: $grid-size;
+}
+
+@mixin build-trace-top-bar($height) {
+ @include build-trace-bar($height);
+
position: -webkit-sticky;
position: sticky;
top: $header-height;
- padding: $grid-size;
.with-performance-bar & {
top: $header-height + $performance-bar-height;
@@ -376,3 +382,28 @@
}
}
}
+
+/*
+* Mixin that handles the size and right margin of avatars.
+*/
+@mixin avatar-size($size, $margin-right) {
+ width: $size;
+ height: $size;
+ margin-right: $margin-right;
+}
+
+@mixin code-icon-size() {
+ width: $gl-font-size * $code-line-height * 0.9;
+ height: $gl-font-size * $code-line-height * 0.9;
+}
+
+@mixin avatar-counter($border-radius: 1em) {
+ background-color: $gray-darkest;
+ color: $white-light;
+ border: 1px solid $gray-normal;
+ border-radius: $border-radius;
+ font-family: $regular-font;
+ font-size: 9px;
+ line-height: 16px;
+ text-align: center;
+}
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index 31297b9d20c..ada8f2fe1a6 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -13,8 +13,8 @@
a,
button {
- padding: $gl-btn-padding;
- padding-bottom: 11px;
+ padding: $gl-padding-8;
+ padding-bottom: $gl-padding-8 + 1;
font-size: 14px;
line-height: 28px;
color: $gl-text-color-secondary;
@@ -58,8 +58,12 @@
}
.top-area {
- @include clearfix;
border-bottom: 1px solid $border-color;
+ display: flex;
+
+ @include media-breakpoint-down(md) {
+ flex-flow: column-reverse wrap;
+ }
.nav-text {
padding-top: 16px;
@@ -75,9 +79,8 @@
}
.nav-links {
- margin-bottom: 0;
border-bottom: 0;
- float: left;
+ flex: 1;
&.wide {
width: 100%;
@@ -98,16 +101,23 @@
&.mobile-separator {
border-bottom: 1px solid $border-color;
+ margin-bottom: $gl-padding-8;
}
}
}
.nav-controls {
display: inline-block;
- float: right;
text-align: right;
- padding: $gl-padding-8 0;
- margin-bottom: 0;
+
+ @include media-breakpoint-down(sm) {
+ margin-top: $gl-padding-8;
+ }
+
+ @include media-breakpoint-up(md) {
+ display: flex;
+ align-items: center;
+ }
> .btn,
> .btn-container,
@@ -115,8 +125,6 @@
> input,
> form {
margin-right: $gl-padding-top;
- display: inline-block;
- vertical-align: top;
&:last-child {
margin-right: 0;
@@ -143,7 +151,7 @@
@include media-breakpoint-up(lg) { width: 250px; }
}
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
padding-bottom: 0;
width: 100%;
@@ -153,7 +161,7 @@
.dropdown-toggle,
.dropdown-menu-toggle,
.form-control {
- margin: 0 0 10px;
+ margin: 0 0 $gl-padding-8;
display: block;
width: 100%;
}
@@ -165,7 +173,7 @@
form {
display: block;
height: auto;
- margin-bottom: 14px;
+ margin-bottom: $gl-padding-8;
input {
width: 100%;
@@ -236,20 +244,11 @@
width: 100%;
}
- @include media-breakpoint-down(xs) {
- flex-flow: row wrap;
-
+ @include media-breakpoint-down(md) {
.nav-controls {
$controls-margin: $btn-margin-5 - 2px;
flex: 0 0 100%;
-
- &.controls-flex {
- display: flex;
- flex-flow: row wrap;
- align-items: center;
- justify-content: center;
- padding: 0 0 $gl-padding-top;
- }
+ margin-top: $gl-padding-8;
.controls-item,
.controls-item-full,
@@ -326,8 +325,8 @@
.fade-right,
.fade-left {
- top: 16px;
- bottom: auto;
+ bottom: $gl-padding;
+ top: auto;
}
&.is-smaller {
@@ -367,6 +366,7 @@
display: flex;
border-bottom: 1px solid $border-color;
overflow: hidden;
+ align-items: center;
.nav-links {
border-bottom: 0;
diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss
index 36ab38f1c9d..3ab83f4c8e6 100644
--- a/app/assets/stylesheets/framework/snippets.scss
+++ b/app/assets/stylesheets/framework/snippets.scss
@@ -22,6 +22,10 @@
.snippet-file-content {
border-radius: 3px;
+
+ .file-title-flex-parent .btn-clipboard {
+ line-height: 28px;
+ }
}
.snippet-header {
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index e8176e59c19..42a739e88f7 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -42,8 +42,8 @@
}
}
- .avatar {
- margin-right: 15px;
+ img.avatar {
+ margin-right: $gl-padding;
}
.controls {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 7c152efd9c7..9e1431963d9 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -403,6 +403,7 @@ code {
.git-revision-dropdown .dropdown-content ul li a {
@extend .ref-name;
+ word-break: break-all;
}
/**
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 1cf122102cc..dc451a97e17 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -381,6 +381,7 @@ $breadcrumb-min-height: 48px;
$home-panel-title-row-height: 64px;
$home-panel-avatar-mobile-size: 24px;
$gl-line-height: 16px;
+$gl-line-height-20: 20px;
$gl-line-height-24: 24px;
$gl-line-height-14: 14px;
@@ -589,6 +590,7 @@ $issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards
*/
$avatar-radius: 50%;
$gl-avatar-size: 40px;
+$gl-avatar-border-opacity: 0.1;
/*
* Blame
@@ -639,6 +641,7 @@ $input-lg-width: 320px;
*/
$document-index-color: #888;
$help-shortcut-header-color: #333;
+$accepting-mr-label-color: #69d100;
/*
* Issues
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index fb4d3f23cd9..ea96381a098 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -7,6 +7,7 @@ $secondary: $gray-light;
$input-disabled-bg: $gray-light;
$input-border-color: $gray-200;
$input-color: $gl-text-color;
+$input-font-size: $gl-font-size;
$font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font;
$btn-line-height: 20px;
diff --git a/app/assets/stylesheets/page_bundles/_ide_mixins.scss b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
index 896a3466cb4..9465dd5bed6 100644
--- a/app/assets/stylesheets/page_bundles/_ide_mixins.scss
+++ b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
@@ -2,17 +2,17 @@
display: flex;
flex-direction: column;
height: 100%;
- margin-top: -$grid-size;
- margin-bottom: -$grid-size;
- &.build-page .top-bar {
+ .top-bar {
+ @include build-trace-bar(35px);
+
top: 0;
- height: auto;
font-size: 12px;
border-top-right-radius: $border-radius-default;
- }
-
- .top-bar {
margin-left: -$gl-padding;
+
+ .controllers {
+ @include build-controllers(15px, center, false, 0, inline, 0);
+ }
}
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 0c1067bfacc..f08fa80495d 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -719,7 +719,7 @@ $ide-commit-header-height: 48px;
border: 1px solid $white-dark;
}
-.ide-commit-radios {
+.ide-commit-options {
label {
font-weight: normal;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 09ff518bbdf..5e3652db48f 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -31,14 +31,15 @@
width: 320px;
.dropdown-content {
- max-height: 162px;
+ max-height: 140px;
}
}
.issue-board-dropdown-content {
- margin: 0 8px 10px;
- padding-bottom: 10px;
- border-bottom: 1px solid $dropdown-divider-bg;
+ margin: 0;
+ padding: $gl-padding-4 $gl-padding $gl-padding;
+ border-bottom: 0;
+ color: $gl-text-color-secondary;
}
.issue-boards-page {
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index 809ba6d4953..255383d89c8 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -69,6 +69,8 @@
align-self: flex-start;
font-weight: 500;
font-size: 20px;
+ color: $orange-900;
+ opacity: 1;
margin: $gl-padding-8 14px 0 0;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 66cd113db84..e12ea6fcb99 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -154,8 +154,6 @@
}
.avatar-cell {
- width: 46px;
-
img {
margin-right: 0;
}
@@ -182,6 +180,14 @@
display: flex;
align-items: center;
}
+
+ .committer {
+ color: $gl-text-color-tertiary;
+
+ .commit-author-link {
+ color: $gl-text-color;
+ }
+ }
}
.commit-actions {
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index cb5f1a84005..c386493231c 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -21,7 +21,6 @@
.detail-page-header-body {
position: relative;
- line-height: 35px;
display: flex;
flex: 1 1;
min-width: 0;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index b2b3720fdde..4ebf1019456 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -15,7 +15,6 @@
position: sticky;
top: $mr-file-header-top;
z-index: 102;
- height: $mr-version-controls-height;
&::before {
content: '';
@@ -494,6 +493,12 @@ table.code {
}
}
+ .line_holder:last-of-type {
+ td:first-child {
+ border-bottom-left-radius: $border-radius-default;
+ }
+ }
+
&.left-side-selected {
td.line_content.parallel.right-side {
user-select: none;
@@ -609,10 +614,9 @@ table.code {
.diff-comment-avatar-holders {
position: absolute;
- height: 19px;
- width: 19px;
- margin-left: -15px;
+ margin-left: -$gl-padding;
z-index: 100;
+ @include code-icon-size();
&:hover {
.diff-comment-avatar,
@@ -646,40 +650,45 @@ table.code {
.diff-comments-more-count {
position: absolute;
left: 0;
- width: 19px;
- height: 19px;
margin-right: 0;
border-color: $white-light;
cursor: pointer;
transition: all 0.1s ease-out;
+ @include code-icon-size();
@for $i from 1 through 4 {
&:nth-child(#{$i}) {
z-index: (4 - $i);
}
}
+
+ .avatar {
+ @include code-icon-size();
+ }
}
.diff-comments-more-count {
- width: 19px;
- min-width: 19px;
padding-left: 0;
padding-right: 0;
overflow: hidden;
+ @include code-icon-size();
}
.diff-comments-more-count,
.diff-notes-collapse {
- @extend .avatar-counter;
+ @include avatar-counter(50%);
}
.diff-notes-collapse {
- width: 24px;
- height: 24px;
+ border: 0;
border-radius: 50%;
padding: 0;
transition: transform 0.1s ease-out;
z-index: 100;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ @include code-icon-size();
.collapse-icon {
height: 50%;
@@ -999,6 +1008,10 @@ table.code {
display: block;
}
}
+
+ .note-edit-form {
+ margin-left: $note-icon-gutter-width;
+ }
}
.discussion-body .image .frame {
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index e34628002ac..500f5816d38 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -8,7 +8,7 @@
border-bottom: 1px solid $white-normal;
color: $gl-text-color-secondary;
position: relative;
- line-height: $gl-line-height;
+ line-height: $gl-line-height-20;
.system-note-image {
position: absolute;
@@ -48,7 +48,7 @@
}
.event-user-info {
- margin-bottom: $gl-padding-8;
+ margin-bottom: $gl-padding-4;
.author_name {
a {
@@ -67,7 +67,7 @@
}
.event-body {
- margin-top: $gl-padding-8;
+ margin-top: $gl-padding-4;
margin-right: 174px;
color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 0a07747e0d4..656202f4e58 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -35,9 +35,6 @@
}
.group-nav-container .nav-controls {
- align-items: flex-start;
- padding: $gl-padding-top 0 0;
-
.group-filter-form {
flex: 1 1 auto;
margin-right: $gl-padding-8;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 04c66006027..dcbb23684d1 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -69,7 +69,11 @@
}
.emoji-block {
- padding: 10px 0;
+ padding: $gl-padding-4 0;
+
+ @include media-breakpoint-down(md) {
+ padding: $gl-padding-8 0;
+ }
}
}
@@ -132,6 +136,10 @@
z-index: 200;
overflow: hidden;
+ @include media-breakpoint-down(sm) {
+ z-index: 251;
+ }
+
a:not(.btn) {
color: inherit;
@@ -218,7 +226,7 @@
.title {
color: $gl-text-color;
- margin-bottom: 10px;
+ margin-bottom: $gl-padding-8;
line-height: 1;
.avatar {
@@ -604,7 +612,6 @@
.participants-list {
display: flex;
flex-wrap: wrap;
- margin: -7px;
}
.user-list {
@@ -614,7 +621,7 @@
.participants-author {
display: inline-block;
- padding: 7px;
+ padding: 0 $gl-padding-8 $gl-padding-8 0;
&:nth-of-type(7n) {
padding-right: 0;
@@ -641,7 +648,6 @@
.participants-more,
.user-list-more {
- margin-top: 5px;
margin-left: 5px;
a,
@@ -676,8 +682,7 @@
justify-content: center;
align-items: center;
margin-top: 0;
- padding-left: 9px;
- padding-right: 9px;
+ padding: 0 $gl-padding-8;
@include media-breakpoint-up(sm) {
display: inline-block;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index c7d2369a6b8..48289c8f381 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -258,8 +258,15 @@ ul.related-merge-requests > li {
}
}
-.discussion-reply-holder .note-edit-form {
- display: block;
+.discussion-reply-holder {
+ .avatar-note-form-holder .note-edit-form {
+ display: block;
+ margin-left: $note-icon-gutter-width;
+
+ @include media-breakpoint-down(xs) {
+ margin-left: 0;
+ }
+ }
}
.issue-sort-dropdown {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 13288d8bad1..11e8a32389f 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -456,8 +456,9 @@
// Don't hide the overflow in system messages
.system-note-message,
-.issuable-detail,
+.issuable-details,
.md-preview-holder,
+.referenced-commands,
.note-body {
.scoped-label-wrapper {
.badge {
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 22a515cbdaa..d8aabecc036 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -21,13 +21,6 @@
color: $login-brand-holder-color;
}
- h1:first-child {
- font-weight: $gl-font-weight-normal;
- margin-bottom: 0.68em;
- margin-top: 0;
- font-size: 34px;
- }
-
h3 {
font-size: 22px;
}
@@ -49,8 +42,8 @@
.login-box,
.omniauth-container {
box-shadow: 0 0 0 1px $border-color;
- border-bottom-right-radius: $border-radius-small;
- border-bottom-left-radius: $border-radius-small;
+ border-bottom-right-radius: $border-radius;
+ border-bottom-left-radius: $border-radius;
padding: 15px;
.login-heading h3 {
@@ -80,7 +73,8 @@
.login-body {
font-size: 13px;
- input + p {
+ input + p,
+ input ~ p.field-validation {
margin-top: 5px;
}
@@ -95,7 +89,7 @@
}
.omniauth-container {
- border-radius: $border-radius-small;
+ border-radius: $border-radius;
font-size: 13px;
p {
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index f8e273a2735..68af01f9ccc 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -20,17 +20,6 @@
}
}
- .list-item-name {
- @include media-breakpoint-up(sm) {
- float: left;
- width: 50%;
- }
-
- strong {
- font-weight: $gl-font-weight-bold;
- }
- }
-
.controls {
@include media-breakpoint-up(sm) {
display: flex;
@@ -43,10 +32,11 @@
.form-group {
margin-bottom: 0;
+ }
- @include media-breakpoint-down(sm) {
- display: block;
- margin-left: 5px;
+ .member-controls {
+ .fa {
+ line-height: inherit;
}
}
@@ -66,23 +56,12 @@
}
.member-form-control {
- @include media-breakpoint-down(sm) {
- width: $dropdown-member-form-control-width;
- margin-left: 0;
- padding-bottom: 5px;
- }
-
@include media-breakpoint-down(xs) {
margin-right: 0;
width: auto;
}
}
-.member-access-text {
- margin-left: auto;
- line-height: 43px;
-}
-
.member-search-form {
position: relative;
@@ -221,9 +200,6 @@
}
.content-list.members-list li {
- display: flex;
- justify-content: space-between;
-
.list-item-name {
float: none;
display: flex;
@@ -252,33 +228,24 @@
align-self: flex-start;
}
+ @include media-breakpoint-down(sm) {
+ .member-access-text {
+ margin: 0 0 $gl-padding-4 ($grid-size * 6);
+ }
+ }
+
@include media-breakpoint-down(xs) {
display: block;
- .controls > .btn {
- margin-left: 0;
- margin-right: 0;
+ .controls > .btn,
+ .controls .member-form-control {
+ margin: 0 0 $gl-padding-8;
display: block;
}
- .controls > .btn:last-child {
- margin-left: 5px;
- margin-right: 5px;
- width: auto;
- }
-
.form-control {
width: 100%;
}
-
- .member-access-text {
- line-height: 0;
- margin-left: 50px;
- }
-
- .member-controls {
- margin-top: 5px;
- }
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 44b558dd5ff..8cb3fab74e0 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -87,6 +87,11 @@
padding: $gl-padding;
}
+.mr-widget-info {
+ padding-left: $gl-padding-50 - $gl-padding-32;
+ padding-right: $gl-padding;
+}
+
.mr-state-widget {
color: $gl-text-color;
@@ -180,46 +185,6 @@
}
}
}
-
- .accept-control {
- display: inline-block;
- float: left;
- margin: 0;
- margin-left: 20px;
- padding: 5px;
- padding-top: 8px;
- line-height: 20px;
-
- &.right {
- float: right;
- padding-right: 0;
- }
-
- .modify-merge-commit-link {
- padding: 0;
- background-color: transparent;
- border: 0;
- color: $gl-text-color;
-
- &:hover,
- &:focus {
- text-decoration: underline;
- }
- }
-
- .merge-param-checkbox {
- margin: 0;
- }
-
- a .fa-question-circle {
- color: $gl-text-color-secondary;
-
- &:hover,
- &:focus {
- color: $link-hover-color;
- }
- }
- }
}
.ci-widget {
@@ -402,12 +367,6 @@
width: 100%;
text-align: center;
}
-
- .accept-control {
- width: 100%;
- text-align: center;
- margin: 0;
- }
}
.commit-message-editor {
@@ -560,6 +519,10 @@
.mr-links {
padding-left: $status-icon-size + $gl-btn-padding;
+
+ &:last-child {
+ padding-bottom: $gl-padding;
+ }
}
.mr-info-list {
@@ -883,15 +846,40 @@
display: flex;
justify-content: space-between;
- @include media-breakpoint-down(sm) {
- flex-direction: column-reverse;
+ @include media-breakpoint-down(xs) {
+ .discussion-filter-container,
+ .line-resolve-all-container {
+ margin-bottom: $gl-padding-4;
+ }
}
.discussion-filter-container {
- margin-top: $gl-padding-8;
-
&:not(:only-child) {
- padding-right: $gl-padding-8;
+ margin: $gl-padding-4;
+ }
+ }
+
+ .merge-request-tabs {
+ height: $grid-size * 6;
+ }
+}
+
+// Wrap MR tabs/buttons so you don't have to scroll on desktop
+@include media-breakpoint-down(md) {
+ .merge-request-tabs-container,
+ .epic-tabs-container {
+ flex-direction: column-reverse;
+ padding-top: $gl-padding-8;
+ }
+}
+
+@include media-breakpoint-down(lg) {
+ .right-sidebar-expanded {
+ .merge-request-tabs-container,
+ .epic-tabs-container {
+ flex-direction: column-reverse;
+ align-items: flex-start;
+ padding-top: $gl-padding-8;
}
}
}
@@ -1030,11 +1018,6 @@
background: $black-transparent;
}
-.source-branch-removal-status {
- padding-left: 50px;
- padding-bottom: $gl-padding;
-}
-
.mr-compare {
.diff-file .file-title-flex-parent {
top: $header-height + 51px;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 3343b55d24b..c6bac33e888 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -59,6 +59,7 @@
border-radius: $border-radius-base;
transition: border-color ease-in-out 0.15s,
box-shadow ease-in-out 0.15s;
+ background-color: $white-light;
&.is-focused {
@extend .form-control:focus;
@@ -103,6 +104,11 @@
margin: auto;
align-items: center;
+ a {
+ color: $orange-600;
+ text-decoration: underline;
+ }
+
.icon {
margin-right: $issuable-warning-icon-margin;
vertical-align: text-bottom;
@@ -168,6 +174,16 @@
.discussion-form {
background-color: $white-light;
+
+ @include media-breakpoint-down(xs) {
+ .user-avatar-link {
+ display: none;
+ }
+
+ .note-edit-form {
+ margin-left: 0;
+ }
+ }
}
table {
@@ -234,13 +250,25 @@ table {
.diff-file,
.commit-diff {
.discussion-reply-holder {
- background-color: $white-light;
+ background-color: $gray-light;
border-radius: 0 0 3px 3px;
padding: $gl-padding;
+ border-top: 1px solid $gray-100;
+
+ + .new-note {
+ background-color: $gray-light;
+ border-top: 1px solid $gray-100;
+ }
&.is-replying {
padding-bottom: $gl-padding;
}
+
+ .user-avatar-link {
+ img {
+ margin-top: -3px;
+ }
+ }
}
}
@@ -334,7 +362,7 @@ table {
.toolbar-button-icon {
position: relative;
top: 1px;
- margin-right: 3px;
+ margin-right: $gl-padding-4;
color: inherit;
font-size: 16px;
}
@@ -461,6 +489,15 @@ table {
border: 0;
font-size: 14px;
line-height: 16px;
+
+ &:hover,
+ &:focus {
+ text-decoration: none;
+
+ .text-attach-file {
+ text-decoration: underline;
+ }
+ }
}
.markdown-selector {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 50c87e55f56..5cacd42bf0d 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -80,21 +80,17 @@ $note-form-margin-left: 72px;
}
}
- li.note {
- border-bottom: 1px solid $border-color;
- }
-
.replies-toggle {
background-color: $gray-light;
padding: $gl-padding-8 $gl-padding;
+ border-top: 1px solid $gray-100;
+ border-bottom: 1px solid $gray-100;
.collapse-replies-btn:hover {
color: $blue-600;
}
&.expanded {
- border-bottom: 1px solid $border-color;
-
span {
cursor: pointer;
}
@@ -211,8 +207,13 @@ $note-form-margin-left: 72px;
display: none;
}
+ .user-avatar-link img {
+ margin-top: $gl-padding-8;
+ }
+
.note-edit-form {
display: block;
+ margin-left: 0;
&.current-note-edit-form + .note-awards {
display: none;
@@ -264,8 +265,8 @@ $note-form-margin-left: 72px;
}
.system-note {
- padding: 6px 21px;
- margin: $gl-padding-24 0;
+ padding: $gl-padding-4 20px;
+ margin: $gl-padding 0;
background-color: transparent;
.note-header-info {
@@ -364,7 +365,7 @@ $note-form-margin-left: 72px;
height: $system-note-icon-size;
border: 1px solid $border-color;
border-radius: $system-note-icon-size;
- margin: -6px $gl-padding 0 0;
+ margin: -6px 20px 0 0;
svg {
width: $system-note-svg-size;
@@ -430,14 +431,16 @@ $note-form-margin-left: 72px;
.notes > .note-discussion li.note.system-note {
border-bottom: 0;
- padding: 0 $gl-padding;
+ padding: 0;
}
}
.diff-file {
.is-over {
.add-diff-note {
- display: inline-block;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
}
}
@@ -517,12 +520,30 @@ $note-form-margin-left: 72px;
}
}
-.commit-diff {
- .notes-content {
- background-color: $white-light;
+.code-commit .notes-content,
+.diff-viewer > .image ~ .note-container {
+ background-color: $white-light;
+
+ .avatar-note-form-holder {
+ .user-avatar-link img {
+ margin: 13px $gl-padding $gl-padding;
+ }
+
+ form,
+ ~ .discussion-form-container {
+ padding: $gl-padding;
+
+ @include media-breakpoint-up(sm) {
+ margin-left: $note-icon-gutter-width;
+ }
+ }
}
}
+.diff-viewer > .image ~ .note-container form.new-note {
+ margin-left: 0;
+}
+
.discussion-header,
.note-header-info {
a {
@@ -548,7 +569,7 @@ $note-form-margin-left: 72px;
}
.discussion-header {
- min-height: 72px;
+ min-height: 74px;
.note-header-info {
padding-bottom: 0;
@@ -561,8 +582,10 @@ $note-form-margin-left: 72px;
}
.unresolved {
- .note-header-info {
- margin-top: $gl-padding-8;
+ .discussion-header {
+ .note-header-info {
+ margin-top: $gl-padding-8;
+ }
}
}
@@ -741,7 +764,7 @@ $note-form-margin-left: 72px;
.add-diff-note {
@include btn-comment-icon;
opacity: 0;
- margin-left: -50px;
+ margin-left: -52px;
position: absolute;
top: 50%;
transform: translateY(-50%);
@@ -760,15 +783,13 @@ $note-form-margin-left: 72px;
background-color: $white-light;
}
- a {
+ a:not(.learn-more) {
color: $blue-600;
}
}
.line-resolve-all-container {
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
- margin-right: 0;
- }
+ margin: $gl-padding-4;
> div {
white-space: nowrap;
@@ -784,6 +805,8 @@ $note-form-margin-left: 72px;
}
.btn {
+ line-height: $gl-line-height;
+
svg {
fill: $gray-darkest;
}
@@ -809,10 +832,11 @@ $note-form-margin-left: 72px;
.line-resolve-all {
vertical-align: middle;
display: inline-block;
- padding: 6px 10px;
+ padding: $gl-padding-4 10px;
background-color: $gray-light;
border: 1px solid $border-color;
border-radius: $border-radius-default;
+ font-size: $gl-btn-small-font-size;
&.has-next-btn {
border-top-right-radius: 0;
@@ -822,11 +846,16 @@ $note-form-margin-left: 72px;
.line-resolve-btn {
margin-right: 5px;
+ color: $gray-darkest;
svg {
vertical-align: middle;
}
}
+
+ @include media-breakpoint-down(xs) {
+ flex: 1;
+ }
}
.line-resolve-btn {
@@ -836,7 +865,6 @@ $note-form-margin-left: 72px;
background-color: transparent;
border: 0;
outline: 0;
- color: $gray-darkest;
transition: color $general-hover-transition-duration $general-hover-transition-curve;
&.is-disabled {
@@ -900,10 +928,6 @@ $note-form-margin-left: 72px;
.diff-comment-form {
display: block;
}
-
- .add-diff-note svg {
- margin-top: 4px;
- }
}
.discussion-filter-container {
diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss
index e98cb444f0a..e1cbf0e6654 100644
--- a/app/assets/stylesheets/pages/notifications.scss
+++ b/app/assets/stylesheets/pages/notifications.scss
@@ -4,6 +4,34 @@
.dropdown-menu {
@extend .dropdown-menu-right;
}
+
+ @include media-breakpoint-down(sm) {
+ .notification-dropdown {
+ width: 100%;
+ }
+
+ .notification-form {
+ display: block;
+ }
+
+ .notifications-btn,
+ .btn-group {
+ width: 100%;
+ }
+
+ .table-section {
+ border-top: 0;
+ min-height: unset;
+
+ &:not(:first-child) {
+ padding-top: 0;
+ }
+ }
+
+ .update-notifications {
+ width: 100%;
+ }
+ }
}
.notification {
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 37071a57bb3..dbf600df9d6 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -261,3 +261,13 @@ input[type='checkbox']:hover {
color: $blue-600;
}
}
+
+// Disable webkit input icons, link to solution: https://stackoverflow.com/questions/9421551/how-do-i-remove-all-default-webkit-search-field-styling
+/* stylelint-disable property-no-vendor-prefix */
+input[type='search']::-webkit-search-decoration,
+input[type='search']::-webkit-search-cancel-button,
+input[type='search']::-webkit-search-results-button,
+input[type='search']::-webkit-search-results-decoration {
+ -webkit-appearance: none;
+}
+/* stylelint-enable */
diff --git a/app/controllers/acme_challenges_controller.rb b/app/controllers/acme_challenges_controller.rb
new file mode 100644
index 00000000000..67a39d8870b
--- /dev/null
+++ b/app/controllers/acme_challenges_controller.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AcmeChallengesController < ActionController::Base
+ def show
+ if acme_order
+ render plain: acme_order.challenge_file_content, content_type: 'text/plain'
+ else
+ head :not_found
+ end
+ end
+
+ private
+
+ def acme_order
+ @acme_order ||= PagesDomainAcmeOrder.find_by_domain_and_token(params[:domain], params[:token])
+ end
+end
diff --git a/app/controllers/admin/logs_controller.rb b/app/controllers/admin/logs_controller.rb
index 06b0e6a15a3..704e727b1da 100644
--- a/app/controllers/admin/logs_controller.rb
+++ b/app/controllers/admin/logs_controller.rb
@@ -15,7 +15,8 @@ class Admin::LogsController < Admin::ApplicationController
Gitlab::EnvironmentLogger,
Gitlab::SidekiqLogger,
Gitlab::RepositoryCheckLogger,
- Gitlab::ProjectServiceLogger
+ Gitlab::ProjectServiceLogger,
+ Gitlab::Kubernetes::Logger
]
end
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index aeff0c96b64..70db15916b9 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -3,7 +3,7 @@
class Admin::ProjectsController < Admin::ApplicationController
include MembersPresentation
- before_action :project, only: [:show, :transfer, :repository_check]
+ before_action :project, only: [:show, :transfer, :repository_check, :destroy]
before_action :group, only: [:show, :transfer]
def index
@@ -35,6 +35,15 @@ class Admin::ProjectsController < Admin::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
+ def destroy
+ ::Projects::DestroyService.new(@project, current_user, {}).async_execute
+ flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name }
+
+ redirect_to admin_projects_path, status: :found
+ rescue Projects::DestroyService::DestroyError => ex
+ redirect_to admin_projects_path, status: 302, alert: ex.message
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def transfer
namespace = Namespace.find_by(id: params[:new_namespace_id])
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 4cbab6811bc..7321f719deb 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -42,7 +42,7 @@ class ApplicationController < ActionController::Base
:bitbucket_server_import_enabled?,
:google_code_import_enabled?, :fogbugz_import_enabled?,
:git_import_enabled?, :gitlab_project_import_enabled?,
- :manifest_import_enabled?
+ :manifest_import_enabled?, :phabricator_import_enabled?
# Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
# concerns due to caching private data.
@@ -424,6 +424,10 @@ class ApplicationController < ActionController::Base
Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest')
end
+ def phabricator_import_enabled?
+ Gitlab::PhabricatorImport.available?
+ end
+
# U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
@@ -436,6 +440,8 @@ class ApplicationController < ActionController::Base
end
def set_session_storage(&block)
+ return yield if sessionless_user?
+
Gitlab::Session.with_session(session, &block)
end
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 73ebd4e0e42..80ee7c35906 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -12,9 +12,6 @@ class Clusters::ClustersController < Clusters::BaseController
before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy]
before_action :update_applications_status, only: [:cluster_status]
- before_action only: [:show] do
- push_frontend_feature_flag(:metrics_time_window)
- end
helper_method :token_in_session
diff --git a/app/controllers/concerns/import_url_params.rb b/app/controllers/concerns/import_url_params.rb
new file mode 100644
index 00000000000..e51e4157f50
--- /dev/null
+++ b/app/controllers/concerns/import_url_params.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module ImportUrlParams
+ def import_url_params
+ return {} unless params.dig(:project, :import_url).present?
+
+ { import_url: import_params_to_full_url(params[:project]) }
+ end
+
+ def import_params_to_full_url(params)
+ Gitlab::UrlSanitizer.new(
+ params[:import_url],
+ credentials: {
+ user: params[:import_url_user],
+ password: params[:import_url_password]
+ }
+ ).full_url
+ end
+end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 91e875dca54..9cf25915e92 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -41,6 +41,7 @@ module IssuableCollections
return if pagination_disabled?
@issuables = @issuables.page(params[:page])
+ @issuables = per_page_for_relative_position if params[:sort] == 'relative_position'
@issuable_meta_data = issuable_meta_data(@issuables, collection_type)
@total_pages = issuable_page_count
end
@@ -80,6 +81,11 @@ module IssuableCollections
(row_count.to_f / limit).ceil
end
+ # manual / relative_position sorting allows for 100 items on the page
+ def per_page_for_relative_position
+ @issuables.per(100) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
def issuable_finder_for(finder_class)
finder_class.new(current_user, finder_options)
end
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index cfff154c3dd..8b8b7db72f8 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -26,16 +26,22 @@ module MilestoneActions
end
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def labels
respond_to do |format|
format.html { redirect_to milestone_redirect_path }
format.json do
+ milestone_labels = @milestone.issue_labels_visible_by_user(current_user)
+
render json: tabs_json("shared/milestones/_labels_tab", {
- labels: @milestone.labels.map { |label| label.present(issuable_subject: @milestone.parent) } # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ labels: milestone_labels.map do |label|
+ label.present(issuable_subject: @milestone.parent)
+ end
})
end
end
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
private
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 70811f5ea59..65d14781d92 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -6,18 +6,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
before_action :set_non_archived_param
+ before_action :projects, only: [:index]
before_action :default_sorting
skip_cross_project_access_check :index, :starred
def index
- @projects = load_projects(params.merge(non_public: true))
-
respond_to do |format|
format.html do
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/40260
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- render
- end
+ render_projects
end
format.atom do
load_events
@@ -51,6 +47,17 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
private
+ def projects
+ @projects ||= load_projects(params.merge(non_public: true))
+ end
+
+ def render_projects
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/40260
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ render
+ end
+ end
+
def default_sorting
params[:sort] ||= 'latest_activity_desc'
@sort = params[:sort]
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index 7b5dc22815c..1ce0afac83b 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -16,13 +16,8 @@ class GraphqlController < ApplicationController
before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
def execute
- variables = Gitlab::Graphql::Variables.new(params[:variables]).to_h
- query = params[:query]
- operation_name = params[:operationName]
- context = {
- current_user: current_user
- }
- result = GitlabSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
+ result = multiplex? ? execute_multiplex : execute_query
+
render json: result
end
@@ -38,6 +33,44 @@ class GraphqlController < ApplicationController
private
+ def execute_multiplex
+ GitlabSchema.multiplex(multiplex_queries, context: context)
+ end
+
+ def execute_query
+ variables = build_variables(params[:variables])
+ operation_name = params[:operationName]
+
+ GitlabSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
+ end
+
+ def query
+ params[:query]
+ end
+
+ def multiplex_queries
+ params[:_json].map do |single_query_info|
+ {
+ query: single_query_info[:query],
+ variables: build_variables(single_query_info[:variables]),
+ operation_name: single_query_info[:operationName],
+ context: context
+ }
+ end
+ end
+
+ def context
+ @context ||= { current_user: current_user }
+ end
+
+ def build_variables(variable_info)
+ Gitlab::Graphql::Variables.new(variable_info).to_h
+ end
+
+ def multiplex?
+ params[:_json].present?
+ end
+
def authorize_access_api!
access_denied!("API not accessible for user.") unless can?(current_user, :access_api)
end
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index a37ba682b91..28ead8d44da 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -11,7 +11,7 @@ class Import::FogbugzController < Import::BaseController
def callback
begin
- res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys)
+ res = Gitlab::FogbugzImport::Client.new(import_params.to_h.symbolize_keys)
rescue
# If the URI is invalid various errors can occur
return redirect_to new_import_fogbugz_path, alert: _('Could not connect to FogBugz, check your URL')
@@ -26,7 +26,7 @@ class Import::FogbugzController < Import::BaseController
end
def create_user_map
- user_map = params[:users]
+ user_map = user_map_params.to_h[:users]
unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? }
flash.now[:alert] = _('All users must have a name.')
@@ -99,6 +99,10 @@ class Import::FogbugzController < Import::BaseController
params.permit(:uri, :email, :password)
end
+ def user_map_params
+ params.permit(users: %w(name email gitlab_user))
+ end
+
def verify_fogbugz_import_enabled
render_404 unless fogbugz_import_enabled?
end
diff --git a/app/controllers/import/phabricator_controller.rb b/app/controllers/import/phabricator_controller.rb
new file mode 100644
index 00000000000..d1c04817689
--- /dev/null
+++ b/app/controllers/import/phabricator_controller.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class Import::PhabricatorController < Import::BaseController
+ include ImportHelper
+
+ before_action :verify_import_enabled
+
+ def new
+ end
+
+ def create
+ @project = Gitlab::PhabricatorImport::ProjectCreator
+ .new(current_user, import_params).execute
+
+ if @project&.persisted?
+ redirect_to @project
+ else
+ @name = params[:name]
+ @path = params[:path]
+ @errors = @project&.errors&.full_messages || [_("Invalid import params")]
+
+ render :new
+ end
+ end
+
+ def verify_import_enabled
+ render_404 unless phabricator_import_enabled?
+ end
+
+ private
+
+ def import_params
+ params.permit(:path, :phabricator_server_url, :api_token, :name, :namespace_id)
+ end
+end
diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb
index 0d2a6145d0e..b03f4b7435f 100644
--- a/app/controllers/profiles/accounts_controller.rb
+++ b/app/controllers/profiles/accounts_controller.rb
@@ -17,7 +17,7 @@ class Profiles::AccountsController < Profiles::ApplicationController
if unlink_provider_allowed?(provider)
identity.destroy
else
- flash[:alert] = "You are not allowed to unlink your primary login account"
+ flash[:alert] = _("You are not allowed to unlink your primary login account")
end
redirect_to profile_account_path
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
index 503eda250b4..f666a1150a6 100644
--- a/app/controllers/profiles/emails_controller.rb
+++ b/app/controllers/profiles/emails_controller.rb
@@ -28,9 +28,9 @@ class Profiles::EmailsController < Profiles::ApplicationController
def resend_confirmation_instructions
if Emails::ConfirmService.new(current_user, user: current_user).execute(@email)
- flash[:notice] = "Confirmation email sent to #{@email.email}"
+ flash[:notice] = _("Confirmation email sent to %{email}") % { email: @email.email }
else
- flash[:alert] = "There was a problem sending the confirmation email"
+ flash[:alert] = _("There was a problem sending the confirmation email")
end
redirect_to profile_emails_url
diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb
new file mode 100644
index 00000000000..c755bcb718a
--- /dev/null
+++ b/app/controllers/profiles/groups_controller.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class Profiles::GroupsController < Profiles::ApplicationController
+ include RoutableActions
+
+ def update
+ group = find_routable!(Group, params[:id])
+ notification_setting = current_user.notification_settings.find_by(source: group) # rubocop: disable CodeReuse/ActiveRecord
+
+ if notification_setting.update(update_params)
+ flash[:notice] = "Notification settings for #{group.name} saved"
+ else
+ flash[:alert] = "Failed to save new settings for #{group.name}"
+ end
+
+ redirect_back_or_default(default: profile_notifications_path)
+ end
+
+ private
+
+ def update_params
+ params.require(:notification_setting).permit(:notification_email)
+ end
+end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index b719b70c56e..617e5bb7cb3 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -14,9 +14,9 @@ class Profiles::NotificationsController < Profiles::ApplicationController
result = Users::UpdateService.new(current_user, user_params.merge(user: current_user)).execute
if result[:status] == :success
- flash[:notice] = "Notification settings saved"
+ flash[:notice] = _("Notification settings saved")
else
- flash[:alert] = "Failed to save new settings"
+ flash[:alert] = _("Failed to save new settings")
end
redirect_back_or_default(default: profile_notifications_path)
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index 7038447581c..d2787c2e450 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -14,7 +14,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
def create
unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
- redirect_to new_profile_password_path, alert: 'You must provide a valid current password'
+ redirect_to new_profile_password_path, alert: _('You must provide a valid current password')
return
end
@@ -29,7 +29,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
if result[:status] == :success
Users::UpdateService.new(current_user, user: @user, password_expires_at: nil).execute
- redirect_to root_path, notice: 'Password successfully changed'
+ redirect_to root_path, notice: _('Password successfully changed')
else
render :new
end
@@ -45,14 +45,14 @@ class Profiles::PasswordsController < Profiles::ApplicationController
password_attributes[:password_automatically_set] = false
unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
- redirect_to edit_profile_password_path, alert: 'You must provide a valid current password'
+ redirect_to edit_profile_password_path, alert: _('You must provide a valid current password')
return
end
result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute
if result[:status] == :success
- flash[:notice] = "Password was successfully updated. Please login with it"
+ flash[:notice] = _('Password was successfully updated. Please login with it')
redirect_to new_user_session_path
else
@user.reset
@@ -62,7 +62,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
def reset
current_user.send_reset_password_instructions
- redirect_to edit_profile_password_path, notice: 'We sent you an email with reset password instructions'
+ redirect_to edit_profile_password_path, notice: _('We sent you an email with reset password instructions')
end
private
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 83e14275a8b..95b9344c551 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -18,7 +18,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
two_factor_authentication_reason(
global: lambda do
flash.now[:alert] =
- s_('The global settings require you to enable Two-Factor Authentication for your account.')
+ _('The global settings require you to enable Two-Factor Authentication for your account.')
end,
group: lambda do |groups|
flash.now[:alert] = groups_notification(groups)
@@ -27,7 +27,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
unless two_factor_grace_period_expired?
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
- flash.now[:alert] = flash.now[:alert] + s_(" You need to do this before %{grace_period_deadline}.") % { grace_period_deadline: l(grace_period_deadline) }
+ flash.now[:alert] = flash.now[:alert] + _(" You need to do this before %{grace_period_deadline}.") % { grace_period_deadline: l(grace_period_deadline) }
end
end
@@ -44,7 +44,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
render 'create'
else
- @error = s_('Invalid pin code')
+ @error = _('Invalid pin code')
@qr_code = build_qr_code
setup_u2f_registration
render 'show'
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index cb02581da37..98cd66cf6f9 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -4,6 +4,10 @@ class Projects::ClustersController < Clusters::ClustersController
prepend_before_action :project
before_action :repository
+ before_action do
+ push_frontend_feature_flag(:prometheus_computed_alerts)
+ end
+
layout 'project'
private
diff --git a/app/controllers/projects/environments/prometheus_api_controller.rb b/app/controllers/projects/environments/prometheus_api_controller.rb
index f8ef23cd83e..9c6c6513a78 100644
--- a/app/controllers/projects/environments/prometheus_api_controller.rb
+++ b/app/controllers/projects/environments/prometheus_api_controller.rb
@@ -13,7 +13,7 @@ class Projects::Environments::PrometheusApiController < Projects::ApplicationCon
).execute
if result.nil?
- return render status: :accepted, json: {
+ return render status: :no_content, json: {
status: _('processing'),
message: _('Not ready yet. Try again later.')
}
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index c342e1c80b0..e002a4d349b 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -11,10 +11,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
- push_frontend_feature_flag(:metrics_time_window)
push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint)
push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
push_frontend_feature_flag(:grafana_dashboard_link)
+ push_frontend_feature_flag(:prometheus_computed_alerts)
end
def index
@@ -220,8 +220,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
def metrics_params
- return unless Feature.enabled?(:metrics_time_window, project)
-
params.require([:start, :end])
end
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 7a80da53025..956093b972b 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -15,6 +15,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
alias_method :authenticated_user, :actor
# Git clients will not know what authenticity token to send along
+ skip_around_action :set_session_storage
skip_before_action :verify_authenticity_token
skip_before_action :repository
before_action :authenticate_user
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 4640be015de..afbf9fd7720 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -2,6 +2,7 @@
class Projects::ImportsController < Projects::ApplicationController
include ContinueParams
+ include ImportUrlParams
# Authorize
before_action :authorize_admin_project!
@@ -67,10 +68,12 @@ class Projects::ImportsController < Projects::ApplicationController
end
def import_params_attributes
- [:import_url]
+ []
end
def import_params
- params.require(:project).permit(import_params_attributes)
+ params.require(:project)
+ .permit(import_params_attributes)
+ .merge(import_url_params)
end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index eb469d2d714..f2a6268b3e9 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -7,11 +7,15 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
private
- # rubocop: disable CodeReuse/ActiveRecord
def merge_request
- @issuable = @merge_request ||= @project.merge_requests.includes(author: :status).find_by!(iid: params[:id])
+ @issuable =
+ @merge_request ||=
+ merge_request_includes(@project.merge_requests).find_by_iid!(params[:id])
+ end
+
+ def merge_request_includes(association)
+ association.includes(:metrics, :assignees, author: :status) # rubocop:disable CodeReuse/ActiveRecord
end
- # rubocop: enable CodeReuse/ActiveRecord
def merge_request_params
params.require(:merge_request).permit(merge_request_params_attributes)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 8f177895b08..9e7e3ed5afb 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -145,14 +145,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
render partial: 'projects/merge_requests/widget/commit_change_content', layout: false
end
- def cancel_merge_when_pipeline_succeeds
- unless @merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
+ def cancel_auto_merge
+ unless @merge_request.can_cancel_auto_merge?(current_user)
return access_denied!
end
- ::MergeRequests::MergeWhenPipelineSucceedsService
- .new(@project, current_user)
- .cancel(@merge_request)
+ AutoMergeService.new(project, current_user).cancel(@merge_request)
render json: serialize_widget(@merge_request)
end
@@ -229,12 +227,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def merge_params_attributes
- [:should_remove_source_branch, :commit_message, :squash_commit_message, :squash]
+ [:should_remove_source_branch, :commit_message, :squash_commit_message, :squash, :auto_merge_strategy]
end
- def merge_when_pipeline_succeeds_active?
- params[:merge_when_pipeline_succeeds].present? &&
- @merge_request.head_pipeline && @merge_request.head_pipeline.active?
+ def auto_merge_requested?
+ # Support params[:merge_when_pipeline_succeeds] during the transition period
+ params[:auto_merge_strategy].present? || params[:merge_when_pipeline_succeeds].present?
end
def close_merge_request_if_no_source_project
@@ -258,9 +256,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def merge!
- # Disable the CI check if merge_when_pipeline_succeeds is enabled since we have
+ # Disable the CI check if auto_merge_strategy is specified since we have
# to wait until CI completes to know
- unless @merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds_active?)
+ unless @merge_request.mergeable?(skip_ci_check: auto_merge_requested?)
return :failed
end
@@ -274,23 +272,15 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false))
- if params[:merge_when_pipeline_succeeds].present?
- return :failed unless @merge_request.actual_head_pipeline
-
- if @merge_request.actual_head_pipeline.active?
- ::MergeRequests::MergeWhenPipelineSucceedsService
- .new(@project, current_user, merge_params)
- .execute(@merge_request)
-
- :merge_when_pipeline_succeeds
- elsif @merge_request.actual_head_pipeline.success?
- # This can be triggered when a user clicks the auto merge button while
- # the tests finish at about the same time
- @merge_request.merge_async(current_user.id, merge_params)
-
- :success
+ if auto_merge_requested?
+ if merge_request.auto_merge_enabled?
+ # TODO: We should have a dedicated endpoint for updating merge params.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63130.
+ AutoMergeService.new(project, current_user, merge_params).update(merge_request)
else
- :failed
+ AutoMergeService.new(project, current_user, merge_params)
+ .execute(merge_request,
+ params[:auto_merge_strategy] || AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
end
else
@merge_request.merge_async(current_user.id, merge_params)
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index 58b1bc54181..89f21d8dadb 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -65,11 +65,11 @@ class Projects::PagesDomainsController < Projects::ApplicationController
private
def create_params
- params.require(:pages_domain).permit(:key, :certificate, :domain)
+ params.require(:pages_domain).permit(:key, :certificate, :domain, :auto_ssl_enabled)
end
def update_params
- params.require(:pages_domain).permit(:key, :certificate)
+ params.require(:pages_domain).permit(:key, :certificate, :auto_ssl_enabled)
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/serverless/functions_controller.rb b/app/controllers/projects/serverless/functions_controller.rb
index 79030da64d3..4b0d001fca6 100644
--- a/app/controllers/projects/serverless/functions_controller.rb
+++ b/app/controllers/projects/serverless/functions_controller.rb
@@ -10,15 +10,13 @@ module Projects
format.json do
functions = finder.execute
- if functions.any?
- render json: serialize_function(functions)
- else
- head :no_content
- end
+ render json: {
+ knative_installed: finder.knative_installed,
+ functions: serialize_function(functions)
+ }.to_json
end
format.html do
- @installed = finder.installed?
render
end
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index c4dff95a4b9..1b8d479209b 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -50,7 +50,8 @@ module Projects
:runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_human_readable, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path,
- auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy]
+ auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy],
+ ci_cd_settings_attributes: [:default_git_depth]
)
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index e88c46144ef..12db493978b 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -7,6 +7,7 @@ class ProjectsController < Projects::ApplicationController
include PreviewMarkdown
include SendFileUpload
include RecordUserLastActivity
+ include ImportUrlParams
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
@@ -333,6 +334,7 @@ class ProjectsController < Projects::ApplicationController
def project_params(attributes: [])
params.require(:project)
.permit(project_params_attributes + attributes)
+ .merge(import_url_params)
end
def project_params_attributes
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index a80ab3bcd28..cb25548c83f 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -25,6 +25,7 @@ class SearchController < ApplicationController
@show_snippets = search_service.show_snippets?
@search_results = search_service.search_results
@search_objects = search_service.search_objects
+ @display_options = search_service.display_options
render_commits if @scope == 'commits'
eager_load_user_status if @scope == 'users'
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 6fea61cf45d..a841859621e 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -18,6 +18,7 @@ class SessionsController < Devise::SessionsController
prepend_before_action :store_redirect_uri, only: [:new]
prepend_before_action :ldap_servers, only: [:new, :create]
prepend_before_action :require_no_authentication_without_flash, only: [:new, :create]
+ prepend_before_action :ensure_password_authentication_enabled!, if: :password_based_login?, only: [:create]
before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha
@@ -138,6 +139,14 @@ class SessionsController < Devise::SessionsController
end
# rubocop: enable CodeReuse/ActiveRecord
+ def ensure_password_authentication_enabled!
+ render_403 unless Gitlab::CurrentSettings.password_authentication_enabled_for_web?
+ end
+
+ def password_based_login?
+ user_params[:login].present? || user_params[:password].present?
+ end
+
def user_params
params.require(:user).permit(:login, :password, :remember_me, :otp_attempt, :device_response)
end
diff --git a/app/finders/clusters/knative_services_finder.rb b/app/finders/clusters/knative_services_finder.rb
new file mode 100644
index 00000000000..7d3b53ef663
--- /dev/null
+++ b/app/finders/clusters/knative_services_finder.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+module Clusters
+ class KnativeServicesFinder
+ include ReactiveCaching
+ include Gitlab::Utils::StrongMemoize
+
+ KNATIVE_STATES = {
+ 'checking' => 'checking',
+ 'installed' => 'installed',
+ 'not_found' => 'not_found'
+ }.freeze
+
+ self.reactive_cache_key = ->(finder) { finder.model_name }
+ self.reactive_cache_worker_finder = ->(_id, *cache_args) { from_cache(*cache_args) }
+
+ attr_reader :cluster, :project
+
+ def initialize(cluster, project)
+ @cluster = cluster
+ @project = project
+ end
+
+ def with_reactive_cache_memoized(*cache_args, &block)
+ strong_memoize(:reactive_cache) do
+ with_reactive_cache(*cache_args, &block)
+ end
+ end
+
+ def clear_cache!
+ clear_reactive_cache!(*cache_args)
+ end
+
+ def self.from_cache(cluster_id, project_id)
+ cluster = Clusters::Cluster.find(cluster_id)
+ project = ::Project.find(project_id)
+
+ new(cluster, project)
+ end
+
+ def calculate_reactive_cache(*)
+ # read_services calls knative_client.discover implicitily. If we stop
+ # detecting services but still want to detect knative, we'll need to
+ # explicitily call: knative_client.discover
+ #
+ # We didn't create it separately to avoid 2 cluster requests.
+ ksvc = read_services
+ pods = knative_client.discovered ? read_pods : []
+ { services: ksvc, pods: pods, knative_detected: knative_client.discovered }
+ end
+
+ def services
+ return [] unless search_namespace
+
+ cached_data = with_reactive_cache_memoized(*cache_args) { |data| data }
+ cached_data.to_h.fetch(:services, [])
+ end
+
+ def cache_args
+ [cluster.id, project.id]
+ end
+
+ def service_pod_details(service)
+ cached_data = with_reactive_cache_memoized(*cache_args) { |data| data }
+ cached_data.to_h.fetch(:pods, []).select do |pod|
+ filter_pods(pod, service)
+ end
+ end
+
+ def knative_detected
+ cached_data = with_reactive_cache_memoized(*cache_args) { |data| data }
+
+ knative_state = cached_data.to_h[:knative_detected]
+
+ return KNATIVE_STATES['checking'] if knative_state.nil?
+ return KNATIVE_STATES['installed'] if knative_state
+
+ KNATIVE_STATES['uninstalled']
+ end
+
+ def model_name
+ self.class.name.underscore.tr('/', '_')
+ end
+
+ private
+
+ def search_namespace
+ @search_namespace ||= cluster.kubernetes_namespace_for(project)
+ end
+
+ def knative_client
+ cluster.kubeclient.knative_client
+ end
+
+ def filter_pods(pod, service)
+ pod["metadata"]["labels"]["serving.knative.dev/service"] == service
+ end
+
+ def read_services
+ knative_client.get_services(namespace: search_namespace).as_json
+ rescue Kubeclient::ResourceNotFoundError
+ []
+ end
+
+ def read_pods
+ cluster.kubeclient.core_client.get_pods(namespace: search_namespace).as_json
+ end
+
+ def id
+ nil
+ end
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 50e9418677c..3592505a977 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -43,7 +43,7 @@ class IssuableFinder
FILTER_NONE = 'none'.freeze
FILTER_ANY = 'any'.freeze
- # This is accepted as a deprecated filter and is also used in unassigning users
+ # This is used in unassigning users
NONE = '0'.freeze
attr_accessor :current_user, :params
@@ -248,8 +248,7 @@ class IssuableFinder
def filter_by_no_label?
downcased = label_names.map(&:downcase)
- # Label::NONE is deprecated and should be removed in 12.0
- downcased.include?(FILTER_NONE) || downcased.include?(Label::NONE)
+ downcased.include?(FILTER_NONE)
end
def filter_by_any_label?
@@ -449,8 +448,7 @@ class IssuableFinder
# rubocop: enable CodeReuse/ActiveRecord
def filter_by_no_assignee?
- # Assignee_id takes precedence over assignee_username
- [NONE, FILTER_NONE].include?(params[:assignee_id].to_s.downcase) || params[:assignee_username].to_s == NONE
+ params[:assignee_id].to_s.downcase == FILTER_NONE
end
def filter_by_any_assignee?
diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb
index f90a7868102..917de249104 100644
--- a/app/finders/members_finder.rb
+++ b/app/finders/members_finder.rb
@@ -9,25 +9,18 @@ class MembersFinder
@group = project.group
end
- # rubocop: disable CodeReuse/ActiveRecord
- def execute(include_descendants: false)
+ def execute(include_descendants: false, include_invited_groups_members: false)
project_members = project.project_members
project_members = project_members.non_invite unless can?(current_user, :admin_project, project)
- if group
- group_members = GroupMembersFinder.new(group).execute(include_descendants: include_descendants) # rubocop: disable CodeReuse/Finder
- group_members = group_members.non_invite
+ union_members = group_union_members(include_descendants, include_invited_groups_members)
- union = Gitlab::SQL::Union.new([project_members, group_members], remove_duplicates: false) # rubocop: disable Gitlab/Union
-
- sql = distinct_on(union)
-
- Member.includes(:user).from("(#{sql}) AS #{Member.table_name}")
+ if union_members.any?
+ distinct_union_of_members(union_members << project_members)
else
project_members
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def can?(*args)
Ability.allowed?(*args)
@@ -35,6 +28,34 @@ class MembersFinder
private
+ def group_union_members(include_descendants, include_invited_groups_members)
+ [].tap do |members|
+ members << direct_group_members(include_descendants) if group
+ members << project_invited_groups_members if include_invited_groups_members
+ end
+ end
+
+ def direct_group_members(include_descendants)
+ GroupMembersFinder.new(group).execute(include_descendants: include_descendants).non_invite # rubocop: disable CodeReuse/Finder
+ end
+
+ def project_invited_groups_members
+ invited_groups_ids_including_ancestors = Gitlab::ObjectHierarchy
+ .new(project.invited_groups)
+ .base_and_ancestors
+ .public_or_visible_to_user(current_user)
+ .select(:id)
+
+ GroupMember.with_source_id(invited_groups_ids_including_ancestors)
+ end
+
+ def distinct_union_of_members(union_members)
+ union = Gitlab::SQL::Union.new(union_members, remove_duplicates: false) # rubocop: disable Gitlab/Union
+ sql = distinct_on(union)
+
+ Member.includes(:user).from([Arel.sql("(#{sql}) AS #{Member.table_name}")]) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
def distinct_on(union)
# We're interested in a list of members without duplicates by user_id.
# We prefer project members over group members, project members should go first.
diff --git a/app/finders/projects/serverless/functions_finder.rb b/app/finders/projects/serverless/functions_finder.rb
index e5bffccabfe..ebe50806ca1 100644
--- a/app/finders/projects/serverless/functions_finder.rb
+++ b/app/finders/projects/serverless/functions_finder.rb
@@ -14,8 +14,16 @@ module Projects
knative_services.flatten.compact
end
- def installed?
- clusters_with_knative_installed.exists?
+ # Possible return values: Clusters::KnativeServicesFinder::KNATIVE_STATE
+ def knative_installed
+ states = @clusters.map do |cluster|
+ cluster.application_knative
+ cluster.knative_services_finder(project).knative_detected.tap do |state|
+ return state if state == ::Clusters::KnativeServicesFinder::KNATIVE_STATES['checking'] # rubocop:disable Cop/AvoidReturnFromBlocks
+ end
+ end
+
+ states.any? { |state| state == ::Clusters::KnativeServicesFinder::KNATIVE_STATES['installed'] }
end
def service(environment_scope, name)
@@ -25,7 +33,7 @@ module Projects
def invocation_metrics(environment_scope, name)
return unless prometheus_adapter&.can_query?
- cluster = clusters_with_knative_installed.preload_knative.find do |c|
+ cluster = @clusters.find do |c|
environment_scope == c.environment_scope
end
@@ -34,7 +42,7 @@ module Projects
end
def has_prometheus?(environment_scope)
- clusters_with_knative_installed.preload_knative.to_a.any? do |cluster|
+ @clusters.any? do |cluster|
environment_scope == cluster.environment_scope && cluster.application_prometheus_available?
end
end
@@ -42,10 +50,12 @@ module Projects
private
def knative_service(environment_scope, name)
- clusters_with_knative_installed.preload_knative.map do |cluster|
+ @clusters.map do |cluster|
next if environment_scope != cluster.environment_scope
- services = cluster.application_knative.services_for(ns: cluster.kubernetes_namespace_for(project))
+ services = cluster
+ .knative_services_finder(project)
+ .services
.select { |svc| svc["metadata"]["name"] == name }
add_metadata(cluster, services).first unless services.nil?
@@ -53,8 +63,11 @@ module Projects
end
def knative_services
- clusters_with_knative_installed.preload_knative.map do |cluster|
- services = cluster.application_knative.services_for(ns: cluster.kubernetes_namespace_for(project))
+ @clusters.map do |cluster|
+ services = cluster
+ .knative_services_finder(project)
+ .services
+
add_metadata(cluster, services) unless services.nil?
end
end
@@ -65,17 +78,14 @@ module Projects
s["cluster_id"] = cluster.id
if services.length == 1
- s["podcount"] = cluster.application_knative.service_pod_details(
- cluster.kubernetes_namespace_for(project),
- s["metadata"]["name"]).length
+ s["podcount"] = cluster
+ .knative_services_finder(project)
+ .service_pod_details(s["metadata"]["name"])
+ .length
end
end
end
- def clusters_with_knative_installed
- @clusters.with_knative_installed
- end
-
# rubocop: disable CodeReuse/ServiceClass
def prometheus_adapter
@prometheus_adapter ||= ::Prometheus::AdapterService.new(project).prometheus_adapter
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 897e12c1b56..2e5bdbd79c8 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -7,7 +7,7 @@ class GitlabSchema < GraphQL::Schema
AUTHENTICATED_COMPLEXITY = 250
ADMIN_COMPLEXITY = 300
- ANONYMOUS_MAX_DEPTH = 10
+ DEFAULT_MAX_DEPTH = 10
AUTHENTICATED_MAX_DEPTH = 15
use BatchLoader::GraphQL
@@ -16,17 +16,28 @@ class GitlabSchema < GraphQL::Schema
use Gitlab::Graphql::Connections
use Gitlab::Graphql::GenericTracing
- query_analyzer Gitlab::Graphql::QueryAnalyzers::LogQueryComplexity.analyzer
+ query_analyzer Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer.new
query(Types::QueryType)
default_max_page_size 100
max_complexity DEFAULT_MAX_COMPLEXITY
+ max_depth DEFAULT_MAX_DEPTH
mutation(Types::MutationType)
class << self
+ def multiplex(queries, **kwargs)
+ kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context])
+
+ queries.each do |query|
+ query[:max_depth] = max_query_depth(kwargs[:context])
+ end
+
+ super(queries, **kwargs)
+ end
+
def execute(query_str = nil, **kwargs)
kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context])
kwargs[:max_depth] ||= max_query_depth(kwargs[:context])
@@ -34,6 +45,31 @@ class GitlabSchema < GraphQL::Schema
super(query_str, **kwargs)
end
+ def id_from_object(object)
+ unless object.respond_to?(:to_global_id)
+ # This is an error in our schema and needs to be solved. So raise a
+ # more meaningfull error message
+ raise "#{object} does not implement `to_global_id`. "\
+ "Include `GlobalID::Identification` into `#{object.class}"
+ end
+
+ object.to_global_id
+ end
+
+ def object_from_id(global_id)
+ gid = GlobalID.parse(global_id)
+
+ unless gid
+ raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id."
+ end
+
+ if gid.model_class < ApplicationRecord
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find
+ else
+ gid.find
+ end
+ end
+
private
def max_query_complexity(ctx)
@@ -54,7 +90,7 @@ class GitlabSchema < GraphQL::Schema
if current_user
AUTHENTICATED_MAX_DEPTH
else
- ANONYMOUS_MAX_DEPTH
+ DEFAULT_MAX_DEPTH
end
end
end
diff --git a/app/graphql/mutations/merge_requests/base.rb b/app/graphql/mutations/merge_requests/base.rb
index 7d0cb777ad1..e85d16fc2c5 100644
--- a/app/graphql/mutations/merge_requests/base.rb
+++ b/app/graphql/mutations/merge_requests/base.rb
@@ -10,7 +10,7 @@ module Mutations
required: true,
description: "The project the merge request to mutate is in"
- argument :iid, GraphQL::ID_TYPE,
+ argument :iid, GraphQL::STRING_TYPE,
required: true,
description: "The iid of the merge request to mutate"
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 31850c2cadb..5b7eb57841c 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -10,7 +10,7 @@ module Resolvers
end
end
- def self.resolver_complexity(args)
+ def self.resolver_complexity(args, child_complexity:)
complexity = 1
complexity += 1 if args[:sort]
complexity += 5 if args[:search]
diff --git a/app/graphql/resolvers/concerns/resolves_pipelines.rb b/app/graphql/resolvers/concerns/resolves_pipelines.rb
index a166211fc18..a6f82cc8505 100644
--- a/app/graphql/resolvers/concerns/resolves_pipelines.rb
+++ b/app/graphql/resolvers/concerns/resolves_pipelines.rb
@@ -20,7 +20,7 @@ module ResolvesPipelines
end
class_methods do
- def resolver_complexity(args)
+ def resolver_complexity(args, child_complexity:)
complexity = super
complexity += 2 if args[:sha]
complexity += 2 if args[:ref]
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index f7e49166ca0..6988b451ec3 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -2,11 +2,11 @@
module Resolvers
class IssuesResolver < BaseResolver
- argument :iid, GraphQL::ID_TYPE,
+ argument :iid, GraphQL::STRING_TYPE,
required: false,
description: 'The IID of the issue, e.g., "1"'
- argument :iids, [GraphQL::ID_TYPE],
+ argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'The list of IIDs of issues, e.g., [1, 2]'
argument :state, Types::IssuableStateEnum,
@@ -58,7 +58,7 @@ module Resolvers
IssuesFinder.new(context[:current_user], args).execute
end
- def self.resolver_complexity(args)
+ def self.resolver_complexity(args, child_complexity:)
complexity = super
complexity += 2 if args[:labelName]
diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb
index 90795c797ac..b84e60066e1 100644
--- a/app/graphql/resolvers/merge_requests_resolver.rb
+++ b/app/graphql/resolvers/merge_requests_resolver.rb
@@ -2,11 +2,11 @@
module Resolvers
class MergeRequestsResolver < BaseResolver
- argument :iid, GraphQL::ID_TYPE,
+ argument :iid, GraphQL::STRING_TYPE,
required: false,
description: 'The IID of the merge request, e.g., "1"'
- argument :iids, [GraphQL::ID_TYPE],
+ argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'The list of IIDs of issues, e.g., [1, 2]'
diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb
new file mode 100644
index 00000000000..677ea808aeb
--- /dev/null
+++ b/app/graphql/resolvers/namespace_projects_resolver.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class NamespaceProjectsResolver < BaseResolver
+ argument :include_subgroups, GraphQL::BOOLEAN_TYPE,
+ required: false,
+ default_value: false,
+ description: 'Include also subgroup projects'
+
+ type Types::ProjectType, null: true
+
+ alias_method :namespace, :object
+
+ def resolve(include_subgroups:)
+ # The namespace could have been loaded in batch by `BatchLoader`.
+ # At this point we need the `id` or the `full_path` of the namespace
+ # to query for projects, so make sure it's loaded and not `nil` before continuing.
+ namespace.sync if namespace.respond_to?(:sync)
+ return Project.none if namespace.nil?
+
+ if include_subgroups
+ namespace.all_projects.with_route
+ else
+ namespace.projects.with_route
+ end
+ end
+
+ def self.resolver_complexity(args, child_complexity:)
+ complexity = super
+ complexity + 10
+ end
+ end
+end
diff --git a/app/graphql/resolvers/namespace_resolver.rb b/app/graphql/resolvers/namespace_resolver.rb
new file mode 100644
index 00000000000..17b3800d151
--- /dev/null
+++ b/app/graphql/resolvers/namespace_resolver.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class NamespaceResolver < BaseResolver
+ prepend FullPathResolver
+
+ type Types::NamespaceType, null: true
+
+ def resolve(full_path:)
+ model_by_full_path(Namespace, full_path)
+ end
+ end
+end
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index 15331129134..dd0d9105df6 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -29,15 +29,18 @@ module Types
# proc because we set complexity depending on arguments and number of
# items which can be loaded.
proc do |ctx, args, child_complexity|
- page_size = @max_page_size || ctx.schema.default_max_page_size
- limit_value = [args[:first], args[:last], page_size].compact.min
-
# Resolvers may add extra complexity depending on used arguments
- complexity = child_complexity + self.resolver&.try(:resolver_complexity, args).to_i
+ complexity = child_complexity + self.resolver&.try(:resolver_complexity, args, child_complexity: child_complexity).to_i
+
+ field_defn = to_graphql
- # Resolvers may add extra complexity depending on number of items being loaded.
- multiplier = self.resolver&.try(:complexity_multiplier, args).to_f
- complexity += complexity * limit_value * multiplier
+ if field_defn.connection?
+ # Resolvers may add extra complexity depending on number of items being loaded.
+ page_size = field_defn.connection_max_page_size || ctx.schema.default_max_page_size
+ limit_value = [args[:first], args[:last], page_size].compact.min
+ multiplier = self.resolver&.try(:complexity_multiplier, args).to_f
+ complexity += complexity * limit_value * multiplier
+ end
complexity.to_i
end
diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb
index 82b78abd573..e40059c46bb 100644
--- a/app/graphql/types/base_object.rb
+++ b/app/graphql/types/base_object.rb
@@ -6,5 +6,10 @@ module Types
prepend Gitlab::Graphql::ExposePermissions
field_class Types::BaseField
+
+ # All graphql fields exposing an id, should expose a global id.
+ def id
+ GitlabSchema.id_from_object(object)
+ end
end
end
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index de7d6570a3e..cff81e5670b 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -10,7 +10,7 @@ module Types
expose_permissions Types::PermissionTypes::Ci::Pipeline
field :id, GraphQL::ID_TYPE, null: false
- field :iid, GraphQL::ID_TYPE, null: false
+ field :iid, GraphQL::STRING_TYPE, null: false
field :sha, GraphQL::STRING_TYPE, null: false
field :before_sha, GraphQL::STRING_TYPE, null: true
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index b21a226d07f..dd5133189dc 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -15,6 +15,10 @@ module Types
field :description, GraphQL::STRING_TYPE, null: true
field :state, IssueStateEnum, null: false
+ field :reference, GraphQL::STRING_TYPE, null: false, method: :to_reference do
+ argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false
+ end
+
field :author, Types::UserType,
null: false,
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find }
@@ -37,7 +41,9 @@ module Types
field :upvotes, GraphQL::INT_TYPE, null: false
field :downvotes, GraphQL::INT_TYPE, null: false
field :user_notes_count, GraphQL::INT_TYPE, null: false
+ field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path
field :web_url, GraphQL::STRING_TYPE, null: false
+ field :relative_position, GraphQL::INT_TYPE, null: true
field :closed_at, Types::TimeType, null: true
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 120ffe0dfde..85ac3102442 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -11,7 +11,7 @@ module Types
present_using MergeRequestPresenter
field :id, GraphQL::ID_TYPE, null: false
- field :iid, GraphQL::ID_TYPE, null: false
+ field :iid, GraphQL::STRING_TYPE, null: false
field :title, GraphQL::STRING_TYPE, null: false
field :description, GraphQL::STRING_TYPE, null: true
field :state, MergeRequestStateEnum, null: false
diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb
index 36d8ee8c878..f6d91320e50 100644
--- a/app/graphql/types/namespace_type.rb
+++ b/app/graphql/types/namespace_type.rb
@@ -15,5 +15,10 @@ module Types
field :visibility, GraphQL::STRING_TYPE, null: true
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
+
+ field :projects,
+ Types::ProjectType.connection_type,
+ null: false,
+ resolver: ::Resolvers::NamespaceProjectsResolver
end
end
diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb
new file mode 100644
index 00000000000..62537361918
--- /dev/null
+++ b/app/graphql/types/project_statistics_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ class ProjectStatisticsType < BaseObject
+ graphql_name 'ProjectStatistics'
+
+ field :commit_count, GraphQL::INT_TYPE, null: false
+
+ field :storage_size, GraphQL::INT_TYPE, null: false
+ field :repository_size, GraphQL::INT_TYPE, null: false
+ field :lfs_objects_size, GraphQL::INT_TYPE, null: false
+ field :build_artifacts_size, GraphQL::INT_TYPE, null: false
+ field :packages_size, GraphQL::INT_TYPE, null: false
+ field :wiki_size, GraphQL::INT_TYPE, null: true
+ end
+end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 06a1aab09f6..2236ffa394d 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -69,6 +69,10 @@ module Types
field :namespace, Types::NamespaceType, null: false
field :group, Types::GroupType, null: true
+ field :statistics, Types::ProjectStatisticsType,
+ null: false,
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(obj.id).find }
+
field :repository, Types::RepositoryType, null: false
field :merge_requests,
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 40d7de1a49a..536bdb077ad 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -14,6 +14,11 @@ module Types
resolver: Resolvers::GroupResolver,
description: "Find a group"
+ field :namespace, Types::NamespaceType,
+ null: true,
+ resolver: Resolvers::NamespaceResolver,
+ description: "Find a namespace"
+
field :metadata, Types::MetadataType,
null: true,
resolver: Resolvers::MetadataResolver,
diff --git a/app/graphql/types/tree/blob_type.rb b/app/graphql/types/tree/blob_type.rb
index 230624201b0..ba191b59132 100644
--- a/app/graphql/types/tree/blob_type.rb
+++ b/app/graphql/types/tree/blob_type.rb
@@ -4,7 +4,14 @@ module Types
class BlobType < BaseObject
implements Types::Tree::EntryType
+ present_using BlobPresenter
+
graphql_name 'Blob'
+
+ field :web_url, GraphQL::STRING_TYPE, null: true
+ field :lfs_oid, GraphQL::STRING_TYPE, null: true, resolve: -> (blob, args, ctx) do
+ Gitlab::Graphql::Loaders::BatchCommitLoader.new(blob.repository, blob.id).find
+ end
end
end
end
diff --git a/app/graphql/types/tree/tree_entry_type.rb b/app/graphql/types/tree/tree_entry_type.rb
index d5cfb898aea..23ec2ef0ec2 100644
--- a/app/graphql/types/tree/tree_entry_type.rb
+++ b/app/graphql/types/tree/tree_entry_type.rb
@@ -4,8 +4,12 @@ module Types
class TreeEntryType < BaseObject
implements Types::Tree::EntryType
+ present_using TreeEntryPresenter
+
graphql_name 'TreeEntry'
description 'Represents a directory'
+
+ field :web_url, GraphQL::STRING_TYPE, null: true
end
end
end
diff --git a/app/graphql/types/tree/tree_type.rb b/app/graphql/types/tree/tree_type.rb
index 1eb6c43972e..1ee93ed9542 100644
--- a/app/graphql/types/tree/tree_type.rb
+++ b/app/graphql/types/tree/tree_type.rb
@@ -4,9 +4,15 @@ module Types
class TreeType < BaseObject
graphql_name 'Tree'
- field :trees, Types::Tree::TreeEntryType.connection_type, null: false
+ field :trees, Types::Tree::TreeEntryType.connection_type, null: false, resolve: -> (obj, args, ctx) do
+ Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository)
+ end
+
field :submodules, Types::Tree::SubmoduleType.connection_type, null: false
- field :blobs, Types::Tree::BlobType.connection_type, null: false
+
+ field :blobs, Types::Tree::BlobType.connection_type, null: false, resolve: -> (obj, args, ctx) do
+ Gitlab::Graphql::Representation::TreeEntry.decorate(obj.blobs, obj.repository)
+ end
end
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 971d1052824..4469118f065 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -160,6 +160,7 @@ module ApplicationSettingsHelper
:akismet_api_key,
:akismet_enabled,
:allow_local_requests_from_hooks_and_services,
+ :dns_rebinding_protection_enabled,
:archive_builds_in_human_readable,
:authorized_keys_enabled,
:auto_devops_enabled,
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 7e631053b54..0d6a6496993 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -188,7 +188,7 @@ module BlobHelper
end
def copy_file_path_button(file_path)
- clipboard_button(text: file_path, gfm: "`#{file_path}`", class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard')
+ clipboard_button(text: file_path, gfm: "`#{file_path}`", class: 'btn-clipboard btn-transparent', title: 'Copy file path to clipboard')
end
def copy_blob_source_button(blob)
diff --git a/app/helpers/ci_variables_helper.rb b/app/helpers/ci_variables_helper.rb
index 5bfdeb9e33c..fc51f00d052 100644
--- a/app/helpers/ci_variables_helper.rb
+++ b/app/helpers/ci_variables_helper.rb
@@ -17,7 +17,7 @@ module CiVariablesHelper
if variable && !only_key_value
variable.masked
else
- true
+ false
end
end
@@ -27,4 +27,8 @@ module CiVariablesHelper
%w(File file)
]
end
+
+ def ci_variable_maskable_regex
+ Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(/^\//, '').sub(/\/[a-z]*$/, '').gsub('\/', '/')
+ end
end
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index dc0e5511fcf..36122d3a22a 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -57,12 +57,6 @@ module EmailsHelper
pluralize(valid_length, unit)
end
- def reset_token_expire_message
- link_tag = link_to('request a new one', new_user_password_url(user_email: @user.email))
- "This link is valid for #{password_reset_token_valid_time}. " \
- "After it expires, you can #{link_tag}."
- end
-
def header_logo
if current_appearance&.header_logo?
image_tag(
@@ -98,16 +92,17 @@ module EmailsHelper
case format
when :html
- " via merge request #{link_to(merge_request.to_reference, merge_request.web_url)}"
+ merge_request_link = link_to(merge_request.to_reference, merge_request.web_url)
+ _("via merge request %{link}").html_safe % { link: merge_request_link }
else
# If it's not HTML nor text then assume it's text to be safe
- " via merge request #{merge_request.to_reference} (#{merge_request.web_url})"
+ _("via merge request %{link}") % { link: "#{merge_request.to_reference} (#{merge_request.web_url})" }
end
when String
# Technically speaking this should be Commit but per
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15610#note_163812339
# we can't deserialize Commit without custom serializer for ActiveJob
- " via #{closed_via}"
+ _("via %{closed_via}") % { closed_via: closed_via }
else
""
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 365b94f5a3e..8002eb08ada 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -30,7 +30,8 @@ module EnvironmentsHelper
"environments-endpoint": project_environments_path(project, format: :json),
"project-path" => project_path(project),
"tags-path" => project_tags_path(project),
- "has-metrics" => "#{environment.has_metrics?}"
+ "has-metrics" => "#{environment.has_metrics?}",
+ "external-dashboard-url" => project.metrics_setting_external_dashboard_url
}
end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 7af766c8544..a3f53ca8dd6 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -99,7 +99,7 @@ module GroupsHelper
end
def remove_group_message(group)
- _("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
+ _("You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
{ group_name: group.name }
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 76300e791e6..db4f29cd996 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -5,7 +5,7 @@ module LabelsHelper
include ActionView::Helpers::TagHelper
def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
- return true if label.is_a?(GroupLabel)
+ return true unless label.project_label?
return true unless project
project.feature_available?(issuables_type, current_user)
@@ -76,29 +76,39 @@ module LabelsHelper
end
def suggested_colors
- [
- '#0033CC',
- '#428BCA',
- '#44AD8E',
- '#A8D695',
- '#5CB85C',
- '#69D100',
- '#004E00',
- '#34495E',
- '#7F8C8D',
- '#A295D6',
- '#5843AD',
- '#8E44AD',
- '#FFECDB',
- '#AD4363',
- '#D10069',
- '#CC0033',
- '#FF0000',
- '#D9534F',
- '#D1D100',
- '#F0AD4E',
- '#AD8D43'
- ]
+ {
+ '#0033CC' => s_('SuggestedColors|UA blue'),
+ '#428BCA' => s_('SuggestedColors|Moderate blue'),
+ '#44AD8E' => s_('SuggestedColors|Lime green'),
+ '#A8D695' => s_('SuggestedColors|Feijoa'),
+ '#5CB85C' => s_('SuggestedColors|Slightly desaturated green'),
+ '#69D100' => s_('SuggestedColors|Bright green'),
+ '#004E00' => s_('SuggestedColors|Very dark lime green'),
+ '#34495E' => s_('SuggestedColors|Very dark desaturated blue'),
+ '#7F8C8D' => s_('SuggestedColors|Dark grayish cyan'),
+ '#A295D6' => s_('SuggestedColors|Slightly desaturated blue'),
+ '#5843AD' => s_('SuggestedColors|Dark moderate blue'),
+ '#8E44AD' => s_('SuggestedColors|Dark moderate violet'),
+ '#FFECDB' => s_('SuggestedColors|Very pale orange'),
+ '#AD4363' => s_('SuggestedColors|Dark moderate pink'),
+ '#D10069' => s_('SuggestedColors|Strong pink'),
+ '#CC0033' => s_('SuggestedColors|Strong red'),
+ '#FF0000' => s_('SuggestedColors|Pure red'),
+ '#D9534F' => s_('SuggestedColors|Soft red'),
+ '#D1D100' => s_('SuggestedColors|Strong yellow'),
+ '#F0AD4E' => s_('SuggestedColors|Soft orange'),
+ '#AD8D43' => s_('SuggestedColors|Dark moderate orange')
+ }
+ end
+
+ def render_suggested_colors
+ colors_html = suggested_colors.map do |color_hex_value, color_name|
+ link_to('', '#', class: "has-tooltip", style: "background-color: #{color_hex_value}", data: { color: color_hex_value }, title: color_name)
+ end
+
+ content_tag(:div, class: 'suggest-colors') do
+ colors_html.join.html_safe
+ end
end
def text_color_for_bg(bg_color)
@@ -159,13 +169,6 @@ module LabelsHelper
label.subscribed?(current_user, project) ? 'Unsubscribe' : 'Subscribe'
end
- def label_deletion_confirm_text(label)
- case label
- when GroupLabel then _('Remove this label? This will affect all projects within the group. Are you sure?')
- when ProjectLabel then _('Remove this label? Are you sure?')
- end
- end
-
def create_label_title(subject)
case subject
when Group
@@ -200,7 +203,7 @@ module LabelsHelper
end
def label_status_tooltip(label, status)
- type = label.is_a?(ProjectLabel) ? 'project' : 'group'
+ type = label.project_label? ? 'project' : 'group'
level = status.unsubscribed? ? type : status.sub('-level', '')
action = status.unsubscribed? ? 'Subscribe' : 'Unsubscribe'
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 991ca42c445..2de4e92e33e 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -103,7 +103,7 @@ module MergeRequestsHelper
def merge_params(merge_request)
{
- merge_when_pipeline_succeeds: true,
+ auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
should_remove_source_branch: true,
sha: merge_request.diff_head_sha,
squash: merge_request.squash
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index a7ce7667916..11b9cf22142 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -100,4 +100,8 @@ module NotificationsHelper
css_class: "icon notifications-icon js-notifications-icon"
)
end
+
+ def show_unsubscribe_title?(noteable)
+ can?(current_user, "read_#{noteable.to_ability_name}".to_sym, noteable)
+ end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 91d15e0e4ea..8dee842a22d 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -241,6 +241,7 @@ module ProjectsHelper
# TODO: Remove this method when removing the feature flag
# https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/11209#note_162234863
+ # make sure to remove from the EE specific controller as well: ee/app/controllers/ee/dashboard/projects_controller.rb
def show_projects?(projects, params)
Feature.enabled?(:project_list_filter_bar) || !!(params[:personal] || params[:name] || any_projects?(projects))
end
@@ -319,6 +320,34 @@ module ProjectsHelper
Ability.allowed?(current_user, :admin_project_member, @project)
end
+ def project_can_be_shared?
+ !membership_locked? || @project.allowed_to_share_with_group?
+ end
+
+ def membership_locked?
+ false
+ end
+
+ def share_project_description(project)
+ share_with_group = project.allowed_to_share_with_group?
+ share_with_members = !membership_locked?
+
+ description =
+ if share_with_group && share_with_members
+ _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.")
+ elsif share_with_group
+ _("You can invite another group to <strong>%{project_name}</strong>.")
+ elsif share_with_members
+ _("You can invite a new member to <strong>%{project_name}</strong>.")
+ end
+
+ description.html_safe % { project_name: project.name }
+ end
+
+ def metrics_external_dashboard_url
+ @project.metrics_setting_external_dashboard_url
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -631,4 +660,8 @@ module ProjectsHelper
project.builds_enabled? &&
!project.repository.gitlab_ci_yml
end
+
+ def vue_file_list_enabled?
+ Gitlab::Graphql.enabled? && Feature.enabled?(:vue_file_list, @project)
+ end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index f2d814e6930..26692934456 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -3,29 +3,30 @@
module SortingHelper
def sort_options_hash
{
- sort_value_created_date => sort_title_created_date,
- sort_value_downvotes => sort_title_downvotes,
- sort_value_due_date => sort_title_due_date,
- sort_value_due_date_later => sort_title_due_date_later,
- sort_value_due_date_soon => sort_title_due_date_soon,
- sort_value_label_priority => sort_title_label_priority,
- sort_value_largest_group => sort_title_largest_group,
- sort_value_largest_repo => sort_title_largest_repo,
- sort_value_milestone => sort_title_milestone,
- sort_value_milestone_later => sort_title_milestone_later,
- sort_value_milestone_soon => sort_title_milestone_soon,
- sort_value_name => sort_title_name,
- sort_value_name_desc => sort_title_name_desc,
- sort_value_oldest_created => sort_title_oldest_created,
- sort_value_oldest_signin => sort_title_oldest_signin,
- sort_value_oldest_updated => sort_title_oldest_updated,
- sort_value_recently_created => sort_title_recently_created,
- sort_value_recently_signin => sort_title_recently_signin,
- sort_value_recently_updated => sort_title_recently_updated,
- sort_value_popularity => sort_title_popularity,
- sort_value_priority => sort_title_priority,
- sort_value_upvotes => sort_title_upvotes,
- sort_value_contacted_date => sort_title_contacted_date
+ sort_value_created_date => sort_title_created_date,
+ sort_value_downvotes => sort_title_downvotes,
+ sort_value_due_date => sort_title_due_date,
+ sort_value_due_date_later => sort_title_due_date_later,
+ sort_value_due_date_soon => sort_title_due_date_soon,
+ sort_value_label_priority => sort_title_label_priority,
+ sort_value_largest_group => sort_title_largest_group,
+ sort_value_largest_repo => sort_title_largest_repo,
+ sort_value_milestone => sort_title_milestone,
+ sort_value_milestone_later => sort_title_milestone_later,
+ sort_value_milestone_soon => sort_title_milestone_soon,
+ sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_oldest_signin => sort_title_oldest_signin,
+ sort_value_oldest_updated => sort_title_oldest_updated,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_recently_signin => sort_title_recently_signin,
+ sort_value_recently_updated => sort_title_recently_updated,
+ sort_value_popularity => sort_title_popularity,
+ sort_value_priority => sort_title_priority,
+ sort_value_upvotes => sort_title_upvotes,
+ sort_value_contacted_date => sort_title_contacted_date,
+ sort_value_relative_position => sort_title_relative_position
}
end
@@ -397,6 +398,10 @@ module SortingHelper
s_('SortOptions|Recent last activity')
end
+ def sort_title_relative_position
+ s_('SortOptions|Manual')
+ end
+
# Values.
def sort_value_access_level_asc
'access_level_asc'
@@ -545,4 +550,8 @@ module SortingHelper
def sort_value_recently_last_activity
'last_activity_on_desc'
end
+
+ def sort_value_relative_position
+ 'relative_position'
+ end
end
diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb
index e80b3f2b54a..ecf37bae6b3 100644
--- a/app/helpers/storage_helper.rb
+++ b/app/helpers/storage_helper.rb
@@ -12,10 +12,11 @@ module StorageHelper
def storage_counters_details(statistics)
counters = {
counter_repositories: storage_counter(statistics.repository_size),
+ counter_wikis: storage_counter(statistics.wiki_size),
counter_build_artifacts: storage_counter(statistics.build_artifacts_size),
counter_lfs_objects: storage_counter(statistics.lfs_objects_size)
}
- _("%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS") % counters
+ _("%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS") % counters
end
end
diff --git a/app/helpers/tracking_helper.rb b/app/helpers/tracking_helper.rb
new file mode 100644
index 00000000000..51ea79d1ddd
--- /dev/null
+++ b/app/helpers/tracking_helper.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module TrackingHelper
+ def tracking_attrs(label, event, property)
+ {} # CE has no tracking features
+ end
+end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 9deb783d289..b318b27992a 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -165,8 +165,46 @@ module VisibilityLevelHelper
!form_model.visibility_level_allowed?(level)
end
+ # Visibility level can be restricted in two ways:
+ #
+ # 1. The group permissions (e.g. a subgroup is private, which requires
+ # all projects to be private)
+ # 2. The global allowed visibility settings, set by the admin
+ def selected_visibility_level(form_model, requested_level)
+ requested_level =
+ if requested_level.present?
+ requested_level.to_i
+ else
+ default_project_visibility
+ end
+
+ [requested_level, max_allowed_visibility_level(form_model)].min
+ end
+
private
+ def max_allowed_visibility_level(form_model)
+ # First obtain the maximum visibility for the project or group
+ current_level = max_allowed_visibility_level_by_model(form_model)
+
+ # Now limit this by the global setting
+ Gitlab::VisibilityLevel.closest_allowed_level(current_level)
+ end
+
+ def max_allowed_visibility_level_by_model(form_model)
+ current_level = Gitlab::VisibilityLevel::PRIVATE
+
+ Gitlab::VisibilityLevel.values.sort.each do |value|
+ if disallowed_visibility_level?(form_model, value)
+ break
+ else
+ current_level = value
+ end
+ end
+
+ current_level
+ end
+
def visibility_level_errors_for_group(group, level_name)
group_name = link_to group.name, group_path(group)
change_visiblity = link_to 'change the visibility', edit_group_path(group)
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 2b046d17122..f3a3203f7ad 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -83,7 +83,7 @@ module Emails
@project = Project.find(project_id)
@results = results
- mail(to: @user.notification_email, subject: subject('Imported issues')) do |format|
+ mail(to: recipient(@user.id, @project.group), subject: subject('Imported issues')) do |format|
format.html { render layout: 'mailer' }
format.text { render layout: 'mailer' }
end
@@ -103,7 +103,7 @@ module Emails
def issue_thread_options(sender_id, recipient_id, reason)
{
from: sender(sender_id),
- to: recipient(recipient_id),
+ to: recipient(recipient_id, @project.group),
subject: subject("#{@issue.title} (##{@issue.iid})"),
'X-GitLab-NotificationReason' => reason
}
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 91dfdf58982..2bfa59774d7 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -58,9 +58,8 @@ module Emails
@member_source_type = member_source_type
@member_source = member_source_class.find(source_id)
@invite_email = invite_email
- inviter = User.find(created_by_id)
- mail(to: inviter.notification_email,
+ mail(to: recipient(created_by_id, member_source_type == 'Project' ? @member_source.group : @member_source),
subject: subject('Invitation declined'))
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index fc6ed695675..864f9e2975a 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -110,7 +110,7 @@ module Emails
def merge_request_thread_options(sender_id, recipient_id, reason = nil)
{
from: sender(sender_id),
- to: recipient(recipient_id),
+ to: recipient(recipient_id, @project.group),
subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})"),
'X-GitLab-NotificationReason' => reason
}
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 1b3c1f9a8a9..70d296fe3b8 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),
+ to: recipient(recipient_id, @group),
subject: subject("#{@note.noteable.title} (#{@note.noteable.reference_link_text})")
}
end
diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb
index ce449237ef6..2d390666f65 100644
--- a/app/mailers/emails/pages_domains.rb
+++ b/app/mailers/emails/pages_domains.rb
@@ -7,7 +7,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("GitLab Pages domain '#{domain.domain}' has been enabled")
)
end
@@ -17,7 +17,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("GitLab Pages domain '#{domain.domain}' has been disabled")
)
end
@@ -27,7 +27,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("Verification succeeded for GitLab Pages domain '#{domain.domain}'")
)
end
@@ -37,7 +37,7 @@ module Emails
@project = domain.project
mail(
- to: recipient.notification_email,
+ to: recipient(recipient.id, @project.group),
subject: subject("ACTION REQUIRED: Verification failed for GitLab Pages domain '#{domain.domain}'")
)
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 2500622caa7..f81f76f67f7 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -7,20 +7,20 @@ module Emails
@project = Project.find project_id
@target_url = project_url(@project)
@old_path_with_namespace = old_path_with_namespace
- mail(to: @user.notification_email,
+ mail(to: recipient(user_id, @project.group),
subject: subject("Project was moved"))
end
def project_was_exported_email(current_user, project)
@project = project
- mail(to: current_user.notification_email,
+ mail(to: recipient(current_user.id, project.group),
subject: subject("Project was exported"))
end
def project_was_not_exported_email(current_user, project, errors)
@project = project
@errors = errors
- mail(to: current_user.notification_email,
+ mail(to: recipient(current_user.id, @project.group),
subject: subject("Project export error"))
end
@@ -28,7 +28,7 @@ module Emails
@project = project
@user = user
- mail(to: user.notification_email, subject: subject("Project cleanup has completed"))
+ mail(to: recipient(user.id, project.group), subject: subject("Project cleanup has completed"))
end
def repository_cleanup_failure_email(project, user, error)
@@ -36,7 +36,7 @@ module Emails
@user = user
@error = error
- mail(to: user.notification_email, subject: subject("Project cleanup failure"))
+ mail(to: recipient(user.id, project.group), subject: subject("Project cleanup failure"))
end
def repository_push_email(project_id, opts = {})
diff --git a/app/mailers/emails/remote_mirrors.rb b/app/mailers/emails/remote_mirrors.rb
index 2018eb7260b..2d8137843ec 100644
--- a/app/mailers/emails/remote_mirrors.rb
+++ b/app/mailers/emails/remote_mirrors.rb
@@ -6,7 +6,7 @@ module Emails
@remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute
@project = @remote_mirror.project
- mail(to: recipient(recipient_id), subject: subject('Remote mirror update failed'))
+ mail(to: recipient(recipient_id, @project.group), subject: subject('Remote mirror update failed'))
end
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0b740809f30..576caea4c10 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -73,12 +73,22 @@ class Notify < BaseMailer
# Look up a User by their ID and return their email address
#
- # recipient_id - User ID
+ # recipient_id - User ID
+ # notification_group - The parent group of the notification
#
# Returns a String containing the User's email address.
- def recipient(recipient_id)
+ def recipient(recipient_id, notification_group = nil)
@current_user = User.find(recipient_id)
- @current_user.notification_email
+ group_notification_email = nil
+
+ if notification_group
+ notification_settings = notification_group.notification_settings_for(@current_user, hierarchy_order: :asc)
+ group_notification_email = notification_settings.find { |n| n.notification_email.present? }&.notification_email
+ end
+
+ # Return group-specific email address if present, otherwise return global
+ # email address
+ group_notification_email || @current_user.notification_email
end
# Formats arguments into a String suitable for use as an email subject
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index fb1e558e46c..bbe2d2e8fd4 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -257,6 +257,12 @@ class ApplicationSetting < ApplicationRecord
algorithm: 'aes-256-gcm',
encode: true
+ attr_encrypted :lets_encrypt_private_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
before_validation :ensure_uuid!
before_validation :strip_sentry_values
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index e51619b0f9c..904d650ef96 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -21,6 +21,7 @@ module ApplicationSettingImplementation
after_sign_up_text: nil,
akismet_enabled: false,
allow_local_requests_from_hooks_and_services: false,
+ dns_rebinding_protection_enabled: true,
authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 18fe2a9624f..0fd8dca70b4 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -17,13 +17,11 @@ class BroadcastMessage < ApplicationRecord
default_value_for :font, '#FFFFFF'
CACHE_KEY = 'broadcast_message_current_json'.freeze
- LEGACY_CACHE_KEY = 'broadcast_message_current'.freeze
after_commit :flush_redis_cache
def self.current
messages = cache.fetch(CACHE_KEY, as: BroadcastMessage, expires_in: cache_expires_in) do
- remove_legacy_cache_key
current_and_future_messages
end
@@ -50,14 +48,6 @@ class BroadcastMessage < ApplicationRecord
nil
end
- # This can be removed in GitLab 12.0+
- # The old cache key had an indefinite lifetime, and in an HA
- # environment a one-shot migration would not work because the cache
- # would be repopulated by a node that has not been upgraded.
- def self.remove_legacy_cache_key
- cache.expire(LEGACY_CACHE_KEY)
- end
-
def active?
started? && !ended?
end
@@ -84,6 +74,5 @@ class BroadcastMessage < ApplicationRecord
def flush_redis_cache
self.class.cache.expire(CACHE_KEY)
- self.class.remove_legacy_cache_key
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 1b67a7272bc..89cc082d0bc 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -2,7 +2,6 @@
module Ci
class Build < CommitStatus
- prepend ArtifactMigratable
include Ci::Processable
include Ci::Metadatable
include Ci::Contextable
@@ -16,11 +15,15 @@ module Ci
include Gitlab::Utils::StrongMemoize
include Deployable
include HasRef
- include UpdateProjectStatistics
BuildArchivedError = Class.new(StandardError)
ignore_column :commands
+ ignore_column :artifacts_file
+ ignore_column :artifacts_metadata
+ ignore_column :artifacts_file_store
+ ignore_column :artifacts_metadata_store
+ ignore_column :artifacts_size
belongs_to :project, inverse_of: :builds
belongs_to :runner
@@ -83,13 +86,7 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts_archive, ->() do
- if Feature.enabled?(:ci_enable_legacy_artifacts)
- where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
- '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
- else
- where('EXISTS (?)',
- Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
- end
+ where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
end
scope :with_existing_job_artifacts, ->(query) do
@@ -111,8 +108,8 @@ module Ci
scope :eager_load_job_artifacts, -> { includes(:job_artifacts) }
- scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
- scope :with_archived_trace_stored_locally, -> { with_archived_trace.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
+ scope :with_artifacts_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.archive.with_files_stored_locally) }
+ scope :with_archived_trace_stored_locally, -> { with_existing_job_artifacts(Ci::JobArtifact.trace.with_files_stored_locally) }
scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
@@ -142,16 +139,10 @@ module Ci
scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) }
- ##
- # TODO: Remove these mounters when we remove :ci_enable_legacy_artifacts feature flag
- mount_uploader :legacy_artifacts_file, LegacyArtifactUploader, mount_on: :artifacts_file
- mount_uploader :legacy_artifacts_metadata, LegacyArtifactUploader, mount_on: :artifacts_metadata
-
acts_as_taggable
add_authentication_token_field :token, encrypted: :optional
- before_save :update_artifacts_size, if: :artifacts_file_changed?
before_save :ensure_token
before_destroy { unscoped_project }
@@ -159,8 +150,6 @@ module Ci
run_after_commit { BuildHooksWorker.perform_async(build.id) }
end
- update_project_statistics stat: :build_artifacts_size, attribute: :artifacts_size
-
class << self
# This is needed for url_for to work,
# as the controller is JobsController
@@ -354,7 +343,7 @@ module Ci
end
def retryable?
- !archived? && (success? || failed?)
+ !archived? && (success? || failed? || canceled?)
end
def retries_count
@@ -542,6 +531,26 @@ module Ci
trace.exist?
end
+ def artifacts_file
+ job_artifacts_archive&.file
+ end
+
+ def artifacts_size
+ job_artifacts_archive&.size
+ end
+
+ def artifacts_metadata
+ job_artifacts_metadata&.file
+ end
+
+ def artifacts?
+ !artifacts_expired? && artifacts_file&.exists?
+ end
+
+ def artifacts_metadata?
+ artifacts? && artifacts_metadata&.exists?
+ end
+
def has_job_artifacts?
job_artifacts.any?
end
@@ -610,14 +619,12 @@ module Ci
# and use that for `ExpireBuildInstanceArtifactsWorker`?
def erase_erasable_artifacts!
job_artifacts.erasable.destroy_all # rubocop: disable DestroyAll
- erase_old_artifacts!
end
def erase(opts = {})
return false unless erasable?
job_artifacts.destroy_all # rubocop: disable DestroyAll
- erase_old_artifacts!
erase_trace!
update_erased!(opts[:erased_by])
end
@@ -655,10 +662,7 @@ module Ci
end
def artifacts_file_for_type(type)
- file = job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
- # TODO: to be removed once legacy artifacts is removed
- file ||= legacy_artifacts_file if type == :archive
- file
+ job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
end
def coverage_regex
@@ -765,6 +769,10 @@ module Ci
end
end
+ def report_artifacts
+ job_artifacts.with_reports
+ end
+
# Virtual deployment status depending on the environment status.
def deployment_status
return unless starts_environment?
@@ -780,13 +788,6 @@ module Ci
private
- def erase_old_artifacts!
- # TODO: To be removed once we get rid of ci_enable_legacy_artifacts feature flag
- remove_artifacts_file!
- remove_artifacts_metadata!
- save
- end
-
def successful_deployment_status
if deployment&.last?
:last
@@ -808,10 +809,6 @@ module Ci
job_artifacts.select { |artifact| artifact.file_type.in?(report_types) }
end
- def update_artifacts_size
- self.artifacts_size = legacy_artifacts_file&.size
- end
-
def erase_trace!
trace.erase!
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 3beb76ffc2b..f80e98e5bca 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -26,10 +26,13 @@ module Ci
metrics: 'metrics.txt'
}.freeze
- TYPE_AND_FORMAT_PAIRS = {
+ INTERNAL_TYPES = {
archive: :zip,
metadata: :gzip,
- trace: :raw,
+ trace: :raw
+ }.freeze
+
+ REPORT_TYPES = {
junit: :gzip,
metrics: :gzip,
@@ -45,6 +48,8 @@ module Ci
performance: :raw
}.freeze
+ TYPE_AND_FORMAT_PAIRS = INTERNAL_TYPES.merge(REPORT_TYPES).freeze
+
belongs_to :project
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
@@ -54,7 +59,7 @@ module Ci
validate :valid_file_format?, unless: :trace?, on: :create
before_save :set_size, if: :file_changed?
- update_project_statistics stat: :build_artifacts_size
+ update_project_statistics project_statistics_name: :build_artifacts_size
after_save :update_file_store, if: :saved_change_to_file?
@@ -66,6 +71,10 @@ module Ci
where(file_type: types)
end
+ scope :with_reports, -> do
+ with_file_types(REPORT_TYPES.keys.map(&:to_s))
+ end
+
scope :test_reports, -> do
with_file_types(TEST_REPORT_FILE_TYPES)
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 80401ca0a1e..3727a9861aa 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -166,6 +166,16 @@ module Ci
end
end
+ after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
+ pipeline.run_after_commit do
+ pipeline.all_merge_requests.each do |merge_request|
+ next unless merge_request.auto_merge_enabled?
+
+ AutoMergeProcessWorker.perform_async(merge_request.id)
+ end
+ end
+ end
+
after_transition any => [:success, :failed] do |pipeline|
pipeline.run_after_commit do
PipelineNotificationWorker.perform_async(pipeline.id)
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index c0a0ca9acf6..6a4241c94bc 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -27,9 +27,13 @@ module Ci
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
+ scope :runnable_schedules, -> { active.where("next_run_at < ?", Time.now) }
+ scope :preloaded, -> { preload(:owner, :project) }
accepts_nested_attributes_for :variables, allow_destroy: true
+ alias_attribute :real_next_run, :next_run_at
+
def owned_by?(current_user)
owner == current_user
end
@@ -46,8 +50,14 @@ module Ci
update_attribute(:active, false)
end
+ ##
+ # The `next_run_at` column is set to the actual execution date of `PipelineScheduleWorker`.
+ # This way, a schedule like `*/1 * * * *` won't be triggered in a short interval
+ # when PipelineScheduleWorker runs irregularly by Sidekiq Memory Killer.
def set_next_run_at
- self.next_run_at = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now)
+ self.next_run_at = Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'],
+ Time.zone.name)
+ .next_time_from(ideal_next_run_at)
end
def schedule_next_run!
@@ -56,15 +66,15 @@ module Ci
update_attribute(:next_run_at, nil) # update without validation
end
- def real_next_run(
- worker_cron: Settings.cron_jobs['pipeline_schedule_worker']['cron'],
- worker_time_zone: Time.zone.name)
- Gitlab::Ci::CronParser.new(worker_cron, worker_time_zone)
- .next_time_from(next_run_at)
- end
-
def job_variables
variables&.map(&:to_runner_variable) || []
end
+
+ private
+
+ def ideal_next_run_at
+ Gitlab::Ci::CronParser.new(cron, cron_timezone)
+ .next_time_from(Time.zone.now)
+ end
end
end
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index bd9c453e2a4..4aaa1f941e5 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -61,6 +61,10 @@ module Clusters
"http://#{hostname}/hub/oauth_callback"
end
+ def oauth_scopes
+ 'api read_repository write_repository'
+ end
+
private
def specification
@@ -94,7 +98,8 @@ module Clusters
},
"singleuser" => {
"extraEnv" => {
- "GITLAB_CLUSTER_ID" => cluster.id.to_s
+ "GITLAB_CLUSTER_ID" => cluster.id.to_s,
+ "GITLAB_HOST" => gitlab_host
}
}
}
@@ -112,6 +117,10 @@ module Clusters
Gitlab.config.gitlab.url
end
+ def gitlab_host
+ Gitlab.config.gitlab.host
+ end
+
def content_values
YAML.load_file(chart_values_file).deep_merge!(specification)
end
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 9fbf5d8af04..d5a3bd62e3d 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -15,9 +15,6 @@ module Clusters
include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData
include AfterCommitQueue
- include ReactiveCaching
-
- self.reactive_cache_key = ->(knative) { [knative.class.model_name.singular, knative.id] }
def set_initial_status
return unless not_installable?
@@ -41,8 +38,6 @@ module Clusters
scope :for_cluster, -> (cluster) { where(cluster: cluster) }
- after_save :clear_reactive_cache!
-
def chart
'knative/knative'
end
@@ -77,55 +72,12 @@ module Clusters
ClusterWaitForIngressIpAddressWorker.perform_async(name, id)
end
- def client
- cluster.kubeclient.knative_client
- end
-
- def services
- with_reactive_cache do |data|
- data[:services]
- end
- end
-
- def calculate_reactive_cache
- { services: read_services, pods: read_pods }
- end
-
def ingress_service
cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system')
end
- def services_for(ns: namespace)
- return [] unless services
- return [] unless ns
-
- services.select do |service|
- service.dig('metadata', 'namespace') == ns
- end
- end
-
- def service_pod_details(ns, service)
- with_reactive_cache do |data|
- data[:pods].select { |pod| filter_pods(pod, ns, service) }
- end
- end
-
private
- def read_pods
- cluster.kubeclient.core_client.get_pods.as_json
- end
-
- def filter_pods(pod, namespace, service)
- pod["metadata"]["namespace"] == namespace && pod["metadata"]["labels"]["serving.knative.dev/service"] == service
- end
-
- def read_services
- client.get_services.as_json
- rescue Kubeclient::ResourceNotFoundError
- []
- end
-
def install_knative_metrics
["kubectl apply -f #{METRICS_CONFIG}"] if cluster.application_prometheus_available?
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 01711e477b1..db7fd8524c2 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.5.0'.freeze
+ VERSION = '0.5.2'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index f7ea7accab2..ccc877fb924 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -5,6 +5,7 @@ module Clusters
include Presentable
include Gitlab::Utils::StrongMemoize
include FromUnion
+ include ReactiveCaching
self.table_name = 'clusters'
@@ -57,6 +58,8 @@ module Clusters
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
+ after_save :clear_reactive_cache!
+
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
@@ -123,15 +126,19 @@ module Clusters
end
def status_name
- if provider
- provider.status_name
- else
- :created
+ provider&.status_name || connection_status.presence || :created
+ end
+
+ def connection_status
+ with_reactive_cache do |data|
+ data[:connection_status]
end
end
- def created?
- status_name == :created
+ def calculate_reactive_cache
+ return unless enabled?
+
+ { connection_status: retrieve_connection_status }
end
def applications
@@ -204,7 +211,7 @@ module Clusters
end
def kube_ingress_domain
- @kube_ingress_domain ||= domain.presence || instance_domain || legacy_auto_devops_domain
+ @kube_ingress_domain ||= domain.presence || instance_domain
end
def predefined_variables
@@ -215,12 +222,43 @@ module Clusters
end
end
+ def knative_services_finder(project)
+ @knative_services_finder ||= KnativeServicesFinder.new(self, project)
+ end
+
private
def instance_domain
@instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
end
+ def retrieve_connection_status
+ kubeclient.core_client.discover
+ rescue *Gitlab::Kubernetes::Errors::CONNECTION
+ :unreachable
+ rescue *Gitlab::Kubernetes::Errors::AUTHENTICATION
+ :authentication_failure
+ rescue Kubeclient::HttpError => e
+ kubeclient_error_status(e.message)
+ rescue => e
+ Gitlab::Sentry.track_acceptable_exception(e, extra: { cluster_id: id })
+
+ :unknown_failure
+ else
+ :connected
+ end
+
+ # KubeClient uses the same error class
+ # For connection errors (eg. timeout) and
+ # for Kubernetes errors.
+ def kubeclient_error_status(message)
+ if message&.match?(/timed out|timeout/i)
+ :unreachable
+ else
+ :authentication_failure
+ end
+ end
+
# To keep backward compatibility with AUTO_DEVOPS_DOMAIN
# environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
# is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 9b951578aee..8e06156c73d 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -11,7 +11,6 @@ module Clusters
RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
self.table_name = 'cluster_platforms_kubernetes'
- self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.id] }
belongs_to :cluster, inverse_of: :platform_kubernetes, class_name: 'Clusters::Cluster'
diff --git a/app/models/commit.rb b/app/models/commit.rb
index f412d252e5c..fa0bf36ba49 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -13,6 +13,7 @@ class Commit
include StaticModel
include Presentable
include ::Gitlab::Utils::StrongMemoize
+ include CacheMarkdownField
attr_mentionable :safe_message, pipeline: :single_line
@@ -37,13 +38,9 @@ class Commit
# Used by GFM to match and present link extensions on node texts and hrefs.
LINK_EXTENSION_PATTERN = /(patch)/.freeze
- def banzai_render_context(field)
- pipeline = field == :description ? :commit_description : :single_line
- context = { pipeline: pipeline, project: self.project }
- context[:author] = self.author if self.author
-
- context
- end
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :full_title, pipeline: :single_line
+ cache_markdown_field :description, pipeline: :commit_description
class << self
def decorate(commits, project)
diff --git a/app/models/concerns/artifact_migratable.rb b/app/models/concerns/artifact_migratable.rb
deleted file mode 100644
index 7c9f579b480..00000000000
--- a/app/models/concerns/artifact_migratable.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# frozen_string_literal: true
-
-# Adapter class to unify the interface between mounted uploaders and the
-# Ci::Artifact model
-# Meant to be prepended so the interface can stay the same
-module ArtifactMigratable
- def artifacts_file
- job_artifacts_archive&.file || legacy_artifacts_file
- end
-
- def artifacts_metadata
- job_artifacts_metadata&.file || legacy_artifacts_metadata
- end
-
- def artifacts?
- !artifacts_expired? && artifacts_file&.exists?
- end
-
- def artifacts_metadata?
- artifacts? && artifacts_metadata.exists?
- end
-
- def artifacts_file_changed?
- job_artifacts_archive&.file_changed? || attribute_changed?(:artifacts_file)
- end
-
- def remove_artifacts_file!
- if job_artifacts_archive
- job_artifacts_archive.destroy
- else
- remove_legacy_artifacts_file!
- end
- end
-
- def remove_artifacts_metadata!
- if job_artifacts_metadata
- job_artifacts_metadata.destroy
- else
- remove_legacy_artifacts_metadata!
- end
- end
-
- def artifacts_size
- read_attribute(:artifacts_size).to_i + job_artifacts.sum(:size).to_i
- end
-
- def legacy_artifacts_file
- return unless Feature.enabled?(:ci_enable_legacy_artifacts)
-
- super
- end
-
- def legacy_artifacts_metadata
- return unless Feature.enabled?(:ci_enable_legacy_artifacts)
-
- super
- end
-end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index f90cd1ea690..42203a5f214 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -13,43 +13,9 @@
module CacheMarkdownField
extend ActiveSupport::Concern
- # Increment this number every time the renderer changes its output
- CACHE_COMMONMARK_VERSION_START = 10
- CACHE_COMMONMARK_VERSION = 16
-
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
- # Knows about the relationship between markdown and html field names, and
- # stores the rendering contexts for the latter
- class FieldData
- def initialize
- @data = {}
- end
-
- delegate :[], :[]=, to: :@data
-
- def markdown_fields
- @data.keys
- end
-
- def html_field(markdown_field)
- "#{markdown_field}_html"
- end
-
- def html_fields
- markdown_fields.map { |field| html_field(field) }
- end
-
- def html_fields_whitelisted
- markdown_fields.each_with_object([]) do |field, fields|
- if @data[field].fetch(:whitelisted, false)
- fields << html_field(field)
- end
- end
- end
- end
-
def skip_project_check?
false
end
@@ -85,24 +51,22 @@ module CacheMarkdownField
end.to_h
updates['cached_markdown_version'] = latest_cached_markdown_version
- updates.each {|html_field, data| write_attribute(html_field, data) }
+ updates.each { |field, data| write_markdown_field(field, data) }
end
def refresh_markdown_cache!
updates = refresh_markdown_cache
- return unless persisted? && Gitlab::Database.read_write?
-
- update_columns(updates)
+ save_markdown(updates)
end
def cached_html_up_to_date?(markdown_field)
- html_field = cached_markdown_fields.html_field(markdown_field)
+ return false if cached_html_for(markdown_field).nil? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend
- return false if cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? # rubocop:disable GitlabSecurity/PublicSend
+ html_field = cached_markdown_fields.html_field(markdown_field)
- markdown_changed = attribute_changed?(markdown_field) || false
- html_changed = attribute_changed?(html_field) || false
+ markdown_changed = markdown_field_changed?(markdown_field)
+ html_changed = markdown_field_changed?(html_field)
latest_cached_markdown_version == cached_markdown_version &&
(html_changed || markdown_changed == html_changed)
@@ -117,21 +81,21 @@ module CacheMarkdownField
end
def cached_html_for(markdown_field)
- raise ArgumentError.new("Unknown field: #{field}") unless
+ raise ArgumentError.new("Unknown field: #{markdown_field}") unless
cached_markdown_fields.markdown_fields.include?(markdown_field)
__send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
end
def latest_cached_markdown_version
- @latest_cached_markdown_version ||= (CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) | local_version
+ @latest_cached_markdown_version ||= (Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) | local_version
end
def local_version
# because local_markdown_version is stored in application_settings which
# uses cached_markdown_version too, we check explicitly to avoid
# endless loop
- return local_markdown_version if has_attribute?(:local_markdown_version)
+ return local_markdown_version if respond_to?(:has_attribute?) && has_attribute?(:local_markdown_version)
settings = Gitlab::CurrentSettings.current_application_settings
@@ -150,32 +114,14 @@ module CacheMarkdownField
included do
cattr_reader :cached_markdown_fields do
- FieldData.new
+ Gitlab::MarkdownCache::FieldData.new
end
- # Always exclude _html fields from attributes (including serialization).
- # They contain unredacted HTML, which would be a security issue
- alias_method :attributes_before_markdown_cache, :attributes
- def attributes
- attrs = attributes_before_markdown_cache
- html_fields = cached_markdown_fields.html_fields
- whitelisted = cached_markdown_fields.html_fields_whitelisted
- exclude_fields = html_fields - whitelisted
-
- exclude_fields.each do |field|
- attrs.delete(field)
- end
-
- if whitelisted.empty?
- attrs.delete('cached_markdown_version')
- end
-
- attrs
+ if self < ActiveRecord::Base
+ include Gitlab::MarkdownCache::ActiveRecord::Extension
+ else
+ prepend Gitlab::MarkdownCache::Redis::Extension
end
-
- # Using before_update here conflicts with elasticsearch-model somehow
- before_create :refresh_markdown_cache, if: :invalidated_markdown_cache?
- before_update :refresh_markdown_cache, if: :invalidated_markdown_cache?
end
class_methods do
@@ -193,10 +139,8 @@ module CacheMarkdownField
# The HTML becomes invalid if any dependent fields change. For now, assume
# author and project invalidate the cache in all circumstances.
define_method(invalidation_method) do
- changed_fields = changed_attributes.keys
- invalidations = changed_fields & [markdown_field.to_s, *INVALIDATED_BY]
- invalidations.delete(markdown_field.to_s) if changed_fields.include?("#{markdown_field}_html")
-
+ invalidations = changed_markdown_fields & [markdown_field.to_s, *INVALIDATED_BY]
+ invalidations.delete(markdown_field.to_s) if changed_markdown_fields.include?("#{markdown_field}_html")
!invalidations.empty? || !cached_html_up_to_date?(markdown_field)
end
end
diff --git a/app/models/concerns/maskable.rb b/app/models/concerns/maskable.rb
index 2943872ffab..e0f2c41b836 100644
--- a/app/models/concerns/maskable.rb
+++ b/app/models/concerns/maskable.rb
@@ -7,9 +7,9 @@ module Maskable
# * No escape characters
# * No variables
# * No spaces
- # * Minimal length of 8 characters
+ # * Minimal length of 8 characters from the Base64 alphabets (RFC4648)
# * Absolutely no fun is allowed
- REGEX = /\A\w{8,}\z/.freeze
+ REGEX = /\A[a-zA-Z0-9_+=\/-]{8,}\z/.freeze
included do
validates :masked, inclusion: { in: [true, false] }
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index e65bbb8ca07..3deb86da6cf 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -1,28 +1,20 @@
# frozen_string_literal: true
module Milestoneish
- def closed_items_count(user)
- memoize_per_user(user, :closed_items_count) do
- (count_issues_by_state(user)['closed'] || 0) + merge_requests.closed_and_merged.size
- end
- end
-
- def total_items_count(user)
- memoize_per_user(user, :total_items_count) do
- total_issues_count(user) + merge_requests.size
- end
- end
-
def total_issues_count(user)
count_issues_by_state(user).values.sum
end
+ def closed_issues_count(user)
+ count_issues_by_state(user)['closed'].to_i
+ end
+
def complete?(user)
- total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
+ total_issues_count(user) > 0 && total_issues_count(user) == closed_issues_count(user)
end
def percent_complete(user)
- ((closed_items_count(user) * 100) / total_items_count(user)).abs
+ closed_issues_count(user) * 100 / total_issues_count(user)
rescue ZeroDivisionError
0
end
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index bfd0c36942b..4b428b0af83 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -3,14 +3,16 @@
module Noteable
extend ActiveSupport::Concern
- # `Noteable` class names that support resolvable notes.
- RESOLVABLE_TYPES = %w(MergeRequest).freeze
-
class_methods do
# `Noteable` class names that support replying to individual notes.
def replyable_types
%w(Issue MergeRequest)
end
+
+ # `Noteable` class names that support resolvable notes.
+ def resolvable_types
+ %w(MergeRequest)
+ end
end
# The timestamp of the note (e.g. the :created_at or :updated_at attribute if provided via
@@ -36,7 +38,7 @@ module Noteable
end
def supports_resolvable_notes?
- RESOLVABLE_TYPES.include?(base_class_name)
+ self.class.resolvable_types.include?(base_class_name)
end
def supports_discussions?
@@ -131,3 +133,5 @@ module Noteable
)
end
end
+
+Noteable.extend(Noteable::ClassMethods)
diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb
index 258c819f243..c2542dbe743 100644
--- a/app/models/concerns/prometheus_adapter.rb
+++ b/app/models/concerns/prometheus_adapter.rb
@@ -6,7 +6,6 @@ module PrometheusAdapter
included do
include ReactiveCaching
- self.reactive_cache_key = ->(adapter) { [adapter.class.model_name.singular, adapter.id] }
self.reactive_cache_lease_timeout = 30.seconds
self.reactive_cache_refresh_interval = 30.seconds
self.reactive_cache_lifetime = 1.minute
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index 1e09cd89550..6c3962b4c4f 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -10,8 +10,6 @@
# class Foo < ApplicationRecord
# include ReactiveCaching
#
-# self.reactive_cache_key = ->(thing) { ["foo", thing.id] }
-#
# after_save :clear_reactive_cache!
#
# def calculate_reactive_cache
@@ -89,6 +87,8 @@ module ReactiveCaching
class_attribute :reactive_cache_worker_finder
# defaults
+ self.reactive_cache_key = -> (record) { [model_name.singular, record.id] }
+
self.reactive_cache_lease_timeout = 2.minutes
self.reactive_cache_refresh_interval = 1.minute
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index 58143a32fdc..4a506146de3 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -73,6 +73,7 @@ module Referable
(?<url>
#{Regexp.escape(Gitlab.config.gitlab.url)}
\/#{Project.reference_pattern}
+ (?:\/\-)?
\/#{Regexp.escape(route)}
\/#{pattern}
(?<path>
diff --git a/app/models/concerns/resolvable_note.rb b/app/models/concerns/resolvable_note.rb
index 16ea330701d..2d2d5fb7168 100644
--- a/app/models/concerns/resolvable_note.rb
+++ b/app/models/concerns/resolvable_note.rb
@@ -12,7 +12,7 @@ module ResolvableNote
validates :resolved_by, presence: true, if: :resolved?
# Keep this scope in sync with `#potentially_resolvable?`
- scope :potentially_resolvable, -> { where(type: RESOLVABLE_TYPES).where(noteable_type: Noteable::RESOLVABLE_TYPES) }
+ scope :potentially_resolvable, -> { where(type: RESOLVABLE_TYPES).where(noteable_type: Noteable.resolvable_types) }
# Keep this scope in sync with `#resolvable?`
scope :resolvable, -> { potentially_resolvable.user }
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 2f0e078c807..b42adad94ba 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -75,4 +75,11 @@ module Taskable
def task_status_short
task_status(short: true)
end
+
+ def task_completion_status
+ @task_completion_status ||= {
+ count: tasks.summary.item_count,
+ completed_count: tasks.summary.complete_count
+ }
+ end
end
diff --git a/app/models/concerns/update_project_statistics.rb b/app/models/concerns/update_project_statistics.rb
index 67e1f0ec930..1f881249322 100644
--- a/app/models/concerns/update_project_statistics.rb
+++ b/app/models/concerns/update_project_statistics.rb
@@ -5,43 +5,47 @@
# It deals with `ProjectStatistics.increment_statistic` making sure not to update statistics on a cascade delete from the
# project, and keeping track of value deltas on each save. It updates the DB only when a change is needed.
#
-# How to use
-# - Invoke `update_project_statistics stat: :a_project_statistics_column, attribute: :an_attr_to_track` in a model class body.
+# Example:
#
-# Expectation
-# - `attribute` must be an ActiveRecord attribute
+# module Ci
+# class JobArtifact < ApplicationRecord
+# include UpdateProjectStatistics
+#
+# update_project_statistics project_statistics_name: :build_artifacts_size
+# end
+# end
+#
+# Expectation:
+#
+# - `statistic_attribute` must be an ActiveRecord attribute
# - The model must implement `project` and `project_id`. i.e. direct Project relationship or delegation
+#
module UpdateProjectStatistics
extend ActiveSupport::Concern
class_methods do
- attr_reader :statistic_name, :statistic_attribute
+ attr_reader :project_statistics_name, :statistic_attribute
- # Configure the model to update +stat+ on ProjectStatistics when +attribute+ changes
+ # Configure the model to update `project_statistics_name` on ProjectStatistics,
+ # when `statistic_attribute` changes
+ #
+ # - project_statistics_name: A column of `ProjectStatistics` to update
+ # - statistic_attribute: An attribute of the current model, default to `size`
#
- # +stat+:: a column of ProjectStatistics to update
- # +attribute+:: an attribute of the current model, default to +:size+
- def update_project_statistics(stat:, attribute: :size)
- @statistic_name = stat
- @statistic_attribute = attribute
+ def update_project_statistics(project_statistics_name:, statistic_attribute: :size)
+ @project_statistics_name = project_statistics_name
+ @statistic_attribute = statistic_attribute
after_save(:update_project_statistics_after_save, if: :update_project_statistics_attribute_changed?)
after_destroy(:update_project_statistics_after_destroy, unless: :project_destroyed?)
end
+
private :update_project_statistics
end
included do
private
- def project_destroyed?
- project.pending_delete?
- end
-
- def update_project_statistics_attribute_changed?
- saved_change_to_attribute?(self.class.statistic_attribute)
- end
-
def update_project_statistics_after_save
attr = self.class.statistic_attribute
delta = read_attribute(attr).to_i - attribute_before_last_save(attr).to_i
@@ -49,12 +53,20 @@ module UpdateProjectStatistics
update_project_statistics(delta)
end
+ def update_project_statistics_attribute_changed?
+ saved_change_to_attribute?(self.class.statistic_attribute)
+ end
+
def update_project_statistics_after_destroy
update_project_statistics(-read_attribute(self.class.statistic_attribute).to_i)
end
+ def project_destroyed?
+ project.pending_delete?
+ end
+
def update_project_statistics(delta)
- ProjectStatistics.increment_statistic(project_id, self.class.statistic_name, delta)
+ ProjectStatistics.increment_statistic(project_id, self.class.project_statistics_name, delta)
end
end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index feabea9b8ba..f75c32633b1 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -15,7 +15,9 @@ class DiffNote < Note
validates :original_position, presence: true
validates :position, presence: true
validates :line_code, presence: true, line_code: true, if: :on_text?
- validates :noteable_type, inclusion: { in: noteable_types }
+ # We need to evaluate the `noteable` types when running the validation since
+ # EE might have added a type when the module was prepended
+ validates :noteable_type, inclusion: { in: -> (_note) { noteable_types } }
validate :positions_complete
validate :verify_supported
validate :diff_refs_match_commit, if: :for_commit?
@@ -44,7 +46,7 @@ class DiffNote < Note
# Returns the diff file from `position`
def latest_diff_file
strong_memoize(:latest_diff_file) do
- position.diff_file(project.repository)
+ position.diff_file(repository)
end
end
@@ -75,7 +77,7 @@ class DiffNote < Note
end
def supports_suggestion?
- return false unless noteable.supports_suggestion? && on_text?
+ return false unless noteable&.supports_suggestion? && on_text?
# We don't want to trigger side-effects of `diff_file` call.
return false unless file = latest_diff_file
return false unless line = file.line_for_position(self.position)
@@ -88,7 +90,7 @@ class DiffNote < Note
end
def banzai_render_context(field)
- super.merge(project: project, suggestions_filter_enabled: supports_suggestion?)
+ super.merge(suggestions_filter_enabled: true)
end
private
@@ -111,7 +113,7 @@ class DiffNote < Note
if note_diff_file
diff = Gitlab::Git::Diff.new(note_diff_file.to_hash)
Gitlab::Diff::File.new(diff,
- repository: project.repository,
+ repository: repository,
diff_refs: original_position.diff_refs)
elsif created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're
@@ -122,7 +124,7 @@ class DiffNote < Note
# `Diff::FileCollection::MergeRequestDiff`.
noteable.diffs(original_position.diff_options).diff_files.first
else
- original_position.diff_file(self.project.repository)
+ original_position.diff_file(repository)
end
# Since persisted diff files already have its content "unfolded"
@@ -137,7 +139,7 @@ class DiffNote < Note
end
def set_line_code
- self.line_code = self.position.line_code(self.project.repository)
+ self.line_code = self.position.line_code(repository)
end
def verify_supported
@@ -171,6 +173,10 @@ class DiffNote < Note
shas << self.position.head_sha
end
- project.repository.keep_around(*shas)
+ repository.keep_around(*shas)
+ end
+
+ def repository
+ noteable.respond_to?(:repository) ? noteable.repository : project.repository
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 53331a19776..cdb4e6e87f6 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -126,10 +126,20 @@ class Group < Namespace
# Overrides notification_settings has_many association
# This allows to apply notification settings from parent groups
# to child groups and projects.
- def notification_settings
+ def notification_settings(hierarchy_order: nil)
source_type = self.class.base_class.name
+ settings = NotificationSetting.where(source_type: source_type, source_id: self_and_ancestors_ids)
- NotificationSetting.where(source_type: source_type, source_id: self_and_ancestors_ids)
+ return settings unless hierarchy_order && self_and_ancestors_ids.length > 1
+
+ settings
+ .joins("LEFT JOIN (#{self_and_ancestors(hierarchy_order: hierarchy_order).to_sql}) AS ordered_groups ON notification_settings.source_id = ordered_groups.id")
+ .select('notification_settings.*, ordered_groups.depth AS depth')
+ .order("ordered_groups.depth #{hierarchy_order}")
+ end
+
+ def notification_settings_for(user, hierarchy_order: nil)
+ notification_settings(hierarchy_order: hierarchy_order).where(user: user)
end
def to_reference(_from = nil, full: nil)
diff --git a/app/models/identity.rb b/app/models/identity.rb
index 8322b9bf35f..1cbd50205ed 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -13,6 +13,7 @@ class Identity < ApplicationRecord
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider?
+ scope :for_user, ->(user) { where(user: user) }
scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
diff --git a/app/models/issue.rb b/app/models/issue.rb
index eb5544f2a12..6da6fbe55cb 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -58,6 +58,7 @@ class Issue < ApplicationRecord
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
scope :order_closest_future_date, -> { reorder('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC') }
+ scope :order_relative_position_asc, -> { reorder(::Gitlab::Database.nulls_last_order('relative_position', 'ASC')) }
scope :preload_associations, -> { preload(:labels, project: :namespace) }
scope :with_api_entity_associations, -> { preload(:timelogs, :assignees, :author, :notes, :labels, project: [:route, { namespace: :route }] ) }
@@ -130,9 +131,10 @@ class Issue < ApplicationRecord
def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s
when 'closest_future_date' then order_closest_future_date
- when 'due_date' then order_due_date_asc
- when 'due_date_asc' then order_due_date_asc
- when 'due_date_desc' then order_due_date_desc
+ when 'due_date' then order_due_date_asc
+ when 'due_date_asc' then order_due_date_asc
+ when 'due_date_desc' then order_due_date_desc
+ when 'relative_position' then order_relative_position_asc
else
super
end
diff --git a/app/models/key.rb b/app/models/key.rb
index b097be8cc89..8aa25924c28 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -59,6 +59,11 @@ class Key < ApplicationRecord
"key-#{id}"
end
+ # EE overrides this
+ def can_delete?
+ true
+ end
+
# rubocop: disable CodeReuse/ServiceClass
def update_last_used_at
Keys::LastUsedService.new(self).execute
diff --git a/app/models/label.rb b/app/models/label.rb
index e9085e8bd25..b83e0862bab 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -13,7 +13,6 @@ class Label < ApplicationRecord
cache_markdown_field :description, pipeline: :single_line
DEFAULT_COLOR = '#428BCA'
- NONE = 'no label'
default_value_for :color, DEFAULT_COLOR
diff --git a/app/models/member.rb b/app/models/member.rb
index 83b4f5b29c4..c7583434148 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -80,6 +80,8 @@ class Member < ApplicationRecord
scope :owners_and_masters, -> { owners_and_maintainers } # @deprecated
scope :with_user, -> (user) { where(user: user) }
+ scope :with_source_id, ->(source_id) { where(source_id: source_id) }
+
scope :order_name_asc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'ASC')) }
scope :order_name_desc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'DESC')) }
scope :order_recent_sign_in, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.last_sign_in_at', 'DESC')) }
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 311ba1ce6bd..7481f1939ad 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -165,7 +165,7 @@ class MergeRequest < ApplicationRecord
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
- validates :merge_user, presence: true, if: :merge_when_pipeline_succeeds?, unless: :importing?
+ validates :merge_user, presence: true, if: :auto_merge_enabled?, unless: :importing?
validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_fork, unless: :closed_without_fork?
validate :validate_target_project, on: :create
@@ -196,6 +196,7 @@ class MergeRequest < ApplicationRecord
alias_attribute :project, :target_project
alias_attribute :project_id, :target_project_id
+ alias_attribute :auto_merge_enabled, :merge_when_pipeline_succeeds
def self.reference_prefix
'!'
@@ -391,7 +392,7 @@ class MergeRequest < ApplicationRecord
def merge_participants
participants = [author]
- if merge_when_pipeline_succeeds? && !participants.include?(merge_user)
+ if auto_merge_enabled? && !participants.include?(merge_user)
participants << merge_user
end
@@ -588,6 +589,8 @@ class MergeRequest < ApplicationRecord
return
end
+ [:source_branch, :target_branch].each { |attr| validate_branch_name(attr) }
+
if opened?
similar_mrs = target_project
.merge_requests
@@ -608,6 +611,16 @@ class MergeRequest < ApplicationRecord
end
end
+ def validate_branch_name(attr)
+ return unless changes_include?(attr)
+
+ branch = read_attribute(attr)
+
+ return unless branch
+
+ errors.add(attr) unless Gitlab::GitRefValidator.validate_merge_request_branch(branch)
+ end
+
def validate_target_project
return true if target_project.merge_requests_enabled?
@@ -782,7 +795,7 @@ class MergeRequest < ApplicationRecord
project.ff_merge_must_be_possible? && !ff_merge_possible?
end
- def can_cancel_merge_when_pipeline_succeeds?(current_user)
+ def can_cancel_auto_merge?(current_user)
can_be_merged_by?(current_user) || self.author == current_user
end
@@ -801,6 +814,16 @@ class MergeRequest < ApplicationRecord
Gitlab::Utils.to_boolean(merge_params['force_remove_source_branch'])
end
+ def auto_merge_strategy
+ return unless auto_merge_enabled?
+
+ merge_params['auto_merge_strategy'] || AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
+ end
+
+ def auto_merge_strategy=(strategy)
+ merge_params['auto_merge_strategy'] = strategy
+ end
+
def remove_source_branch?
should_remove_source_branch? || force_remove_source_branch?
end
@@ -973,20 +996,6 @@ class MergeRequest < ApplicationRecord
end
end
- def reset_merge_when_pipeline_succeeds
- return unless merge_when_pipeline_succeeds?
-
- self.merge_when_pipeline_succeeds = false
- self.merge_user = nil
- if merge_params
- merge_params.delete('should_remove_source_branch')
- merge_params.delete('commit_message')
- merge_params.delete('squash_commit_message')
- end
-
- self.save
- end
-
# Return array of possible target branches
# depends on target project of MR
def target_branches
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 787600569fa..37c129e843a 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -58,6 +58,7 @@ class Milestone < ApplicationRecord
validate :uniqueness_of_title, if: :title_changed?
validate :milestone_type_check
validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? }
+ validate :dates_within_4_digits
strip_attributes :title
@@ -326,6 +327,16 @@ class Milestone < ApplicationRecord
end
end
+ def dates_within_4_digits
+ if start_date && start_date > Date.new(9999, 12, 31)
+ errors.add(:start_date, _("date must not be after 9999-12-31"))
+ end
+
+ if due_date && due_date > Date.new(9999, 12, 31)
+ errors.add(:due_date, _("date must not be after 9999-12-31"))
+ end
+ end
+
def issues_finder_params
{ project_id: project_id }
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 7393ef4b05c..3c270c7396a 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -76,6 +76,7 @@ class Namespace < ApplicationRecord
'namespaces.*',
'COALESCE(SUM(ps.storage_size), 0) AS storage_size',
'COALESCE(SUM(ps.repository_size), 0) AS repository_size',
+ 'COALESCE(SUM(ps.wiki_size), 0) AS wiki_size',
'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size',
'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size',
'COALESCE(SUM(ps.packages_size), 0) AS packages_size'
@@ -205,12 +206,12 @@ class Namespace < ApplicationRecord
.ancestors(upto: top, hierarchy_order: hierarchy_order)
end
- def self_and_ancestors
+ def self_and_ancestors(hierarchy_order: nil)
return self.class.where(id: id) unless parent_id
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
- .base_and_ancestors
+ .base_and_ancestors(hierarchy_order: hierarchy_order)
end
# Returns all the descendants of the current namespace.
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index 6889e0d776b..9b2bbb7eba5 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -50,7 +50,7 @@ class NotificationRecipient
when :mention
@type == :mention
when :participating
- !excluded_participating_action? && %i[participating mention watch].include?(@type)
+ @custom_action == :failed_pipeline || %i[participating mention].include?(@type)
when :custom
custom_enabled? || %i[participating mention].include?(@type)
when :watch
@@ -106,12 +106,6 @@ class NotificationRecipient
NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action)
end
- def excluded_participating_action?
- return false unless @custom_action
-
- NotificationSetting::EXCLUDED_PARTICIPATING_EVENTS.include?(@custom_action)
- end
-
private
def read_ability
@@ -156,23 +150,11 @@ class NotificationRecipient
# Returns the notification_setting of the lowest group in hierarchy with non global level
def closest_non_global_group_notification_settting
return unless @group
- return if indexed_group_notification_settings.empty?
-
- notification_setting = nil
- @group.self_and_ancestors_ids.each do |id|
- notification_setting = indexed_group_notification_settings[id]
- break if notification_setting
- end
-
- notification_setting
- end
-
- def indexed_group_notification_settings
- strong_memoize(:indexed_group_notification_settings) do
- @group.notification_settings.where(user_id: user.id)
- .where.not(level: NotificationSetting.levels[:global])
- .index_by(&:source_id)
- end
+ @group
+ .notification_settings(hierarchy_order: :asc)
+ .where(user: user)
+ .where.not(level: NotificationSetting.levels[:global])
+ .first
end
end
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 61af5c09ae4..8306b11a7b6 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -54,14 +54,11 @@ class NotificationSetting < ApplicationRecord
self.class.email_events(source)
end
- EXCLUDED_PARTICIPATING_EVENTS = [
- :success_pipeline
- ].freeze
-
EXCLUDED_WATCHER_EVENTS = [
:push_to_merge_request,
- :issue_due
- ].push(*EXCLUDED_PARTICIPATING_EVENTS).freeze
+ :issue_due,
+ :success_pipeline
+ ].freeze
def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source)
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 407d85b1520..524df30289e 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -5,6 +5,7 @@ class PagesDomain < ApplicationRecord
VERIFICATION_THRESHOLD = 3.days.freeze
belongs_to :project
+ has_many :acme_orders, class_name: "PagesDomainAcmeOrder"
validates :domain, hostname: { allow_numeric_hostname: true }
validates :domain, uniqueness: { case_sensitive: false }
@@ -134,6 +135,14 @@ class PagesDomain < ApplicationRecord
"#{VERIFICATION_KEY}=#{verification_code}"
end
+ def certificate=(certificate)
+ super(certificate)
+
+ # set nil, if certificate is nil
+ self.certificate_valid_not_before = x509&.not_before
+ self.certificate_valid_not_after = x509&.not_after
+ end
+
private
def set_verification_code
@@ -186,7 +195,7 @@ class PagesDomain < ApplicationRecord
end
def x509
- return unless certificate
+ return unless certificate.present?
@x509 ||= OpenSSL::X509::Certificate.new(certificate)
rescue OpenSSL::X509::CertificateError
diff --git a/app/models/pages_domain_acme_order.rb b/app/models/pages_domain_acme_order.rb
new file mode 100644
index 00000000000..63d7fbc8206
--- /dev/null
+++ b/app/models/pages_domain_acme_order.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class PagesDomainAcmeOrder < ApplicationRecord
+ belongs_to :pages_domain
+
+ scope :expired, -> { where("expires_at < ?", Time.now) }
+
+ validates :pages_domain, presence: true
+ validates :expires_at, presence: true
+ validates :url, presence: true
+ validates :challenge_token, presence: true
+ validates :challenge_file_content, presence: true
+ validates :private_key, presence: true
+
+ attr_encrypted :private_key,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm',
+ encode: true
+
+ def self.find_by_domain_and_token(domain_name, challenge_token)
+ joins(:pages_domain).find_by(pages_domains: { domain: domain_name }, challenge_token: challenge_token)
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index ab4da61dcf8..e64a4b313aa 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -292,6 +292,7 @@ class Project < ApplicationRecord
accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data
accepts_nested_attributes_for :auto_devops, update_only: true
+ accepts_nested_attributes_for :ci_cd_settings, update_only: true
accepts_nested_attributes_for :remote_mirrors,
allow_destroy: true,
@@ -309,6 +310,8 @@ class Project < ApplicationRecord
delegate :group_clusters_enabled?, to: :group, allow_nil: true
delegate :root_ancestor, to: :namespace, allow_nil: true
delegate :last_pipeline, to: :commit, allow_nil: true
+ delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
+ delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings
# Validations
validates :creator, presence: true, on: :create
@@ -406,6 +409,7 @@ class Project < ApplicationRecord
scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) }
+ scope :with_merge_requests_available_for_user, ->(current_user) { with_feature_available_for_user(:merge_requests, current_user) }
scope :with_merge_requests_enabled, -> { with_feature_enabled(:merge_requests) }
scope :with_remote_mirrors, -> { joins(:remote_mirrors).where(remote_mirrors: { enabled: true }).distinct }
@@ -596,6 +600,17 @@ class Project < ApplicationRecord
def group_ids
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end
+
+ # Returns ids of projects with milestones available for given user
+ #
+ # Used on queries to find milestones which user can see
+ # For example: Milestone.where(project_id: ids_with_milestone_available_for(user))
+ def ids_with_milestone_available_for(user)
+ with_issues_enabled = with_issues_available_for_user(user).select(:id)
+ with_merge_requests_enabled = with_merge_requests_available_for_user(user).select(:id)
+
+ from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
+ end
end
def all_pipelines
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index f972c40f317..67c12363a3c 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -1,6 +1,10 @@
# frozen_string_literal: true
class ProjectAutoDevops < ApplicationRecord
+ include IgnorableColumn
+
+ ignore_column :domain
+
belongs_to :project
enum deploy_strategy: {
@@ -12,31 +16,10 @@ class ProjectAutoDevops < ApplicationRecord
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
- validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
-
after_save :create_gitlab_deploy_token, if: :needs_to_create_deploy_token?
- def instance_domain
- Gitlab::CurrentSettings.auto_devops_domain
- end
-
- def has_domain?
- domain.present? || instance_domain.present?
- end
-
- # From 11.8, AUTO_DEVOPS_DOMAIN has been replaced by KUBE_INGRESS_BASE_DOMAIN.
- # See Clusters::Cluster#predefined_variables and https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580
- # for more info.
- #
- # Suppport AUTO_DEVOPS_DOMAIN is scheduled to be removed on
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/52363
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
- if has_domain?
- variables.append(key: 'AUTO_DEVOPS_DOMAIN',
- value: domain.presence || instance_domain)
- end
-
variables.concat(deployment_strategy_default_variables)
end
end
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index 1414164b703..821e022f51b 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -6,6 +6,18 @@ class ProjectCiCdSetting < ApplicationRecord
# The version of the schema that first introduced this model/table.
MINIMUM_SCHEMA_VERSION = 20180403035759
+ DEFAULT_GIT_DEPTH = 50
+
+ before_create :set_default_git_depth
+
+ validates :default_git_depth,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: 0,
+ less_than_or_equal_to: 1000
+ },
+ allow_nil: true
+
def self.available?
@available ||=
ActiveRecord::Migrator.current_version >= MINIMUM_SCHEMA_VERSION
@@ -15,4 +27,12 @@ class ProjectCiCdSetting < ApplicationRecord
@available = nil
super
end
+
+ private
+
+ def set_default_git_depth
+ return unless Feature.enabled?(:ci_set_project_default_git_depth, default_enabled: true)
+
+ self.default_git_depth ||= DEFAULT_GIT_DEPTH
+ end
end
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 0542581c6e0..6bcb051bff6 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -72,6 +72,8 @@ class ProjectFeature < ApplicationRecord
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
+ scope :for_project_id, -> (project) { where(project: project) }
+
def feature_available?(feature, user)
# This feature might not be behind a feature flag at all, so default to true
return false unless ::Feature.enabled?(feature, user, default_enabled: true)
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index 7ba69370f14..ae5d5038099 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -2,11 +2,11 @@
class PipelinesEmailService < Service
prop_accessor :recipients
- boolean_accessor :notify_only_broken_pipelines
+ boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch
validates :recipients, presence: true, if: :valid_recipients?
def initialize_properties
- self.properties ||= { notify_only_broken_pipelines: true }
+ self.properties ||= { notify_only_broken_pipelines: true, notify_only_default_branch: false }
end
def title
@@ -54,7 +54,9 @@ class PipelinesEmailService < Service
placeholder: _('Emails separated by comma'),
required: true },
{ type: 'checkbox',
- name: 'notify_only_broken_pipelines' }
+ name: 'notify_only_broken_pipelines' },
+ { type: 'checkbox',
+ name: 'notify_only_default_branch' }
]
end
@@ -67,6 +69,16 @@ class PipelinesEmailService < Service
end
def should_pipeline_be_notified?(data)
+ notify_for_pipeline_branch?(data) && notify_for_pipeline?(data)
+ end
+
+ def notify_for_pipeline_branch?(data)
+ return true unless notify_only_default_branch?
+
+ data[:object_attributes][:ref] == data[:project][:default_branch]
+ end
+
+ def notify_for_pipeline?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
diff --git a/app/models/project_services/youtrack_service.rb b/app/models/project_services/youtrack_service.rb
index 957be685aea..175c2ebf197 100644
--- a/app/models/project_services/youtrack_service.rb
+++ b/app/models/project_services/youtrack_service.rb
@@ -5,12 +5,12 @@ class YoutrackService < IssueTrackerService
prop_accessor :description, :project_url, :issues_url
- # {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1
+ # {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1, gl-030
def self.reference_pattern(only_long: false)
if only_long
- /(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)/
+ /(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+)/
else
- /(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)|(#{Issue.reference_prefix}(?<issue>\d+))/
+ /(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+)|(#{Issue.reference_prefix}(?<issue>\d+))/
end
end
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 6fe8cb40d25..8a179b4d56d 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -4,11 +4,20 @@ class ProjectStatistics < ApplicationRecord
belongs_to :project
belongs_to :namespace
+ default_value_for :wiki_size, 0
+
+ # older migrations fail due to non-existent attribute without this
+ def wiki_size
+ has_attribute?(:wiki_size) ? super : 0
+ end
+
before_save :update_storage_size
- COLUMNS_TO_REFRESH = [:repository_size, :lfs_objects_size, :commit_count].freeze
+ COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count].freeze
INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size], packages_size: %i[storage_size] }.freeze
+ scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) }
+
def total_repository_size
repository_size + lfs_objects_size
end
@@ -27,22 +36,25 @@ class ProjectStatistics < ApplicationRecord
self.commit_count = project.repository.commit_count
end
- # Repository#size needs to be converted from MB to Byte.
def update_repository_size
self.repository_size = project.repository.size * 1.megabyte
end
+ def update_wiki_size
+ self.wiki_size = project.wiki.repository.size * 1.megabyte
+ end
+
def update_lfs_objects_size
self.lfs_objects_size = project.lfs_objects.sum(:size)
end
# older migrations fail due to non-existent attribute without this
def packages_size
- has_attribute?(:packages_size) ? super.to_i : 0
+ has_attribute?(:packages_size) ? super : 0
end
def update_storage_size
- self.storage_size = repository_size + lfs_objects_size + build_artifacts_size + packages_size
+ self.storage_size = repository_size + wiki_size.to_i + lfs_objects_size + build_artifacts_size + packages_size
end
# Since this incremental update method does not call update_storage_size above,
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 5dcc3e9945a..f1fc5e599eb 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -38,7 +38,9 @@ class Todo < ApplicationRecord
self
end
}, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations
+
belongs_to :user
+ belongs_to :issue, -> { where("target_type = 'Issue'") }, foreign_key: :target_id
delegate :name, :email, to: :author, prefix: true, allow_nil: true
@@ -59,6 +61,7 @@ class Todo < ApplicationRecord
scope :for_target, -> (id) { where(target_id: id) }
scope :for_commit, -> (id) { where(commit_id: id) }
scope :with_api_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) }
+ scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) }
state_machine :state, initial: :pending do
event :done do
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 3218c04b219..728a3040227 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -164,6 +164,7 @@ class ProjectPolicy < BasePolicy
enable :set_issue_iid
enable :set_issue_created_at
+ enable :set_issue_updated_at
enable :set_note_created_at
end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index 6323c1b3389..91c9abe750b 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class BlobPresenter < Gitlab::View::Presenter::Simple
+class BlobPresenter < Gitlab::View::Presenter::Delegated
presents :blob
def highlight(plain: nil)
@@ -13,4 +13,8 @@ class BlobPresenter < Gitlab::View::Presenter::Simple
plain: plain
)
end
+
+ def web_url
+ Gitlab::Routing.url_helpers.project_blob_url(blob.repository.project, File.join(blob.commit_id, blob.path))
+ end
end
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index ed3daf6585b..c75f62149f8 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -4,7 +4,6 @@ module Ci
class BuildRunnerPresenter < SimpleDelegator
include Gitlab::Utils::StrongMemoize
- DEFAULT_GIT_DEPTH_MERGE_REQUEST = 10
RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze
RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze
@@ -26,20 +25,20 @@ module Ci
end
def git_depth
- strong_memoize(:git_depth) do
- git_depth = variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }&.dig(:value)
- git_depth ||= DEFAULT_GIT_DEPTH_MERGE_REQUEST if merge_request_ref?
- git_depth.to_i
- end
+ if git_depth_variable
+ git_depth_variable[:value]
+ elsif Feature.enabled?(:ci_project_git_depth, default_enabled: true)
+ project.default_git_depth
+ end.to_i
end
def refspecs
specs = []
+ specs << refspec_for_merge_request_ref if merge_request_ref?
if git_depth > 0
specs << refspec_for_branch(ref) if branch? || legacy_detached_merge_request_pipeline?
specs << refspec_for_tag(ref) if tag?
- specs << refspec_for_merge_request_ref if merge_request_ref?
else
specs << refspec_for_branch
specs << refspec_for_tag
@@ -90,5 +89,11 @@ module Ci
def refspec_for_merge_request_ref
"+#{ref}:#{ref}"
end
+
+ def git_depth_variable
+ strong_memoize(:git_depth_variable) do
+ variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }
+ end
+ end
end
end
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index 944895904fe..358473d0a74 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -43,7 +43,7 @@ module Ci
if pipeline.ref_exists?
_("for %{link_to_pipeline_ref}").html_safe % { link_to_pipeline_ref: link_to_pipeline_ref }
else
- _("for %{ref}") % { ref: content_tag(:span, pipeline.ref, class: 'ref-name') }
+ _("for %{ref}").html_safe % { ref: content_tag(:span, pipeline.ref, class: 'ref-name') }
end
end
end
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index 33b217c8498..1634d2479a0 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -22,10 +22,6 @@ module Clusters
"https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp?
end
- def can_toggle_cluster?
- can?(current_user, :update_cluster, cluster) && created?
- end
-
def can_read_cluster?
can?(current_user, :read_cluster, cluster)
end
diff --git a/app/presenters/issue_presenter.rb b/app/presenters/issue_presenter.rb
index c12a202efbc..c9dc0dbf443 100644
--- a/app/presenters/issue_presenter.rb
+++ b/app/presenters/issue_presenter.rb
@@ -4,6 +4,16 @@ class IssuePresenter < Gitlab::View::Presenter::Delegated
presents :issue
def web_url
- Gitlab::UrlBuilder.build(issue)
+ url_builder.url
+ end
+
+ def issue_path
+ url_builder.issue_path(issue)
+ end
+
+ private
+
+ def url_builder
+ @url_builder ||= Gitlab::UrlBuilder.new(issue)
end
end
diff --git a/app/presenters/label_presenter.rb b/app/presenters/label_presenter.rb
index 5227ef353c3..1077bf543d9 100644
--- a/app/presenters/label_presenter.rb
+++ b/app/presenters/label_presenter.rb
@@ -35,6 +35,14 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated
issuable_subject.is_a?(Project) && label.is_a?(GroupLabel)
end
+ def project_label?
+ label.is_a?(ProjectLabel)
+ end
+
+ def subject_name
+ label.subject.name
+ end
+
private
def context_subject
diff --git a/app/presenters/member_presenter.rb b/app/presenters/member_presenter.rb
index 9e9b6973b8e..2561c3f0244 100644
--- a/app/presenters/member_presenter.rb
+++ b/app/presenters/member_presenter.rb
@@ -32,6 +32,11 @@ class MemberPresenter < Gitlab::View::Presenter::Delegated
request? && can_update?
end
+ # This functionality is only available in EE.
+ def can_override?
+ false
+ end
+
private
def admin_member_permission
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index ba0711ca867..9c44ed711a6 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -22,9 +22,9 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
end
- def cancel_merge_when_pipeline_succeeds_path
- if can_cancel_merge_when_pipeline_succeeds?(current_user)
- cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request)
+ def cancel_auto_merge_path
+ if can_cancel_auto_merge?(current_user)
+ cancel_auto_merge_project_merge_request_path(project, merge_request)
end
end
diff --git a/app/presenters/tree_entry_presenter.rb b/app/presenters/tree_entry_presenter.rb
new file mode 100644
index 00000000000..7bb10cd1455
--- /dev/null
+++ b/app/presenters/tree_entry_presenter.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class TreeEntryPresenter < Gitlab::View::Presenter::Delegated
+ presents :tree
+
+ def web_url
+ Gitlab::Routing.url_helpers.project_tree_url(tree.repository.project, File.join(tree.commit_id, tree.path))
+ end
+end
diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb
index ae7c20c3bba..8bc6da5aeeb 100644
--- a/app/serializers/analytics_stage_entity.rb
+++ b/app/serializers/analytics_stage_entity.rb
@@ -9,7 +9,8 @@ class AnalyticsStageEntity < Grape::Entity
expose :description
expose :median, as: :value do |stage|
- # median returns a BatchLoader instance which we first have to unwrap by using to_i
- !stage.median.to_i.zero? ? distance_of_time_in_words(stage.median) : nil
+ # median returns a BatchLoader instance which we first have to unwrap by using to_f
+ # we use to_f to make sure results below 1 are presented to the end-user
+ stage.median.to_f.nonzero? ? distance_of_time_in_words(stage.median) : nil
end
end
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 62c26809eeb..67e44ee9d10 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -8,16 +8,18 @@ class BuildDetailsEntity < JobEntity
expose :stuck?, as: :stuck
expose :user, using: UserEntity
expose :runner, using: RunnerEntity
+ expose :metadata, using: BuildMetadataEntity
expose :pipeline, using: PipelineEntity
expose :deployment_status, if: -> (*) { build.starts_environment? } do
expose :deployment_status, as: :status
-
- expose :persisted_environment, as: :environment, with: EnvironmentEntity
+ expose :persisted_environment, as: :environment do |build, options|
+ options.merge(deployment_details: false).yield_self do |opts|
+ EnvironmentEntity.represent(build.persisted_environment, opts)
+ end
+ end
end
- expose :metadata, using: BuildMetadataEntity
-
expose :artifact, if: -> (*) { can?(current_user, :read_build, build) } do
expose :download_path, if: -> (*) { build.artifacts? } do |build|
download_project_job_artifacts_path(project, build)
@@ -40,6 +42,11 @@ class BuildDetailsEntity < JobEntity
end
end
+ expose :report_artifacts,
+ as: :reports,
+ using: JobArtifactReportEntity,
+ if: -> (*) { can?(current_user, :read_build, build) }
+
expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build|
erase_project_job_path(project, build)
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index 34ae06278c8..943c707218d 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -20,16 +20,39 @@ class DeploymentEntity < Grape::Entity
expose :created_at
expose :tag
expose :last?
-
expose :user, using: UserEntity
- expose :commit, using: CommitEntity
- expose :deployable, using: JobEntity
- expose :manual_actions, using: JobEntity, if: -> (*) { can_create_deployment? }
- expose :scheduled_actions, using: JobEntity, if: -> (*) { can_create_deployment? }
+
+ expose :deployable do |deployment, opts|
+ deployment.deployable.yield_self do |deployable|
+ if include_details?
+ JobEntity.represent(deployable, opts)
+ elsif can_read_deployables?
+ { name: deployable.name,
+ build_path: project_job_path(deployable.project, deployable) }
+ end
+ end
+ end
+
+ expose :commit, using: CommitEntity, if: -> (*) { include_details? }
+ expose :manual_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
+ expose :scheduled_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
private
+ def include_details?
+ options.fetch(:deployment_details, true)
+ end
+
def can_create_deployment?
can?(request.current_user, :create_deployment, request.project)
end
+
+ def can_read_deployables?
+ ##
+ # We intentionally do not check `:read_build, deployment.deployable`
+ # because it triggers a policy evaluation that involves multiple
+ # Gitaly calls that might not be cached.
+ #
+ can?(request.current_user, :read_build, request.project)
+ end
end
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 914ad628a99..36e601f45c5 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -44,4 +44,12 @@ class IssueEntity < IssuableEntity
expose :preview_note_path do |issue|
preview_markdown_path(issue.project, target_type: 'Issue', target_id: issue.iid)
end
+
+ expose :confidential_issues_docs_path, if: -> (issue) { issue.confidential? } do |issue|
+ help_page_path('user/project/issues/confidential_issues.md')
+ end
+
+ expose :locked_discussion_docs_path, if: -> (issue) { issue.discussion_locked? } do |issue|
+ help_page_path('user/discussions/index.md', anchor: 'lock-discussions')
+ end
end
diff --git a/app/serializers/job_artifact_report_entity.rb b/app/serializers/job_artifact_report_entity.rb
new file mode 100644
index 00000000000..4280351a6b0
--- /dev/null
+++ b/app/serializers/job_artifact_report_entity.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class JobArtifactReportEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :file_type
+ expose :file_format
+ expose :size
+
+ expose :download_path do |artifact|
+ download_project_job_artifacts_path(artifact.job.project, artifact.job, file_type: artifact.file_format)
+ end
+end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index b130f447cce..a428930dbbf 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -9,7 +9,11 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_params
expose :merge_status
expose :merge_user_id
- expose :merge_when_pipeline_succeeds
+ expose :auto_merge_enabled
+ expose :auto_merge_strategy
+ expose :available_auto_merge_strategies do |merge_request|
+ AutoMergeService.new(merge_request.project, current_user).available_strategies(merge_request) # rubocop: disable CodeReuse/ServiceClass
+ end
expose :source_branch
expose :source_branch_protected do |merge_request|
merge_request.source_project.present? && ProtectedBranch.protected?(merge_request.source_project, merge_request.source_branch)
@@ -182,8 +186,8 @@ class MergeRequestWidgetEntity < IssuableEntity
presenter(merge_request).remove_wip_path
end
- expose :cancel_merge_when_pipeline_succeeds_path do |merge_request|
- presenter(merge_request).cancel_merge_when_pipeline_succeeds_path
+ expose :cancel_auto_merge_path do |merge_request|
+ presenter(merge_request).cancel_auto_merge_path
end
expose :create_issue_to_resolve_discussions_path do |merge_request|
diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb
index d78ad4af4dc..dfef4364965 100644
--- a/app/serializers/pipeline_details_entity.rb
+++ b/app/serializers/pipeline_details_entity.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
class PipelineDetailsEntity < PipelineEntity
+ expose :flags do
+ expose :latest?, as: :latest
+ end
+
expose :details do
- expose :ordered_stages, as: :stages, using: StageEntity
expose :artifacts, using: BuildArtifactEntity
expose :manual_actions, using: BuildActionEntity
expose :scheduled_actions, using: BuildActionEntity
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 8fe5df81e6c..ec2698ecbe3 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -4,6 +4,7 @@ class PipelineEntity < Grape::Entity
include RequestAwareEntity
expose :id
+ expose :iid
expose :user, using: UserEntity
expose :active?, as: :active
@@ -20,7 +21,6 @@ class PipelineEntity < Grape::Entity
end
expose :flags do
- expose :latest?, as: :latest
expose :stuck?, as: :stuck
expose :auto_devops_source?, as: :auto_devops
expose :merge_request_event?, as: :merge_request
@@ -34,6 +34,7 @@ class PipelineEntity < Grape::Entity
expose :details do
expose :detailed_status, as: :status, with: DetailedStatusEntity
+ expose :ordered_stages, as: :stages, using: StageEntity
expose :duration
expose :finished_at
end
diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb
new file mode 100644
index 00000000000..d726085b89a
--- /dev/null
+++ b/app/services/auto_merge/base_service.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module AutoMerge
+ class BaseService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ def execute(merge_request)
+ merge_request.merge_params.merge!(params)
+ merge_request.auto_merge_enabled = true
+ merge_request.merge_user = current_user
+ merge_request.auto_merge_strategy = strategy
+
+ return :failed unless merge_request.save
+
+ yield if block_given?
+
+ # Notify the event that auto merge is enabled or merge param is updated
+ AutoMergeProcessWorker.perform_async(merge_request.id)
+
+ strategy.to_sym
+ end
+
+ def update(merge_request)
+ merge_request.merge_params.merge!(params)
+
+ return :failed unless merge_request.save
+
+ strategy.to_sym
+ end
+
+ def cancel(merge_request)
+ if cancel_auto_merge(merge_request)
+ yield if block_given?
+
+ success
+ else
+ error("Can't cancel the automatic merge", 406)
+ end
+ end
+
+ private
+
+ def strategy
+ strong_memoize(:strategy) do
+ self.class.name.demodulize.remove('Service').underscore
+ end
+ end
+
+ def cancel_auto_merge(merge_request)
+ merge_request.auto_merge_enabled = false
+ merge_request.merge_user = nil
+
+ merge_request.merge_params&.except!(
+ 'should_remove_source_branch',
+ 'commit_message',
+ 'squash_commit_message',
+ 'auto_merge_strategy'
+ )
+
+ merge_request.save
+ end
+ end
+end
diff --git a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
new file mode 100644
index 00000000000..c41073a73e9
--- /dev/null
+++ b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module AutoMerge
+ class MergeWhenPipelineSucceedsService < AutoMerge::BaseService
+ def execute(merge_request)
+ super do
+ if merge_request.saved_change_to_auto_merge_enabled?
+ SystemNoteService.merge_when_pipeline_succeeds(merge_request, project, current_user, merge_request.diff_head_commit)
+ end
+ end
+ end
+
+ def process(merge_request)
+ return unless merge_request.actual_head_pipeline&.success?
+ return unless merge_request.mergeable?
+
+ merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params)
+ end
+
+ def cancel(merge_request)
+ super do
+ SystemNoteService.cancel_merge_when_pipeline_succeeds(merge_request, @project, @current_user)
+ end
+ end
+
+ def available_for?(merge_request)
+ merge_request.actual_head_pipeline&.active?
+ end
+ end
+end
diff --git a/app/services/auto_merge_service.rb b/app/services/auto_merge_service.rb
new file mode 100644
index 00000000000..926d2f5fc66
--- /dev/null
+++ b/app/services/auto_merge_service.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+class AutoMergeService < BaseService
+ STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds'.freeze
+ STRATEGIES = [STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS].freeze
+
+ class << self
+ def all_strategies
+ STRATEGIES
+ end
+
+ def get_service_class(strategy)
+ return unless all_strategies.include?(strategy)
+
+ "::AutoMerge::#{strategy.camelize}Service".constantize
+ end
+ end
+
+ def execute(merge_request, strategy)
+ service = get_service_instance(strategy)
+
+ return :failed unless service&.available_for?(merge_request)
+
+ service.execute(merge_request)
+ end
+
+ def update(merge_request)
+ return :failed unless merge_request.auto_merge_enabled?
+
+ get_service_instance(merge_request.auto_merge_strategy).update(merge_request)
+ end
+
+ def process(merge_request)
+ return unless merge_request.auto_merge_enabled?
+
+ get_service_instance(merge_request.auto_merge_strategy).process(merge_request)
+ end
+
+ def cancel(merge_request)
+ return error("Can't cancel the automatic merge", 406) unless merge_request.auto_merge_enabled?
+
+ get_service_instance(merge_request.auto_merge_strategy).cancel(merge_request)
+ end
+
+ def available_strategies(merge_request)
+ self.class.all_strategies.select do |strategy|
+ get_service_instance(strategy).available_for?(merge_request)
+ end
+ end
+
+ private
+
+ def get_service_instance(strategy)
+ self.class.get_service_class(strategy)&.new(project, current_user, params)
+ end
+end
diff --git a/app/services/ci/pipeline_schedule_service.rb b/app/services/ci/pipeline_schedule_service.rb
new file mode 100644
index 00000000000..5b5e9a26520
--- /dev/null
+++ b/app/services/ci/pipeline_schedule_service.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Ci
+ class PipelineScheduleService < BaseService
+ def execute(schedule)
+ # Ensure `next_run_at` is set properly before creating a pipeline.
+ # Otherwise, multiple pipelines could be created in a short interval.
+ schedule.schedule_next_run!
+
+ RunPipelineScheduleWorker.perform_async(schedule.id, schedule.owner&.id)
+ end
+ end
+end
diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb
index 14a45437287..a9feb60be6e 100644
--- a/app/services/clusters/applications/base_service.rb
+++ b/app/services/clusters/applications/base_service.rb
@@ -81,7 +81,7 @@ module Clusters
oauth_application_params = {
name: params[:application],
redirect_uri: application.callback_url,
- scopes: 'api read_user openid',
+ scopes: application.oauth_scopes,
owner: current_user
}
diff --git a/app/services/issuable/clone/content_rewriter.rb b/app/services/issuable/clone/content_rewriter.rb
index e1e0b75085d..00d7078859d 100644
--- a/app/services/issuable/clone/content_rewriter.rb
+++ b/app/services/issuable/clone/content_rewriter.rb
@@ -28,6 +28,7 @@ module Issuable
new_params = {
project: new_entity.project, noteable: new_entity,
note: rewrite_content(new_note.note),
+ note_html: nil,
created_at: note.created_at,
updated_at: note.updated_at
}
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 2a19e57a94f..805721212ba 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -29,7 +29,7 @@ module Issues
event_service.close_issue(issue, current_user)
create_note(issue, closed_via) if system_note
- closed_via = "commit #{closed_via.id}" if closed_via.is_a?(Commit)
+ closed_via = _("commit %{commit_id}") % { commit_id: closed_via.id } if closed_via.is_a?(Commit)
notification_service.async.close_issue(issue, current_user, closed_via: closed_via) if notifications
todo_service.close_issue(issue, current_user)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index bb9062e9b40..c34fbeb2adb 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -60,31 +60,7 @@ module MergeRequests
end
def create_pipeline_for(merge_request, user)
- return unless can_create_pipeline_for?(merge_request)
-
- create_detached_merge_request_pipeline(merge_request, user)
- end
-
- def create_detached_merge_request_pipeline(merge_request, user)
- if can_use_merge_request_ref?(merge_request)
- Ci::CreatePipelineService.new(merge_request.source_project, user,
- ref: merge_request.ref_path)
- .execute(:merge_request_event, merge_request: merge_request)
- else
- Ci::CreatePipelineService.new(merge_request.source_project, user,
- ref: merge_request.source_branch)
- .execute(:merge_request_event, merge_request: merge_request)
- end
- end
-
- def can_create_pipeline_for?(merge_request)
- ##
- # UpdateMergeRequestsWorker could be retried by an exception.
- # pipelines for merge request should not be recreated in such case.
- return false if merge_request.find_actual_head_pipeline&.triggered_by_merge_request?
- return false if merge_request.has_no_commits?
-
- true
+ MergeRequests::CreatePipelineService.new(project, user).execute(merge_request)
end
def can_use_merge_request_ref?(merge_request)
@@ -92,6 +68,10 @@ module MergeRequests
!merge_request.for_fork?
end
+ def cancel_auto_merge(merge_request)
+ AutoMergeService.new(project, current_user).cancel(merge_request)
+ end
+
# Returns all origin and fork merge requests from `@project` satisfying passed arguments.
# rubocop: disable CodeReuse/ActiveRecord
def merge_requests_for(source_branch, mr_states: [:opened])
diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb
index e77051bb1c9..b81a4dd81d2 100644
--- a/app/services/merge_requests/close_service.rb
+++ b/app/services/merge_requests/close_service.rb
@@ -18,6 +18,7 @@ module MergeRequests
invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
cleanup_environments(merge_request)
+ cancel_auto_merge(merge_request)
end
merge_request
diff --git a/app/services/merge_requests/create_pipeline_service.rb b/app/services/merge_requests/create_pipeline_service.rb
new file mode 100644
index 00000000000..03246cc1920
--- /dev/null
+++ b/app/services/merge_requests/create_pipeline_service.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class CreatePipelineService < MergeRequests::BaseService
+ def execute(merge_request)
+ return unless can_create_pipeline_for?(merge_request)
+
+ create_detached_merge_request_pipeline(merge_request)
+ end
+
+ def create_detached_merge_request_pipeline(merge_request)
+ if can_use_merge_request_ref?(merge_request)
+ Ci::CreatePipelineService.new(merge_request.source_project, current_user,
+ ref: merge_request.ref_path)
+ .execute(:merge_request_event, merge_request: merge_request)
+ else
+ Ci::CreatePipelineService.new(merge_request.source_project, current_user,
+ ref: merge_request.source_branch)
+ .execute(:merge_request_event, merge_request: merge_request)
+ end
+ end
+
+ def can_create_pipeline_for?(merge_request)
+ ##
+ # UpdateMergeRequestsWorker could be retried by an exception.
+ # pipelines for merge request should not be recreated in such case.
+ return false if !allow_duplicate && merge_request.find_actual_head_pipeline&.triggered_by_merge_request?
+ return false if merge_request.has_no_commits?
+
+ true
+ end
+
+ def allow_duplicate
+ params[:allow_duplicate]
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb
deleted file mode 100644
index 973e5b64e88..00000000000
--- a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-module MergeRequests
- class MergeWhenPipelineSucceedsService < MergeRequests::BaseService
- # Marks the passed `merge_request` to be merged when the pipeline succeeds or
- # updates the params for the automatic merge
- def execute(merge_request)
- merge_request.merge_params.merge!(params)
-
- # The service is also called when the merge params are updated.
- already_approved = merge_request.merge_when_pipeline_succeeds?
-
- unless already_approved
- merge_request.merge_when_pipeline_succeeds = true
- merge_request.merge_user = @current_user
-
- SystemNoteService.merge_when_pipeline_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit)
- end
-
- merge_request.save
- end
-
- # Triggers the automatic merge of merge_request once the pipeline succeeds
- def trigger(pipeline)
- return unless pipeline.success?
-
- pipeline_merge_requests(pipeline) do |merge_request|
- next unless merge_request.merge_when_pipeline_succeeds?
- next unless merge_request.mergeable?
-
- merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params)
- end
- end
-
- # Cancels the automatic merge
- def cancel(merge_request)
- if merge_request.merge_when_pipeline_succeeds? && merge_request.open?
- merge_request.reset_merge_when_pipeline_succeeds
- SystemNoteService.cancel_merge_when_pipeline_succeeds(merge_request, @project, @current_user)
-
- success
- else
- error("Can't cancel the automatic merge", 406)
- end
- end
- end
-end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 3abea1ad1ae..4b199bd8fa8 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -24,7 +24,7 @@ module MergeRequests
reload_merge_requests
outdate_suggestions
refresh_pipelines_on_merge_requests
- reset_merge_when_pipeline_succeeds
+ cancel_auto_merges
mark_pending_todos_done
cache_merge_requests_closing_issues
@@ -142,8 +142,10 @@ module MergeRequests
end
end
- def reset_merge_when_pipeline_succeeds
- merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds)
+ def cancel_auto_merges
+ merge_requests_for_source_branch.each do |merge_request|
+ cancel_auto_merge(merge_request)
+ end
end
def mark_pending_todos_done
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 55546432ce4..0066cd0491f 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -43,6 +43,8 @@ module MergeRequests
create_branch_change_note(merge_request, 'target',
merge_request.previous_changes['target_branch'].first,
merge_request.target_branch)
+
+ cancel_auto_merge(merge_request)
end
if merge_request.assignees != old_assignees
@@ -89,7 +91,7 @@ module MergeRequests
merge_request.update(merge_error: nil)
if merge_request.head_pipeline && merge_request.head_pipeline.active?
- MergeRequests::MergeWhenPipelineSucceedsService.new(project, current_user).execute(merge_request)
+ AutoMergeService.new(project, current_user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else
merge_request.merge_async(current_user.id, {})
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index f797c0f11c6..5aa804666f0 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -238,7 +238,7 @@ class NotificationService
merge_request,
current_user,
:merged_merge_request_email,
- skip_current_user: !merge_request.merge_when_pipeline_succeeds?
+ skip_current_user: !merge_request.auto_merge_enabled?
)
end
diff --git a/app/services/pages_domains/create_acme_order_service.rb b/app/services/pages_domains/create_acme_order_service.rb
new file mode 100644
index 00000000000..c600f497fa5
--- /dev/null
+++ b/app/services/pages_domains/create_acme_order_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module PagesDomains
+ class CreateAcmeOrderService
+ attr_reader :pages_domain
+
+ def initialize(pages_domain)
+ @pages_domain = pages_domain
+ end
+
+ def execute
+ lets_encrypt_client = Gitlab::LetsEncrypt::Client.new
+ order = lets_encrypt_client.new_order(pages_domain.domain)
+
+ challenge = order.new_challenge
+
+ private_key = OpenSSL::PKey::RSA.new(4096)
+ saved_order = pages_domain.acme_orders.create!(
+ url: order.url,
+ expires_at: order.expires,
+ private_key: private_key.to_pem,
+
+ challenge_token: challenge.token,
+ challenge_file_content: challenge.file_content
+ )
+
+ challenge.request_validation
+ saved_order
+ end
+ end
+end
diff --git a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
new file mode 100644
index 00000000000..2dfe1a3d8ca
--- /dev/null
+++ b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module PagesDomains
+ class ObtainLetsEncryptCertificateService
+ attr_reader :pages_domain
+
+ def initialize(pages_domain)
+ @pages_domain = pages_domain
+ end
+
+ def execute
+ pages_domain.acme_orders.expired.delete_all
+ acme_order = pages_domain.acme_orders.first
+
+ unless acme_order
+ ::PagesDomains::CreateAcmeOrderService.new(pages_domain).execute
+ return
+ end
+
+ api_order = ::Gitlab::LetsEncrypt::Client.new.load_order(acme_order.url)
+
+ # https://tools.ietf.org/html/rfc8555#section-7.1.6 - statuses diagram
+ case api_order.status
+ when 'ready'
+ api_order.request_certificate(private_key: acme_order.private_key, domain: pages_domain.domain)
+ when 'valid'
+ save_certificate(acme_order.private_key, api_order)
+ acme_order.destroy!
+ # when 'invalid'
+ # TODO: implement error handling
+ end
+ end
+
+ private
+
+ def save_certificate(private_key, api_order)
+ certificate = api_order.certificate
+ pages_domain.update!(key: private_key, certificate: certificate)
+ end
+ end
+end
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index 7386530f45f..2b4c4ae68e2 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -38,7 +38,9 @@ class PreviewMarkdownService < BaseService
head_sha: params[:head_sha],
start_sha: params[:start_sha])
- Gitlab::Diff::SuggestionsParser.parse(text, position: position, project: project)
+ Gitlab::Diff::SuggestionsParser.parse(text, position: position,
+ project: project,
+ supports_suggestion: params[:preview_suggestions])
end
def preview_sugestions?
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index fc234bafc57..0b4ab7b8e4d 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -36,18 +36,22 @@ module Projects
def fork_new_project
new_params = {
- visibility_level: allowed_visibility_level,
- description: @project.description,
- name: target_name,
- path: target_path,
- shared_runners_enabled: @project.shared_runners_enabled,
- namespace_id: target_namespace.id,
- fork_network: fork_network,
+ visibility_level: allowed_visibility_level,
+ description: @project.description,
+ name: target_name,
+ path: target_path,
+ shared_runners_enabled: @project.shared_runners_enabled,
+ namespace_id: target_namespace.id,
+ fork_network: fork_network,
+ # We need to set default_git_depth to 0 for the forked project when
+ # @project.default_git_depth is nil in order to keep the same behaviour
+ # and not get ProjectCiCdSetting::DEFAULT_GIT_DEPTH set on create
+ ci_cd_settings_attributes: { default_git_depth: @project.default_git_depth || 0 },
# We need to assign the fork network membership after the project has
# been instantiated to avoid ActiveRecord trying to create it when
# initializing the project, as that would cause a foreign key constraint
# exception.
- relations_block: -> (project) { build_fork_network_member(project) }
+ relations_block: -> (project) { build_fork_network_member(project) }
}
if @project.avatar.present? && @project.avatar.image?
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index dfa7bd20254..2bc04470342 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -64,6 +64,7 @@ module Projects
if project.previous_changes.include?(:visibility_level) && project.private?
# don't enqueue immediately to prevent todos removal in case of a mistake
+ TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id)
TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id)
elsif (project_changed_feature_keys & todos_features_changes).present?
TodosDestroyer::PrivateFeaturesWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id)
diff --git a/app/services/projects/update_statistics_service.rb b/app/services/projects/update_statistics_service.rb
index f32a779fab0..28677a398f3 100644
--- a/app/services/projects/update_statistics_service.rb
+++ b/app/services/projects/update_statistics_service.rb
@@ -3,7 +3,7 @@
module Projects
class UpdateStatisticsService < BaseService
def execute
- return unless project && project.repository.exists?
+ return unless project
Rails.logger.info("Updating statistics for project #{project.id}")
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index e0cbfac2420..302510341ac 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -52,6 +52,10 @@ class SearchService
@search_objects ||= search_results.objects(scope, params[:page])
end
+ def display_options
+ @display_options ||= search_results.display_options(scope)
+ end
+
private
def search_service
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index a39ff76b798..1390f7cdf46 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -25,7 +25,7 @@ module SystemNoteService
text_parts = ["added #{commits_text}"]
text_parts << commits_list(noteable, new_commits, existing_commits, oldrev)
- text_parts << "[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
+ text_parts << "[Compare with previous version](#{diff_comparison_path(noteable, project, oldrev)})"
body = text_parts.join("\n\n")
@@ -41,7 +41,7 @@ module SystemNoteService
#
# Returns the created Note object
def tag_commit(noteable, project, author, tag_name)
- link = url_helpers.project_tag_url(project, id: tag_name)
+ link = url_helpers.project_tag_path(project, id: tag_name)
body = "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"
create_note(NoteSummary.new(noteable, project, author, body, action: 'tag'))
@@ -272,7 +272,7 @@ module SystemNoteService
text_parts = ["changed this line in"]
if version_params = merge_request.version_params_for(diff_refs)
line_code = change_position.line_code(project.repository)
- url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code))
+ url = url_helpers.diffs_project_merge_request_path(project, merge_request, version_params.merge(anchor: line_code))
text_parts << "[version #{version_index} of the diff](#{url})"
else
@@ -405,7 +405,7 @@ module SystemNoteService
#
# "created branch `201-issue-branch-button`"
def new_issue_branch(issue, project, author, branch)
- link = url_helpers.project_compare_url(project, from: project.default_branch, to: branch)
+ link = url_helpers.project_compare_path(project, from: project.default_branch, to: branch)
body = "created branch [`#{branch}`](#{link}) to address this issue"
@@ -668,10 +668,10 @@ module SystemNoteService
@url_helpers ||= Gitlab::Routing.url_helpers
end
- def diff_comparison_url(merge_request, project, oldrev)
+ def diff_comparison_path(merge_request, project, oldrev)
diff_id = merge_request.merge_request_diff.id
- url_helpers.diffs_project_merge_request_url(
+ url_helpers.diffs_project_merge_request_path(
project,
merge_request,
diff_id: diff_id,
diff --git a/app/services/todos/destroy/base_service.rb b/app/services/todos/destroy/base_service.rb
index f3f1dbb5698..7378f10e7c4 100644
--- a/app/services/todos/destroy/base_service.rb
+++ b/app/services/todos/destroy/base_service.rb
@@ -13,7 +13,7 @@ module Todos
# rubocop: disable CodeReuse/ActiveRecord
def without_authorized(items)
- items.where('user_id NOT IN (?)', authorized_users)
+ items.where('todos.user_id NOT IN (?)', authorized_users)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/todos/destroy/confidential_issue_service.rb b/app/services/todos/destroy/confidential_issue_service.rb
index 6276e332448..6cdd8c16894 100644
--- a/app/services/todos/destroy/confidential_issue_service.rb
+++ b/app/services/todos/destroy/confidential_issue_service.rb
@@ -2,36 +2,55 @@
module Todos
module Destroy
+ # Service class for deleting todos that belongs to confidential issues.
+ # It deletes todos for users that are not at least reporters, issue author or assignee.
+ #
+ # Accepts issue_id or project_id as argument.
+ # When issue_id is passed it deletes matching todos for one confidential issue.
+ # When project_id is passed it deletes matching todos for all confidential issues of the project.
class ConfidentialIssueService < ::Todos::Destroy::BaseService
extend ::Gitlab::Utils::Override
- attr_reader :issue
+ attr_reader :issues
# rubocop: disable CodeReuse/ActiveRecord
- def initialize(issue_id)
- @issue = Issue.find_by(id: issue_id)
+ def initialize(issue_id: nil, project_id: nil)
+ @issues =
+ if issue_id
+ Issue.where(id: issue_id)
+ elsif project_id
+ project_confidential_issues(project_id)
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
private
+ def project_confidential_issues(project_id)
+ project = Project.find(project_id)
+
+ project.issues.confidential_only
+ end
+
override :todos
# rubocop: disable CodeReuse/ActiveRecord
def todos
- Todo.where(target: issue)
- .where('user_id != ?', issue.author_id)
- .where('user_id NOT IN (?)', issue.assignees.select(:id))
+ Todo.joins_issue_and_assignees
+ .where(target: issues)
+ .where('issues.confidential = ?', true)
+ .where('todos.user_id != issues.author_id')
+ .where('todos.user_id != issue_assignees.user_id')
end
# rubocop: enable CodeReuse/ActiveRecord
override :todos_to_remove?
def todos_to_remove?
- issue&.confidential?
+ issues&.any?(&:confidential?)
end
override :project_ids
def project_ids
- issue.project_id
+ issues&.distinct&.select(:project_id)
end
override :authorized_users
diff --git a/app/uploaders/legacy_artifact_uploader.rb b/app/uploaders/legacy_artifact_uploader.rb
deleted file mode 100644
index fac3c3dcb8f..00000000000
--- a/app/uploaders/legacy_artifact_uploader.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-##
-# TODO: Remove this uploader when we remove :ci_enable_legacy_artifacts feature flag
-# See https://gitlab.com/gitlab-org/gitlab-ce/issues/58595
-class LegacyArtifactUploader < GitlabUploader
- extend Workhorse::UploadPath
- include ObjectStorage::Concern
-
- ObjectNotReadyError = Class.new(StandardError)
-
- storage_options Gitlab.config.artifacts
-
- alias_method :upload, :model
-
- def store_dir
- dynamic_segment
- end
-
- private
-
- def dynamic_segment
- raise ObjectNotReadyError, 'Build is not ready' unless model.id
-
- File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.id.to_s)
- end
-end
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index 92ae40512c5..c6781e91cfd 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -1,22 +1,24 @@
-- page_title _("Report abuse to GitLab")
+- page_title _("Report abuse to admin")
%h3.page-title
- = _("Report abuse to GitLab")
+ = _("Report abuse to admin")
%p
- = _("Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately.")
+ = _("Please use this form to report to the admin users who create spam issues, comments or behave inappropriately.")
%p
- = _("A member of GitLab's abuse team will review your report as soon as possible.")
+ = _("A member of the abuse team will review your report as soon as possible.")
%hr
= form_for @abuse_report, html: { class: 'js-quick-submit js-requires-input'} do |f|
= form_errors(@abuse_report)
= f.hidden_field :user_id
.form-group.row
- = f.label :user_id, class: 'col-sm-2 col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :user_id
.col-sm-10
- name = "#{@abuse_report.user.name} (@#{@abuse_report.user.username})"
= text_field_tag :user_name, name, class: "form-control", readonly: true
.form-group.row
- = f.label :message, class: 'col-sm-2 col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :message
.col-sm-10
= f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url)
.form-text.text-muted
diff --git a/app/views/admin/application_settings/_external_authorization_service_form.html.haml b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
index 01f6c7afe61..7587ecbf9d3 100644
--- a/app/views/admin/application_settings/_external_authorization_service_form.html.haml
+++ b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
@@ -5,7 +5,7 @@
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%p
- = _('External Classification Policy Authorization')
+ = _('External Classification Policy Authorization')
.settings-content
= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-external-auth-settings'), html: { class: 'fieldset-form' } do |f|
diff --git a/app/views/admin/application_settings/_outbound.html.haml b/app/views/admin/application_settings/_outbound.html.haml
index f4bfb5af385..dd56bb99a06 100644
--- a/app/views/admin/application_settings/_outbound.html.haml
+++ b/app/views/admin/application_settings/_outbound.html.haml
@@ -8,4 +8,12 @@
= f.label :allow_local_requests_from_hooks_and_services, class: 'form-check-label' do
Allow requests to the local network from hooks and services
+ .form-group
+ .form-check
+ = f.check_box :dns_rebinding_protection_enabled, class: 'form-check-input'
+ = f.label :dns_rebinding_protection_enabled, class: 'form-check-label' do
+ = _('Enforce DNS rebinding attack protection')
+ %span.form-text.text-muted
+ = _('Resolves IP addresses once and uses them to submit requests')
+
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_performance_bar.html.haml b/app/views/admin/application_settings/_performance_bar.html.haml
index f992d531ea5..1e66b635038 100644
--- a/app/views/admin/application_settings/_performance_bar.html.haml
+++ b/app/views/admin/application_settings/_performance_bar.html.haml
@@ -6,7 +6,7 @@
.form-check
= f.check_box :performance_bar_enabled, class: 'form-check-input'
= f.label :performance_bar_enabled, class: 'form-check-label qa-enable-performance-bar-checkbox' do
- Enable the Performance Bar
+ Enable access to the Performance Bar
.form-group
= f.label :performance_bar_allowed_group_path, 'Allowed group', class: 'label-bold'
= f.text_field :performance_bar_allowed_group_path, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
diff --git a/app/views/admin/application_settings/metrics_and_profiling.html.haml b/app/views/admin/application_settings/metrics_and_profiling.html.haml
index d5ba6abe7af..01d61beaf53 100644
--- a/app/views/admin/application_settings/metrics_and_profiling.html.haml
+++ b/app/views/admin/application_settings/metrics_and_profiling.html.haml
@@ -31,7 +31,7 @@
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
- = _('Enable the Performance Bar for a given group.')
+ = _('Enable access to the Performance Bar for a given group.')
= link_to icon('question-circle'), help_page_path('administration/monitoring/performance/performance_bar')
.settings-content
= render 'performance_bar'
diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml
index 12690343f6e..21e84016c66 100644
--- a/app/views/admin/applications/_form.html.haml
+++ b/app/views/admin/applications/_form.html.haml
@@ -2,13 +2,15 @@
= form_errors(application)
= content_tag :div, class: 'form-group row' do
- = f.label :name, class: 'col-sm-2 col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :name
.col-sm-10
= f.text_field :name, class: 'form-control'
= doorkeeper_errors_for application, :name
= content_tag :div, class: 'form-group row' do
- = f.label :redirect_uri, class: 'col-sm-2 col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :redirect_uri
.col-sm-10
= f.text_area :redirect_uri, class: 'form-control'
= doorkeeper_errors_for application, :redirect_uri
@@ -21,14 +23,16 @@
for local tests
= content_tag :div, class: 'form-group row' do
- = f.label :trusted, class: 'col-sm-2 col-form-label pt-0'
+ .col-sm-2.col-form-label.pt-0
+ = f.label :trusted
.col-sm-10
= f.check_box :trusted
%span.form-text.text-muted
Trusted applications are automatically authorized on GitLab OAuth flow.
.form-group.row
- = f.label :scopes, class: 'col-sm-2 col-form-label pt-0'
+ .col-sm-2.col-form-label.pt-0
+ = f.label :scopes
.col-sm-10
= render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index 46beca0465e..c8ee87c6212 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -10,7 +10,8 @@
= form_errors(@broadcast_message)
.form-group.row
- = f.label :message, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :message
.col-sm-10
= f.text_area :message, class: "form-control js-autosize",
required: true,
@@ -20,19 +21,23 @@
.col-sm-10.offset-sm-2
= link_to 'Customize colors', '#', class: 'js-toggle-colors-link'
.form-group.row.js-toggle-colors-container.toggle-colors.hide
- = f.label :color, "Background Color", class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :color, "Background Color"
.col-sm-10
= f.color_field :color, class: "form-control"
.form-group.row.js-toggle-colors-container.toggle-colors.hide
- = f.label :font, "Font Color", class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :font, "Font Color"
.col-sm-10
= f.color_field :font, class: "form-control"
.form-group.row
- = f.label :starts_at, _("Starts at (UTC)"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :starts_at, _("Starts at (UTC)")
.col-sm-10.datetime-controls
= f.datetime_select :starts_at, {}, class: 'form-control form-control-inline'
.form-group.row
- = f.label :ends_at, _("Ends at (UTC)"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :ends_at, _("Ends at (UTC)")
.col-sm-10.datetime-controls
= f.datetime_select :ends_at, {}, class: 'form-control form-control-inline'
.form-actions
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 62d1d01cc83..dd01ef8a29f 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -6,7 +6,8 @@
= render_if_exists 'admin/namespace_plan', f: f
.form-group.row.group-description-holder
- = f.label :avatar, _("Group avatar"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :avatar, _("Group avatar")
.col-sm-10
= render 'shared/choose_avatar_button', f: f
diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml
index 3ab7990d9e2..40a7014e143 100644
--- a/app/views/admin/identities/_form.html.haml
+++ b/app/views/admin/identities/_form.html.haml
@@ -2,12 +2,14 @@
= form_errors(@identity)
.form-group.row
- = f.label :provider, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :provider
.col-sm-10
- values = Gitlab::Auth::OAuth::Provider.providers.map { |name| ["#{Gitlab::Auth::OAuth::Provider.label_for(name)} (#{name})", name] }
= f.select :provider, values, { allow_blank: false }, class: 'form-control'
.form-group.row
- = f.label :extern_uid, _("Identifier"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :extern_uid, _("Identifier")
.col-sm-10
= f.text_field :extern_uid, class: 'form-control', required: true
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index 5e7b4817461..299d0a12e6c 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -2,15 +2,18 @@
= form_errors(@label)
.form-group.row
- = f.label :title, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :title
.col-sm-10
= f.text_field :title, class: "form-control", required: true
.form-group.row
- = f.label :description, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :description
.col-sm-10
= f.text_field :description, class: "form-control js-quick-submit"
.form-group.row
- = f.label :color, _("Background color"), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :color, _("Background color")
.col-sm-10
.input-group
.input-group-prepend
@@ -21,10 +24,7 @@
%br
= _("Or you can choose one of the suggested colors below")
- .suggest-colors
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp;
+ = render_suggested_colors
.form-actions
= f.submit _('Save'), class: 'btn btn-success js-save-button'
diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml
index 9117f63f939..2f7ad35eb3e 100644
--- a/app/views/admin/projects/_projects.html.haml
+++ b/app/views/admin/projects/_projects.html.haml
@@ -7,7 +7,7 @@
= link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
%button.delete-project-button.btn.btn-danger{ data: { toggle: 'modal',
target: '#delete-project-modal',
- delete_project_url: project_path(project),
+ delete_project_url: admin_project_path(project),
project_name: project.name }, type: 'button' }
= s_('AdminProjects|Delete')
@@ -17,7 +17,7 @@
- if project.archived
%span.badge.badge-warning archived
.title
- = link_to(admin_namespace_project_path(project.namespace, project)) do
+ = link_to(admin_project_path(project)) do
.dash-project-avatar
.avatar-container.rect-avatar.s40
= project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40)
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 1e1ad9d5e19..e23accc1ea9 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -117,7 +117,8 @@
.card-body
= form_for @project, url: transfer_admin_project_path(@project), method: :put do |f|
.form-group.row
- = f.label :new_namespace_id, "Namespace", class: 'col-form-label col-sm-3'
+ .col-sm-3.col-form-label
+ = f.label :new_namespace_id, "Namespace"
.col-sm-9
.dropdown
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index 98b6bc7bc46..77729636f9d 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -1,18 +1,20 @@
%fieldset
%legend Access
.form-group.row
- .col-sm-2.text-right
- = f.label :projects_limit, class: 'col-form-label'
- .col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :projects_limit
+ .col-sm-10
+ = f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :can_create_group, class: 'col-form-label'
- .col-sm-10= f.check_box :can_create_group
+ .col-sm-2.col-form-label
+ = f.label :can_create_group
+ .col-sm-10
+ = f.check_box :can_create_group
.form-group.row
- .col-sm-2.text-right
- = f.label :access_level, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :access_level
.col-sm-10
- editing_current_user = (current_user == @user)
@@ -34,8 +36,8 @@
You cannot remove your own admin rights.
.form-group.row
- .col-sm-2.text-right
- = f.label :external, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :external
.hidden{ data: user_internal_regex_data }
.col-sm-10
= f.check_box :external do
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 0656feb79cb..3281718071c 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -5,20 +5,20 @@
%fieldset
%legend Account
.form-group.row
- .col-sm-2.text-right
- = f.label :name, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :name
.col-sm-10
= f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
.form-group.row
- .col-sm-2.text-right
- = f.label :username, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :username
.col-sm-10
= f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required
.form-group.row
- .col-sm-2.text-right
- = f.label :email, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :email
.col-sm-10
= f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
@@ -27,8 +27,8 @@
%fieldset
%legend Password
.form-group.row
- .col-sm-2.text-right
- = f.label :password, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :password
.col-sm-10
%strong
Reset link will be generated and sent to the user.
@@ -38,13 +38,15 @@
%fieldset
%legend Password
.form-group.row
- .col-sm-2.text-right
- = f.label :password, class: 'col-form-label'
- .col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :password
+ .col-sm-10
+ = f.password_field :password, disabled: f.object.force_random_password, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :password_confirmation, class: 'col-form-label'
- .col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :password_confirmation
+ .col-sm-10
+ = f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control'
= render partial: 'access_levels', locals: { f: f }
@@ -55,27 +57,31 @@
%fieldset
%legend Profile
.form-group.row
- .col-sm-2.text-right
- = f.label :avatar, class: 'col-form-label'
+ .col-sm-2.col-form-label
+ = f.label :avatar
.col-sm-10
= f.file_field :avatar
.form-group.row
- .col-sm-2.text-right
- = f.label :skype, class: 'col-form-label'
- .col-sm-10= f.text_field :skype, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :skype
+ .col-sm-10
+ = f.text_field :skype, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :linkedin, class: 'col-form-label'
- .col-sm-10= f.text_field :linkedin, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :linkedin
+ .col-sm-10
+ = f.text_field :linkedin, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :twitter, class: 'col-form-label'
- .col-sm-10= f.text_field :twitter, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :twitter
+ .col-sm-10
+ = f.text_field :twitter, class: 'form-control'
.form-group.row
- .col-sm-2.text-right
- = f.label :website_url, 'Website', class: 'col-form-label'
- .col-sm-10= f.text_field :website_url, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :website_url
+ .col-sm-10
+ = f.text_field :website_url, class: 'form-control'
= render_if_exists 'admin/users/admin_notes', f: f
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index c4178296e67..dcd6f7c8078 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -124,6 +124,8 @@
%strong
= Gitlab::Access.human_access_with_none(@user.highest_role)
+ = render_if_exists 'admin/users/using_license_seat', user: @user
+
- if @user.ldap_user?
%li
%span.light LDAP uid:
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 8d9c083d223..60ca7e4e267 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -13,7 +13,7 @@
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': _('Add reaction'),
data: { title: _('Add reaction') } }
- %span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
- %span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley')
- %span{ class: "award-control-icon award-control-icon-super-positive" }= custom_icon('emoji_smile')
+ %span{ class: "award-control-icon award-control-icon-neutral" }= sprite_icon('slight-smile')
+ %span{ class: "award-control-icon award-control-icon-positive" }= sprite_icon('smiley')
+ %span{ class: "award-control-icon award-control-icon-super-positive" }= sprite_icon('smile')
= icon('spinner spin', class: "award-control-icon award-control-icon-loading")
diff --git a/app/views/ci/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml
index d07cbe4589c..0b5c1a806b2 100644
--- a/app/views/ci/variables/_content.html.haml
+++ b/app/views/ci/variables/_content.html.haml
@@ -1,3 +1,3 @@
-= _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want.')
+= _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want.')
= _('You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>.').html_safe
= link_to _('More information'), help_page_path('ci/variables/README', anchor: 'variables')
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index 464b9faf282..94102b4dcd0 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -6,7 +6,7 @@
= s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.row
- .col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint } }
+ .col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint, maskable_regex: ci_variable_maskable_regex } }
.hide.alert.alert-danger.js-ci-variable-error-box
%ul.ci-variable-list
diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml
index 89bd7b31352..ed4bd5ae19e 100644
--- a/app/views/ci/variables/_variable_row.html.haml
+++ b/app/views/ci/variables/_variable_row.html.haml
@@ -8,7 +8,7 @@
- value = variable&.value
- is_protected_default = ci_variable_protected_by_default?
- is_protected = ci_variable_protected?(variable, only_key_value)
-- is_masked_default = true
+- is_masked_default = false
- is_masked = ci_variable_masked?(variable, only_key_value)
- id_input_name = "#{form_field}[variables_attributes][][id]"
@@ -59,7 +59,7 @@
.append-right-default
= s_("CiVariable|Masked")
%button{ type: 'button',
- class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if is_masked}",
+ class: "js-project-feature-toggle project-feature-toggle qa-variable-masked #{'is-checked' if is_masked}",
"aria-label": s_("CiVariable|Toggle masked") }
%input{ type: "hidden",
class: 'js-ci-variable-input-masked js-project-feature-toggle-input',
diff --git a/app/views/clusters/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml
index 160c5f009a7..a5de67be96b 100644
--- a/app/views/clusters/clusters/_banner.html.haml
+++ b/app/views/clusters/clusters/_banner.html.haml
@@ -5,5 +5,17 @@
.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
+.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
+ .col-11
+ = s_('ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct.')
+ .col-1.p-0
+ %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
+
+.hidden.js-cluster-authentication-failure.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
+ .col-11
+ = s_('ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid.')
+ .col-1.p-0
+ %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
+
.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index deb6b21e2be..4dfbb310142 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -24,7 +24,8 @@
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'),
ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'),
- manage_prometheus_path: manage_prometheus_path } }
+ manage_prometheus_path: manage_prometheus_path,
+ cluster_id: @cluster.id } }
.js-cluster-application-notice
.flash-container
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 214630d245a..8212fb8bb33 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -34,7 +34,7 @@
= icon('spinner spin')
.todos-filters
- .row-content-block.second-block
+ .issues-details-filters.row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form d-sm-flex' do
.filter-categories.flex-fill
.filter-item.inline
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index a7434059de4..5eba819172b 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -1,27 +1,29 @@
+- max_name_length = 128
+- max_username_length = 255
#register-pane.tab-pane.login-box{ role: 'tabpanel' }
.login-body
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors
= render "devise/shared/error_messages", resource: resource
.name.form-group
- = f.label :name, 'Full name', class: 'label-bold'
- = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji", required: true, title: _("This field is required.")
+ = f.label :name, _('Full name'), class: 'label-bold'
+ = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length } }, required: true, title: _("This field is required.")
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
- %p.validation-error.hide Username is already taken.
- %p.validation-success.hide Username is available.
- %p.validation-pending.hide Checking username availability...
+ = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji js-validate-length", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length } }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
+ %p.validation-error.field-validation.hide= _('Username is already taken.')
+ %p.validation-success.field-validation.hide= _('Username is available.')
+ %p.validation-pending.field-validation.hide= _('Checking username availability...')
.form-group
= f.label :email, class: 'label-bold'
- = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: "Please provide a valid email address."
+ = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.")
.form-group
= f.label :email_confirmation, class: 'label-bold'
- = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: "Please retype the email address."
+ = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: _("Please retype the email address.")
.form-group.append-bottom-20#password-strength
= f.label :password, class: 'label-bold'
- = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters."
- %p.gl-field-hint.text-secondary Minimum length is #{@minimum_password_length} characters
+ = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length }
+ %p.gl-field-hint.text-secondary= _('Minimum length is %{minimum_password_length} characters') % { minimum_password_length: @minimum_password_length }
- if Gitlab::CurrentSettings.current_application_settings.enforce_terms?
.form-group
= check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms'
@@ -34,4 +36,4 @@
- if Gitlab::Recaptcha.enabled?
= recaptcha_tags
.submit-container
- = f.submit "Register", class: "btn-register btn qa-new-user-register-button"
+ = f.submit _("Register"), class: "btn-register btn qa-new-user-register-button"
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
index 30b00ca86b3..0a5541c3e82 100644
--- a/app/views/discussions/_notes.html.haml
+++ b/app/views/discussions/_notes.html.haml
@@ -19,20 +19,24 @@
.discussion-reply-holder
- if can_create_note?
+ %a.user-avatar-link.d-none.d-sm-block{ href: user_path(current_user) }
+ = image_tag avatar_icon_for_user(current_user), alt: current_user.to_reference, class: 'avatar s40'
- if discussion.potentially_resolvable?
- line_type = local_assigns.fetch(:line_type, nil)
- .btn-group.discussion-with-resolve-btn{ role: "group" }
- .btn-group{ role: "group" }
- = link_to_reply_discussion(discussion, line_type)
+ .discussion-with-resolve-btn
+ .btn-group.discussion-with-resolve-btn{ role: "group" }
+ .btn-group{ role: "group" }
+ = link_to_reply_discussion(discussion, line_type)
- = render "discussions/resolve_all", discussion: discussion
+ = render "discussions/resolve_all", discussion: discussion
- .btn-group.discussion-actions
- = render "discussions/new_issue_for_discussion", discussion: discussion, merge_request: discussion.noteable
- = render "discussions/jump_to_next", discussion: discussion
+ .btn-group.discussion-actions
+ = render "discussions/new_issue_for_discussion", discussion: discussion, merge_request: discussion.noteable
+ = render "discussions/jump_to_next", discussion: discussion
- else
- = link_to_reply_discussion(discussion)
+ .discussion-with-resolve-btn
+ = link_to_reply_discussion(discussion)
- elsif !current_user
.disabled-comment.text-center
Please
diff --git a/app/views/groups/_create_chat_team.html.haml b/app/views/groups/_create_chat_team.html.haml
index f950968030f..561e68a9155 100644
--- a/app/views/groups/_create_chat_team.html.haml
+++ b/app/views/groups/_create_chat_team.html.haml
@@ -1,8 +1,9 @@
.form-group
- = f.label :create_chat_team, class: 'col-form-label' do
- %span.mattermost-icon
- = custom_icon('icon_mattermost')
- Mattermost
+ .col-sm-2.col-form-label
+ = f.label :create_chat_team do
+ %span.mattermost-icon
+ = custom_icon('icon_mattermost')
+ Mattermost
.col-sm-10
.form-check.js-toggle-container
.js-toggle-button.form-check-input= f.check_box(:create_chat_team, { checked: true }, true, false)
diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml
index 7390c42aba2..b8f632d11d3 100644
--- a/app/views/groups/_group_admin_settings.html.haml
+++ b/app/views/groups/_group_admin_settings.html.haml
@@ -1,5 +1,6 @@
.form-group.row
- = f.label :lfs_enabled, 'Large File Storage', class: 'col-form-label col-sm-2 pt-0'
+ .col-sm-2.col-form-label.pt-0
+ = f.label :lfs_enabled, 'Large File Storage'
.col-sm-10
.form-check
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input'
@@ -10,12 +11,14 @@
%br/
%span.descr This setting can be overridden in each project.
.form-group.row
- = f.label s_('ProjectCreationLevel|Allowed to create projects'), class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label s_('ProjectCreationLevel|Allowed to create projects')
.col-sm-10
= f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, @group.project_creation_level), {}, class: 'form-control'
.form-group.row
- = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2 pt-0'
+ .col-sm-2.col-form-label.pt-0
+ = f.label :require_two_factor_authentication, 'Two-factor authentication'
.col-sm-10
.form-check
= f.check_box :require_two_factor_authentication, class: 'form-check-input'
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 77fe88dacb7..255a9ad038c 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -9,7 +9,7 @@
= render 'groups/home_panel'
.groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
- .top-area.group-nav-container
+ .top-area.group-nav-container.justify-content-between
.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
diff --git a/app/views/import/bitbucket_server/status.html.haml b/app/views/import/bitbucket_server/status.html.haml
index 9280f12e187..40609fddbde 100644
--- a/app/views/import/bitbucket_server/status.html.haml
+++ b/app/views/import/bitbucket_server/status.html.haml
@@ -29,7 +29,7 @@
%tr
%th= _('From Bitbucket Server')
%th= _('To GitLab')
- %th= _(' Status')
+ %th= _('Status')
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml
index 5e4595d930b..a19c8911559 100644
--- a/app/views/import/gitlab_projects/new.html.haml
+++ b/app/views/import/gitlab_projects/new.html.haml
@@ -7,28 +7,7 @@
%hr
= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
- .row
- .form-group.project-name.col-sm-12
- = label_tag :name, _('Project name'), class: 'label-bold'
- = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true
- .form-group.col-12.col-sm-6
- = label_tag :namespace_id, _('Project URL'), class: 'label-bold'
- .form-group
- .input-group
- - if current_user.can_select_namespace?
- .input-group-prepend.has-tooltip{ title: root_url }
- .input-group-text
- = root_url
- = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1
-
- - else
- .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
- .input-group-text.border-0
- #{user_url(current_user.username)}/
- = hidden_field_tag :namespace_id, value: current_user.namespace_id
- .form-group.col-12.col-sm-6.project-path
- = label_tag :path, _('Project slug'), class: 'label-bold'
- = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true
+ = render 'import/shared/new_project_form'
.row
.form-group.col-md-12
diff --git a/app/views/import/manifest/new.html.haml b/app/views/import/manifest/new.html.haml
index 056e4922b9e..df00c4d2179 100644
--- a/app/views/import/manifest/new.html.haml
+++ b/app/views/import/manifest/new.html.haml
@@ -4,9 +4,5 @@
%h3.page-title
= _('Manifest file import')
-- if @errors.present?
- .alert.alert-danger
- - @errors.each do |error|
- = error
-
+= render 'import/shared/errors'
= render 'form'
diff --git a/app/views/import/phabricator/new.html.haml b/app/views/import/phabricator/new.html.haml
new file mode 100644
index 00000000000..811e126579e
--- /dev/null
+++ b/app/views/import/phabricator/new.html.haml
@@ -0,0 +1,25 @@
+- title = _('Phabricator Server Import')
+- page_title title
+- breadcrumb_title title
+- header_title _("Projects"), root_path
+
+%h3.page-title
+ = icon 'issues', text: _('Import tasks from Phabricator into issues')
+
+= render 'import/shared/errors'
+
+= form_tag import_phabricator_path, class: 'new_project', method: :post do
+ = render 'import/shared/new_project_form'
+
+ %h4.prepend-top-0= _('Enter in your Phabricator Server URL and personal access token below')
+
+ .form-group.row
+ = label_tag :phabricator_server_url, _('Phabricator Server URL'), class: 'col-form-label col-md-2'
+ .col-md-4
+ = text_field_tag :phabricator_server_url, params[:phabricator_server_url], class: 'form-control append-right-8', placeholder: 'https://your-phabricator-server', size: 40
+ .form-group.row
+ = label_tag :api_token, _('API Token'), class: 'col-form-label col-md-2'
+ .col-md-4
+ = password_field_tag :api_token, params[:api_token], class: 'form-control append-right-8', placeholder: _('Personal Access Token'), size: 40
+ .form-actions
+ = submit_tag _('Import tasks'), class: 'btn btn-success'
diff --git a/app/views/import/shared/_errors.html.haml b/app/views/import/shared/_errors.html.haml
new file mode 100644
index 00000000000..de60c15351f
--- /dev/null
+++ b/app/views/import/shared/_errors.html.haml
@@ -0,0 +1,4 @@
+- if @errors.present?
+ .alert.alert-danger
+ - @errors.each do |error|
+ = error
diff --git a/app/views/import/shared/_new_project_form.html.haml b/app/views/import/shared/_new_project_form.html.haml
new file mode 100644
index 00000000000..4d13d4f2869
--- /dev/null
+++ b/app/views/import/shared/_new_project_form.html.haml
@@ -0,0 +1,21 @@
+.row
+ .form-group.project-name.col-sm-12
+ = label_tag :name, _('Project name'), class: 'label-bold'
+ = text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true
+ .form-group.col-12.col-sm-6
+ = label_tag :namespace_id, _('Project URL'), class: 'label-bold'
+ .form-group
+ .input-group.flex-nowrap
+ - if current_user.can_select_namespace?
+ .input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
+ .input-group-text
+ = root_url
+ = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1
+ - else
+ .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
+ .input-group-text.border-0
+ #{user_url(current_user.username)}/
+ = hidden_field_tag :namespace_id, value: current_user.namespace_id
+ .form-group.col-12.col-sm-6.project-path
+ = label_tag :path, _('Project slug'), class: 'label-bold'
+ = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index a6023a1cbb9..496ec3c78b0 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -16,7 +16,7 @@
mr_path: merge_requests_dashboard_path },
aria: { label: _('Search or jump to…') }
%button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } }
- .dropdown-menu.dropdown-select
+ .dropdown-menu.dropdown-select.js-dashboard-search-options
= dropdown_content do
%ul
%li.dropdown-menu-empty-item
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 043cca6ad38..c38f96f302a 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -10,4 +10,6 @@
= render 'layouts/page', sidebar: sidebar, nav: nav
= footer_message
+ = render_if_exists "shared/onboarding_guide"
+
= yield :scripts_body
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index f7a561afbb3..ff3410f6268 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -10,15 +10,17 @@
.container.navless-container
.content
= render "layouts/flash"
- .row.append-bottom-15
- .col-sm-7.brand-holder
- %h1
+ .row.mt-3
+ .col-sm-12
+ %h1.mb-3.font-weight-normal
= brand_title
+ .row.mb-3
+ .col-sm-7.order-12.order-sm-1.brand-holder
= brand_image
- if current_appearance&.description?
= brand_text
- else
- %h3
+ %h3.mt-sm-0
= _('Open source software to collaborate on code')
%p
@@ -29,7 +31,7 @@
= render_if_exists 'layouts/devise_help_text'
- .col-sm-5.new-session-forms-container
+ .col-sm-5.order-1.order-sm-12.new-session-forms-container
= yield
%hr.footer-fixed
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 47710b9e9e5..54028dc8554 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -19,17 +19,17 @@
- if dashboard_nav_link?(:activity)
= nav_link(path: 'dashboard#activity', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: _('Activity') do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity' do
= _('Activity')
- if dashboard_nav_link?(:milestones)
= nav_link(controller: 'dashboard/milestones', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: _('Milestones') do
+ = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones' do
= _('Milestones')
- if dashboard_nav_link?(:snippets)
= nav_link(controller: 'dashboard/snippets', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets qa-snippets-link', title: _('Snippets') do
+ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets qa-snippets-link' do
= _('Snippets')
- if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets])
@@ -41,47 +41,47 @@
%ul
- if dashboard_nav_link?(:activity)
= nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, title: _('Activity') do
+ = link_to activity_dashboard_path do
= _('Activity')
- if dashboard_nav_link?(:milestones)
= nav_link(controller: 'dashboard/milestones') do
- = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: _('Milestones') do
+ = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones' do
= _('Milestones')
- if dashboard_nav_link?(:snippets)
= nav_link(controller: 'dashboard/snippets') do
- = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: _('Snippets') do
+ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets' do
= _('Snippets')
-
- = render_if_exists 'dashboard/operations/nav_link'
+ %li.dropdown.d-lg-none
+ = render_if_exists 'dashboard/operations/nav_link_list'
- if can?(current_user, :read_instance_statistics)
- = nav_link(controller: [:conversational_development_index, :cohorts]) do
- = link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = nav_link(controller: [:conversational_development_index, :cohorts], html_options: { class: 'd-lg-none' }) do
+ = link_to instance_statistics_root_path do
= _('Instance Statistics')
- if current_user.admin?
= nav_link(controller: 'admin/dashboard') do
- = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to admin_root_path, class: 'd-lg-none admin-icon qa-admin-area-link' do
= _('Admin Area')
- if Gitlab::Sherlock.enabled?
%li
- = link_to sherlock_transactions_path, class: 'admin-icon', title: _('Sherlock Transactions'),
- data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to sherlock_transactions_path, class: 'd-lg-none admin-icon' do
= _('Sherlock Transactions')
-# Shortcut to Dashboard > Projects
- if dashboard_nav_link?(:projects)
%li.hidden
- = link_to dashboard_projects_path, title: _('Projects'), class: 'dashboard-shortcuts-projects' do
+ = link_to dashboard_projects_path, class: 'dashboard-shortcuts-projects' do
= _('Projects')
- if current_controller?('ide')
%li.line-separator.d-none.d-sm-block
= nav_link(controller: 'ide') do
- = link_to '#', class: 'dashboard-shortcuts-web-ide', title: _('Web IDE') do
+ = link_to '#', class: 'dashboard-shortcuts-web-ide' do
= _('Web IDE')
- = render_if_exists 'dashboard/operations/nav_link'
+ %li.dropdown{ class: 'd-none d-lg-block' }
+ = render_if_exists 'dashboard/operations/nav_link'
- if can?(current_user, :read_instance_statistics)
= nav_link(controller: [:conversational_development_index, :cohorts], html_options: { class: "d-none d-lg-block d-xl-block"}) do
= link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 399305baec1..9b6551552c7 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -36,6 +36,8 @@
= render_if_exists 'projects/sidebar/security_dashboard'
+ = render_if_exists 'projects/sidebar/dependencies'
+
- if can?(current_user, :read_cycle_analytics, @project)
= nav_link(path: 'cycle_analytics#show') do
= link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml
index f21cf1ad34b..d3733ab3a09 100644
--- a/app/views/notify/closed_issue_email.html.haml
+++ b/app/views/notify/closed_issue_email.html.haml
@@ -1,2 +1,2 @@
%p
- Issue was closed by #{sanitize_name(@updated_by.name)} #{closure_reason_text(@closed_via, format: formats.first)}.
+ = _("Issue was closed by %{name} %{reason}").html_safe % { name: sanitize_name(@updated_by.name), reason: closure_reason_text(@closed_via, format: formats.first) }
diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
index 5567adc9165..ff2548a4b42 100644
--- a/app/views/notify/closed_issue_email.text.haml
+++ b/app/views/notify/closed_issue_email.text.haml
@@ -1,3 +1,3 @@
-Issue was closed by #{sanitize_name(@updated_by.name)} #{closure_reason_text(@closed_via, format: formats.first)}.
+= _("Issue was closed by %{name} %{reason}").html_safe % { name: sanitize_name(@updated_by.name), reason: closure_reason_text(@closed_via, format: formats.first) }
Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)}
diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml
index dfbb5c75bd3..ec135ae994f 100644
--- a/app/views/notify/new_user_email.html.haml
+++ b/app/views/notify/new_user_email.html.haml
@@ -13,4 +13,5 @@
%p
= link_to "Click here to set your password", edit_password_url(@user, reset_password_token: @token)
%p
- = raw reset_token_expire_message
+ This link is valid for #{password_reset_token_valid_time}.
+ After it expires, you can #{link_to("request a new one", new_user_password_url(user_email: @user.email))}.
diff --git a/app/views/notify/new_user_email.text.erb b/app/views/notify/new_user_email.text.erb
index f3f20f3bfba..7e0db75472d 100644
--- a/app/views/notify/new_user_email.text.erb
+++ b/app/views/notify/new_user_email.text.erb
@@ -1,10 +1,17 @@
Hi <%= sanitize_name(@user.name) %>!
+<% if Gitlab::CurrentSettings.allow_signup? %>
+Your account has been created successfully.
+<% else %>
The Administrator created an account for you. Now you are a member of the company GitLab application.
+<% end %>
login.................. <%= @user.email %>
+
<% if @user.created_by_id %>
- <%= link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token) %>
+Click here to set your password:
+<%= edit_password_url(@user, :reset_password_token => @token) %>
- <%= reset_token_expire_message %>
+This link is valid for <%= password_reset_token_valid_time %>. After it expires, you can request a new one here:
+<%= new_user_password_url(user_email: @user.email) %>
<% end %>
diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml
index 9f525547dd9..977ff30d5a6 100644
--- a/app/views/profiles/_event_table.html.haml
+++ b/app/views/profiles/_event_table.html.haml
@@ -1,14 +1,12 @@
%h5.prepend-top-0
- History of authentications
+ = _('History of authentications')
%ul.content-list
- events.each do |event|
%li
%span.description
= audit_icon(event.details[:with], class: "append-right-5")
- Signed in with
- = event.details[:with]
- authentication
+ = _('Signed in with %{authentication} authentication') % { authentication: event.details[:with]}
%span.float-right= time_ago_with_tooltip(event.created_at)
= paginate events, theme: "gitlab"
diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml
index 2bf514d72a5..bb31049111c 100644
--- a/app/views/profiles/active_sessions/_active_session.html.haml
+++ b/app/views/profiles/active_sessions/_active_session.html.haml
@@ -8,18 +8,19 @@
%div
%strong= active_session.ip_address
- if is_current_session
- %div This is your current session
+ %div
+ = _('This is your current session')
- else
%div
- Last accessed on
+ = _('Last accessed on')
= l(active_session.updated_at, format: :short)
%div
%strong= active_session.browser
- on
+ = s_('ProfileSession|on')
%strong= active_session.os
%div
- %strong Signed in
- on
+ %strong= _('Signed in')
+ = s_('ProfileSession|on')
= l(active_session.created_at, format: :short)
diff --git a/app/views/profiles/active_sessions/index.html.haml b/app/views/profiles/active_sessions/index.html.haml
index 8688a52843d..d651319fc3f 100644
--- a/app/views/profiles/active_sessions/index.html.haml
+++ b/app/views/profiles/active_sessions/index.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Active Sessions'
+- page_title _('Active Sessions')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,7 +6,7 @@
%h4.prepend-top-0
= page_title
%p
- This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.
+ = _('This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.')
.col-lg-8
.append-bottom-default
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index a924369050b..275c0428d34 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,4 +1,4 @@
-- page_title "Authentication log"
+- page_title _('Authentication log')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,6 +6,6 @@
%h4.prepend-top-0
= page_title
%p
- This is a security log of important events involving your account.
+ = _('This is a security log of important events involving your account.')
.col-lg-8
= render 'event_table', events: @events
diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml
index 9e82e47c1e1..ff67f92ad07 100644
--- a/app/views/profiles/chat_names/_chat_name.html.haml
+++ b/app/views/profiles/chat_names/_chat_name.html.haml
@@ -21,7 +21,7 @@
- if chat_name.last_used_at
= time_ago_with_tooltip(chat_name.last_used_at)
- else
- Never
+ = _('Never')
%td
- = link_to 'Remove', profile_chat_name_path(chat_name), method: :delete, class: 'btn btn-danger float-right', data: { confirm: 'Are you sure you want to revoke this nickname?' }
+ = link_to _('Remove'), profile_chat_name_path(chat_name), method: :delete, class: 'btn btn-danger float-right', data: { confirm: _('Are you sure you want to revoke this nickname?') }
diff --git a/app/views/profiles/chat_names/index.html.haml b/app/views/profiles/chat_names/index.html.haml
index 4b6e419af50..0c8098a97d5 100644
--- a/app/views/profiles/chat_names/index.html.haml
+++ b/app/views/profiles/chat_names/index.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Chat'
+- page_title _('Chat')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,7 +6,7 @@
%h4.prepend-top-0
= page_title
%p
- You can see your Chat accounts.
+ = _('You can see your chat accounts.')
.col-lg-8
%h5 Active chat names (#{@chat_names.size})
@@ -16,15 +16,15 @@
%table.table.chat-names
%thead
%tr
- %th Project
- %th Service
- %th Team domain
- %th Nickname
- %th Last used
+ %th= _('Project')
+ %th= _('Service')
+ %th= _('Team domain')
+ %th= _('Nickname')
+ %th= _('Last used')
%th
%tbody
= render @chat_names
- else
.settings-message.text-center
- You don't have any active chat names.
+ = _("You don't have any active chat names.")
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 1823f191fb3..3c20518c038 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "Emails"
+- page_title _('Emails')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,56 +6,58 @@
%h4.prepend-top-0
= page_title
%p
- Control emails linked to your account
+ = _('Control emails linked to your account')
.col-lg-8
%h4.prepend-top-0
- Add email address
+ = _('Add email address')
= form_for 'email', url: profile_emails_path do |f|
.form-group
- = f.label :email, class: 'label-bold'
+ = f.label :email, _('Email'), class: 'label-bold'
= f.text_field :email, class: 'form-control'
.prepend-top-default
- = f.submit 'Add email address', class: 'btn btn-success'
+ = f.submit _('Add email address'), class: 'btn btn-success'
%hr
%h4.prepend-top-0
- Linked emails (#{@emails.count + 1})
+ = _('Linked emails (%{email_count})') % { email_count: @emails.count + 1 }
.account-well.append-bottom-default
%ul
%li
- Your Primary Email will be used for avatar detection.
+ = _('Your Primary Email will be used for avatar detection.')
%li
- Your Commit Email will be used for web based operations, such as edits and merges.
+ = _('Your Commit Email will be used for web based operations, such as edits and merges.')
%li
- Your Notification Email will be used for account notifications.
+ - address = profile_notifications_path
+ - notification_message = _('Your Default Notification Email will be used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set.') % { openingTag: "<a href='#{address}'>".html_safe, closingTag: '</a>'.html_safe}
+ = notification_message.html_safe
%li
- Your Public Email will be displayed on your public profile.
+ = _('Your Public Email will be displayed on your public profile.')
%li
- All email addresses will be used to identify your commits.
+ = _('All email addresses will be used to identify your commits.')
%ul.content-list
%li
= render partial: 'shared/email_with_badge', locals: { email: @primary_email, verified: current_user.confirmed? }
%span.float-right
- %span.badge.badge-success Primary email
+ %span.badge.badge-success= s_('Profiles|Primary email')
- if @primary_email === current_user.commit_email
- %span.badge.badge-info Commit email
+ %span.badge.badge-info= s_('Profiles|Commit email')
- if @primary_email === current_user.public_email
- %span.badge.badge-info Public email
+ %span.badge.badge-info= s_('Profiles|Public email')
- if @primary_email === current_user.notification_email
- %span.badge.badge-info Notification email
+ %span.badge.badge-info= s_('Profiles|Default notification email')
- @emails.each do |email|
%li
= render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
%span.float-right
- if email.email === current_user.commit_email
- %span.badge.badge-info Commit email
+ %span.badge.badge-info= s_('Profiles|Commit email')
- if email.email === current_user.public_email
- %span.badge.badge-info Public email
+ %span.badge.badge-info= s_('Profiles|Public email')
- if email.email === current_user.notification_email
- %span.badge.badge-info Notification email
+ %span.badge.badge-info= s_('Profiles|Notification email')
- unless email.confirmed?
- - confirm_title = "#{email.confirmation_sent_at ? 'Resend' : 'Send'} confirmation email"
+ - confirm_title = "#{email.confirmation_sent_at ? _('Resend confirmation email') : _('Send confirmation email')}"
= link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'btn btn-sm btn-warning prepend-left-10'
- = link_to profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-danger prepend-left-10' do
- %span.sr-only Remove
+ = link_to profile_email_path(email), data: { confirm: _('Are you sure?')}, method: :delete, class: 'btn btn-sm btn-danger prepend-left-10' do
+ %span.sr-only= _('Remove')
= icon('trash')
diff --git a/app/views/profiles/gpg_keys/_form.html.haml b/app/views/profiles/gpg_keys/_form.html.haml
index 6c4cb614a2b..225487b2638 100644
--- a/app/views/profiles/gpg_keys/_form.html.haml
+++ b/app/views/profiles/gpg_keys/_form.html.haml
@@ -3,8 +3,8 @@
= form_errors(@gpg_key)
.form-group
- = f.label :key, class: 'label-bold'
- = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
+ = f.label :key, s_('Profiles|Key'), class: 'label-bold'
+ = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: _("Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'.")
.prepend-top-default
- = f.submit 'Add key', class: "btn btn-success"
+ = f.submit s_('Profiles|Add key'), class: "btn btn-success"
diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml
index d1fd7bc8e71..f8351644df5 100644
--- a/app/views/profiles/gpg_keys/_key.html.haml
+++ b/app/views/profiles/gpg_keys/_key.html.haml
@@ -9,17 +9,19 @@
%code= key.fingerprint
- if key.subkeys.present?
.subkeys
- %span.bold Subkeys:
+ %span.bold
+ = _('Subkeys')
+ = ':'
%ul.subkeys-list
- key.subkeys.each do |subkey|
%li
%code= subkey.fingerprint
.float-right
%span.key-created-at
- created #{time_ago_with_tooltip(key.created_at)}
- = link_to profile_gpg_key_path(key), data: { confirm: 'Are you sure? Removing this GPG key does not affect already signed commits.' }, method: :delete, class: "btn btn-danger prepend-left-10" do
- %span.sr-only Remove
+ = s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(key.created_at)}
+ = link_to profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: "btn btn-danger prepend-left-10" do
+ %span.sr-only= _('Remove')
= icon('trash')
- = link_to revoke_profile_gpg_key_path(key), data: { confirm: 'Are you sure? All commits that were signed with this GPG key will be unverified.' }, method: :put, class: "btn btn-danger prepend-left-10" do
- %span.sr-only Revoke
- Revoke
+ = link_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: "btn btn-danger prepend-left-10" do
+ %span.sr-only= _('Revoke')
+ = _('Revoke')
diff --git a/app/views/profiles/gpg_keys/_key_table.html.haml b/app/views/profiles/gpg_keys/_key_table.html.haml
index b9b60c218fd..ebbd1c8f672 100644
--- a/app/views/profiles/gpg_keys/_key_table.html.haml
+++ b/app/views/profiles/gpg_keys/_key_table.html.haml
@@ -6,6 +6,6 @@
- else
%p.settings-message.text-center
- if is_admin
- There are no GPG keys associated with this account.
+ = _('There are no GPG keys associated with this account.')
- else
- There are no GPG keys with access to your account.
+ = _('There are no GPG keys with access to your account.')
diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml
index 1d2e41cb437..f9f898a9225 100644
--- a/app/views/profiles/gpg_keys/index.html.haml
+++ b/app/views/profiles/gpg_keys/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "GPG Keys"
+- page_title _('GPG Keys')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,16 +6,16 @@
%h4.prepend-top-0
= page_title
%p
- GPG keys allow you to verify signed commits.
+ = _('GPG keys allow you to verify signed commits.')
.col-lg-8
%h5.prepend-top-0
- Add a GPG key
+ = _('Add a GPG key')
%p.profile-settings-content
- Before you can add a GPG key you need to
- = link_to 'generate it.', help_page_path('user/project/repository/gpg_signed_commits/index.md')
+ - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/repository/gpg_signed_commits/index.md') }
+ = _('Before you can add a GPG key you need to %{help_link_start}Generate it.%{help_link_end}'.html_safe) % {help_link_start: help_link_start, help_link_end:'</a>'.html_safe }
= render 'form'
%hr
%h5
- Your GPG keys (#{@gpg_keys.count})
+ = _('Your GPG keys (%{count})') % { count:@gpg_keys.count}
.append-bottom-default
= render 'key_table'
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index 21eef08983c..63ef5eaa172 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -3,11 +3,11 @@
= form_errors(@key)
.form-group
- = f.label :key, class: 'label-bold'
- %p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key.")
- = f.text_area :key, class: "form-control js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-rsa …"')
+ = f.label :key, s_('Profiles|Key'), class: 'label-bold'
+ %p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Don't use your private SSH key.")
+ = f.text_area :key, class: "form-control js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-ed25519 …" or "ssh-rsa …"')
.form-group
- = f.label :title, class: 'label-bold'
+ = f.label :title, _('Title'), class: 'label-bold'
= f.text_field :title, class: "form-control input-lg qa-key-title-field", required: true, placeholder: s_('Profiles|e.g. My MacBook key')
%p.form-text.text-muted= _('Name your individual key via a title')
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index ce20994b0f4..b9d73d89334 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -17,7 +17,8 @@
= key.last_used_at ? time_ago_with_tooltip(key.last_used_at) : 'n/a'
.float-right
%span.key-created-at
- created #{time_ago_with_tooltip(key.created_at)}
- = link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do
- %span.sr-only Remove
- = icon('trash')
+ = s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(key.created_at)}
+ - if key.can_delete?
+ = link_to path_to_key(key, is_admin), data: { confirm: _('Are you sure?')}, method: :delete, class: "btn btn-transparent prepend-left-10" do
+ %span.sr-only= _('Remove')
+ = icon('trash')
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 88473c7f72d..0ef01dec493 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -3,25 +3,26 @@
.col-md-4
.card
.card-header
- SSH Key
+ = _('SSH Key')
%ul.content-list
%li
- %span.light Title:
+ %span.light= _('Title:')
%strong= @key.title
%li
- %span.light Created on:
+ %span.light= _('Created on:')
%strong= @key.created_at.to_s(:medium)
%li
- %span.light Last used on:
+ %span.light= _('Last used on:')
%strong= @key.last_used_at.try(:to_s, :medium) || 'N/A'
.col-md-8
= form_errors(@key, type: 'key') unless @key.valid?
%p
- %span.light Fingerprint:
+ %span.light= _('Fingerprint:')
%code.key-fingerprint= @key.fingerprint
%pre.well-pre
= @key.key
.col-md-12
.float-right
- = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key qa-delete-key-button"
+ - if @key.can_delete?
+ = link_to _('Remove'), path_to_key(@key, is_admin), data: {confirm: _('Are you sure?')}, method: :delete, class: "btn btn-remove delete-key qa-delete-key-button"
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
index e088140fdd2..4a6d8a1870d 100644
--- a/app/views/profiles/keys/_key_table.html.haml
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -6,6 +6,6 @@
- else
%p.settings-message.text-center
- if is_admin
- There are no SSH keys associated with this account.
+ = _('There are no SSH keys associated with this account.')
- else
- There are no SSH keys with access to your account.
+ = _('There are no SSH keys with access to your account.')
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 55ca8d0ebd4..da6aa0fce3a 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,4 +1,4 @@
-- page_title "SSH Keys"
+- page_title _('SSH Keys')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -6,10 +6,10 @@
%h4.prepend-top-0
= page_title
%p
- SSH keys allow you to establish a secure connection between your computer and GitLab.
+ = _('SSH keys allow you to establish a secure connection between your computer and GitLab.')
.col-lg-8
%h5.prepend-top-0
- Add an SSH key
+ = _('Add an SSH key')
%p.profile-settings-content
- generate_link_url = help_page_path("ssh/README", anchor: 'generating-a-new-ssh-key-pair')
- existing_link_url = help_page_path("ssh/README", anchor: 'locating-an-existing-ssh-key-pair')
@@ -19,6 +19,6 @@
= render 'form'
%hr
%h5
- Your SSH keys (#{@keys.count})
+ = _('Your SSH keys (%{count})') % { count:@keys.count }
.append-bottom-default
= render 'key_table'
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index 28be6172219..360de7a0c11 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,5 +1,5 @@
- add_to_breadcrumbs "SSH Keys", profile_keys_path
- breadcrumb_title @key.title
-- page_title @key.title, "SSH Keys"
+- page_title @key.title, _('SSH Keys')
- @content_class = "limit-container-width" unless fluid_layout
= render "key_details"
diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml
index a12246bcdcc..cf17ee44145 100644
--- a/app/views/profiles/notifications/_group_settings.html.haml
+++ b/app/views/profiles/notifications/_group_settings.html.haml
@@ -1,12 +1,17 @@
-%li.notification-list-item
- %span.notification.fa.fa-holder.append-right-5
- - if setting.global?
- = notification_icon(current_user.global_notification_setting.level)
- - else
- = notification_icon(setting.level)
+.gl-responsive-table-row.notification-list-item
+ .table-section.section-40
+ %span.notification.fa.fa-holder.append-right-5
+ - if setting.global?
+ = notification_icon(current_user.global_notification_setting.level)
+ - else
+ = notification_icon(setting.level)
- %span.str-truncated
- = link_to group.name, group_path(group)
+ %span.str-truncated
+ = link_to group.name, group_path(group)
- .float-right
+ .table-section.section-30.text-right
= render 'shared/notifications/button', notification_setting: setting
+
+ .table-section.section-30
+ = form_for @user.notification_settings.find { |ns| ns.source == group }, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f|
+ = f.select :notification_email, @user.all_emails, { include_blank: 'Global notification email' }, class: 'select2 js-group-notification-email'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index e616e5546b3..1f311e9a4a4 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "Notifications"
+- page_title _('Notifications')
- @content_class = "limit-container-width" unless fluid_layout
%div
@@ -14,12 +14,12 @@
%h4.prepend-top-0
= page_title
%p
- You can specify notification level per group or per project.
+ = _('You can specify notification level per group or per project.')
%p
- By default, all projects and groups will use the global notifications setting.
+ = _('By default, all projects and groups will use the global notifications setting.')
.col-lg-8
%h5.prepend-top-0
- Global notification settings
+ = _('Global notification settings')
= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
= render_if_exists 'profiles/notifications/email_settings', form: f
@@ -35,19 +35,18 @@
= form_for @user, url: profile_notifications_path, method: :put do |f|
%label{ for: 'user_notified_of_own_activity' }
= f.check_box :notified_of_own_activity
- %span Receive notifications about your own activity
+ %span= _('Receive notifications about your own activity')
%hr
%h5
- Groups (#{@group_notifications.count})
+ = _('Groups (%{count})') % { count: @group_notifications.count }
%div
- %ul.bordered-list
- - @group_notifications.each do |setting|
- = render 'group_settings', setting: setting, group: setting.source
+ - @group_notifications.each do |setting|
+ = render 'group_settings', setting: setting, group: setting.source
%h5
- Projects (#{@project_notifications.count})
+ = _('Projects (%{count})') % { count: @project_notifications.count }
%p.account-well
- To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there.
+ = _('To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there.')
.append-bottom-default
%ul.bordered-list
- @project_notifications.each do |setting|
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 0b4b9841ea1..ac8c31189d0 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,5 +1,5 @@
-- breadcrumb_title "Edit Password"
-- page_title "Password"
+- breadcrumb_title _('Edit Password')
+- page_title _('Password')
- @content_class = "limit-container-width" unless fluid_layout
.row.prepend-top-default
@@ -7,28 +7,29 @@
%h4.prepend-top-0
= page_title
%p
- After a successful password update, you will be redirected to the login page where you can log in with your new password.
+ = _('After a successful password update, you will be redirected to the login page where you can log in with your new password.')
.col-lg-8
%h5.prepend-top-0
- Change your password
- - unless @user.password_automatically_set?
- or recover your current one
+ - if @user.password_automatically_set
+ = _('Change your password')
+ - else
+ = _('Change your password or recover your current one')
= form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
= form_errors(@user)
- unless @user.password_automatically_set?
.form-group
- = f.label :current_password, class: 'label-bold'
+ = f.label :current_password, _('Current password'), class: 'label-bold'
= f.password_field :current_password, required: true, class: 'form-control'
%p.form-text.text-muted
- You must provide your current password in order to change it.
+ = _('You must provide your current password in order to change it.')
.form-group
- = f.label :password, 'New password', class: 'label-bold'
+ = f.label :password, _('New password'), class: 'label-bold'
= f.password_field :password, required: true, class: 'form-control'
.form-group
- = f.label :password_confirmation, class: 'label-bold'
+ = f.label :password_confirmation, _('Password confirmation'), class: 'label-bold'
= f.password_field :password_confirmation, required: true, class: 'form-control'
.prepend-top-default.append-bottom-default
- = f.submit 'Save password', class: "btn btn-success append-right-10"
+ = f.submit _('Save password'), class: "btn btn-success append-right-10"
- unless @user.password_automatically_set?
- = link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link"
+ = link_to _('I forgot my password'), reset_profile_password_path, method: :put, class: "account-btn-link"
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index 4b84835429c..ce60455ab89 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -13,13 +13,18 @@
- unless @user.password_automatically_set?
.form-group.row
- = f.label :current_password, class: 'col-form-label col-sm-2'
- .col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :current_password, _('Current password')
+ .col-sm-10
+ = f.password_field :current_password, required: true, class: 'form-control'
.form-group.row
- = f.label :password, class: 'col-form-label col-sm-2'
- .col-sm-10= f.password_field :password, required: true, class: 'form-control'
+ .col-sm-2.col-form-label
+ = f.label :password, _('New password')
+ .col-sm-10
+ = f.password_field :password, required: true, class: 'form-control'
.form-group.row
- = f.label :password_confirmation, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :password_confirmation, _('Password confirmation')
.col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 58f2eb229ba..4ebfaff0860 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,11 +1,12 @@
-- page_title 'Preferences'
+- page_title _('Preferences')
- @content_class = "limit-container-width" unless fluid_layout
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-4.application-theme
%h4.prepend-top-0
= s_('Preferences|Navigation theme')
- %p Customize the appearance of the application header and navigation sidebar.
+ %p
+ = s_('Preferences|Customize the appearance of the application header and navigation sidebar.')
.col-lg-8.application-theme
- Gitlab::Themes.each do |theme|
= label_tag do
@@ -18,11 +19,11 @@
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Syntax highlighting theme
+ = s_('Preferences|Syntax highlighting theme')
%p
- This setting allows you to customize the appearance of the syntax.
+ = s_('Preferences|This setting allows you to customize the appearance of the syntax.')
= succeed '.' do
- = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
.col-lg-8.syntax-theme
- Gitlab::ColorSchemes.each do |scheme|
= label_tag do
@@ -35,31 +36,31 @@
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Behavior
+ = s_('Preferences|Behavior')
%p
- This setting allows you to customize the behavior of the system layout and default views.
+ = s_('Preferences|This setting allows you to customize the behavior of the system layout and default views.')
= succeed '.' do
- = link_to 'Learn more', help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
.col-lg-8
.form-group
= f.label :layout, class: 'label-bold' do
- Layout width
+ = s_('Preferences|Layout width')
= f.select :layout, layout_choices, {}, class: 'form-control'
.form-text.text-muted
- Choose between fixed (max. 1280px) and fluid (100%) application layout.
+ = s_('Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout.')
.form-group
= f.label :dashboard, class: 'label-bold' do
- Default dashboard
+ = s_('Preferences|Default dashboard')
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
= render_if_exists 'profiles/preferences/group_overview_selector', f: f # EE-specific
.form-group
= f.label :project_view, class: 'label-bold' do
- Project overview content
+ = s_('Preferences|Project overview content')
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.form-text.text-muted
- Choose what content you want to see on a project’s overview page.
+ = s_('Preferences|Choose what content you want to see on a project’s overview page.')
.col-sm-12
%hr
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 917e7acc353..e36d5192a29 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -47,9 +47,9 @@
- if @user.status
= emoji_icon @user.status.emoji
%span#js-no-emoji-placeholder.no-emoji-placeholder{ class: ('hidden' if @user.status) }
- = sprite_icon('emoji_slightly_smiling_face', css_class: 'award-control-icon-neutral')
- = sprite_icon('emoji_smiley', css_class: 'award-control-icon-positive')
- = sprite_icon('emoji_smile', css_class: 'award-control-icon-super-positive')
+ = sprite_icon('slight-smile', css_class: 'award-control-icon-neutral')
+ = sprite_icon('smiley', css_class: 'award-control-icon-positive')
+ = sprite_icon('smile', css_class: 'award-control-icon-super-positive')
- reset_message_button = button_tag type: :button,
id: 'js-clear-user-status-button',
class: 'clear-user-status btn has-tooltip',
diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml
index 759d39cf5f5..be0af977011 100644
--- a/app/views/profiles/two_factor_auths/_codes.html.haml
+++ b/app/views/profiles/two_factor_auths/_codes.html.haml
@@ -1,8 +1,6 @@
%p.slead
- Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one
- time each to regain access to your account. Please save them in a safe place, or you
- %b will
- lose access to your account.
+ - lose_2fa_message = _('Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account.') % { b_start:'<b>', b_end:'</b>' }
+ = lose_2fa_message.html_safe
.codes.card
%ul
@@ -11,5 +9,5 @@
%span.monospace= code
.d-flex
- = link_to 'Proceed', profile_account_path, class: 'btn btn-success append-right-10'
- = link_to 'Download codes', "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
+ = link_to _('Proceed'), profile_account_path, class: 'btn btn-success append-right-10'
+ = link_to _('Download codes'), "data:text/plain;charset=utf-8,#{CGI.escape(@codes.join("\n"))}", download: "gitlab-recovery-codes.txt", class: 'btn btn-default'
diff --git a/app/views/profiles/two_factor_auths/codes.html.haml b/app/views/profiles/two_factor_auths/codes.html.haml
index addf356697a..53907ebffab 100644
--- a/app/views/profiles/two_factor_auths/codes.html.haml
+++ b/app/views/profiles/two_factor_auths/codes.html.haml
@@ -1,5 +1,6 @@
-- page_title 'Recovery Codes', 'Two-factor Authentication'
+- page_title _('Recovery Codes'), _('Two-factor Authentication')
-%h3.page-title Two-factor Authentication Recovery codes
+%h3.page-title
+ = _('Two-factor Authentication Recovery codes')
%hr
= render 'codes'
diff --git a/app/views/profiles/two_factor_auths/create.html.haml b/app/views/profiles/two_factor_auths/create.html.haml
index e330aadac13..973eb8136c4 100644
--- a/app/views/profiles/two_factor_auths/create.html.haml
+++ b/app/views/profiles/two_factor_auths/create.html.haml
@@ -1,6 +1,6 @@
-- page_title 'Two-factor Authentication', 'Account'
+- page_title _('Two-factor Authentication'), _('Account')
.alert.alert-success
- Congratulations! You have enabled Two-factor Authentication!
+ = _('Congratulations! You have enabled Two-factor Authentication!')
= render 'codes'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index d986c566928..5501e63e027 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,72 +1,68 @@
-- page_title 'Two-Factor Authentication', 'Account'
-- add_to_breadcrumbs("Two-Factor Authentication", profile_account_path)
+- page_title _('Two-Factor Authentication'), _('Account')
+- add_to_breadcrumbs(_('Two-Factor Authentication'), profile_account_path)
- @content_class = "limit-container-width" unless fluid_layout
.js-two-factor-auth{ 'data-two-factor-skippable' => "#{two_factor_skippable?}", 'data-two_factor_skip_url' => skip_profile_two_factor_auth_path }
.row.prepend-top-default
.col-lg-4
%h4.prepend-top-0
- Register Two-Factor Authenticator
+ = _('Register Two-Factor Authenticator')
%p
- Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).
+ = _('Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA).')
.col-lg-8
- if current_user.two_factor_otp_enabled?
%p
- You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.
+ = _("You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication.")
%p
- If you lose your recovery codes you can generate new ones, invalidating all previous codes.
+ = _('If you lose your recovery codes you can generate new ones, invalidating all previous codes.')
%div
- = link_to 'Disable two-factor authentication', profile_two_factor_auth_path,
+ = link_to _('Disable two-factor authentication'), profile_two_factor_auth_path,
method: :delete,
- data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." },
+ data: { confirm: _('Are you sure? This will invalidate your registered applications and U2F devices.') },
class: 'btn btn-danger append-right-10'
= form_tag codes_profile_two_factor_auth_path, {style: 'display: inline-block', method: :post} do |f|
- = submit_tag 'Regenerate recovery codes', class: 'btn'
+ = submit_tag _('Regenerate recovery codes'), class: 'btn'
- else
%p
- Install a soft token authenticator like <a href="https://freeotp.github.io/">FreeOTP</a>
- or Google Authenticator from your application repository and scan this QR code.
- More information is available in the #{link_to('documentation', help_page_path('user/profile/account/two_factor_authentication'))}.
+ - help_link_start = '<a href="%{url}" target="_blank">' % { url: help_page_path('user/profile/account/two_factor_authentication') }
+ - register_2fa_token = _('Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}.') % { free_otp_link:'<a href="https://freeotp.github.io/">FreeOTP</a>', help_link_start:help_link_start, help_link_end:'</a>' }
+ = register_2fa_token.html_safe
.row.append-bottom-10
.col-md-4
= raw @qr_code
.col-md-8
.account-well
%p.prepend-top-0.append-bottom-0
- Can't scan the code?
+ = _("Can't scan the code?")
%p.prepend-top-0.append-bottom-0
- To add the entry manually, provide the following details to the application on your phone.
+ = _('To add the entry manually, provide the following details to the application on your phone.')
%p.prepend-top-0.append-bottom-0
- Account:
- = @account_string
+ = _('Account: %{account}') % { account: @account_string }
%p.prepend-top-0.append-bottom-0
- Key:
- = current_user.otp_secret.scan(/.{4}/).join(' ')
+ = _('Key: %{key}') %{ key: current_user.otp_secret.scan(/.{4}/).join(' ') }
%p.two-factor-new-manual-content
- Time based: Yes
+ = _('Time based: Yes')
= form_tag profile_two_factor_auth_path, method: :post do |f|
- if @error
.alert.alert-danger
= @error
.form-group
- = label_tag :pin_code, nil, class: "label-bold"
+ = label_tag :pin_code, _('Pin code'), class: "label-bold"
= text_field_tag :pin_code, nil, class: "form-control", required: true
.prepend-top-default
- = submit_tag 'Register with two-factor app', class: 'btn btn-success'
+ = submit_tag _('Register with two-factor app'), class: 'btn btn-success'
%hr
.row.prepend-top-default
.col-lg-4
%h4.prepend-top-0
- Register Universal Two-Factor (U2F) Device
+ = _('Register Universal Two-Factor (U2F) Device')
%p
- Use a hardware device to add the second factor of authentication.
+ = _('Use a hardware device to add the second factor of authentication.')
%p
- As U2F devices are only supported by a few browsers, we require that you set up a
- two-factor authentication app before a U2F device. That way you'll always be able to
- log in - even when you're using an unsupported browser.
+ = _("As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser.")
.col-lg-8
- if @u2f_registration.errors.present?
= form_errors(@u2f_registration)
@@ -74,7 +70,8 @@
%hr
- %h5 U2F Devices (#{@u2f_registrations.length})
+ %h5
+ = _('U2F Devices (%{length})') % { length: @u2f_registrations.length }
- if @u2f_registrations.present?
.table-responsive
@@ -85,16 +82,16 @@
%col{ width: "20%" }
%thead
%tr
- %th Name
- %th Registered On
+ %th= _('Name')
+ %th= s_('2FADevice|Registered On')
%th
%tbody
- @u2f_registrations.each do |registration|
%tr
- %td= registration.name.presence || "<no name set>"
+ %td= registration.name.presence || _("<no name set>")
%td= registration.created_at.to_date.to_s(:medium)
- %td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger float-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." }
+ %td= link_to _('Delete'), profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger float-right", data: { confirm: _('Are you sure you want to delete this device? This action cannot be undone.') }
- else
.settings-message.text-center
- You don't have any U2F devices registered yet.
+ = _("You don't have any U2F devices registered yet.")
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 0edd8ee5e46..2b0c3985755 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -4,7 +4,6 @@
- project = local_assigns.fetch(:project) { @project }
- content_url = local_assigns.fetch(:content_url) { @tree.readme ? project_blob_path(@project, tree_join(@ref, @tree.readme.path)) : project_tree_path(@project, @ref) }
- show_auto_devops_callout = show_auto_devops_callout?(@project)
-- vue_file_list = Feature.enabled?(:vue_file_list, @project)
#tree-holder.tree-holder.clearfix
.nav-block
@@ -14,11 +13,11 @@
= render 'shared/commit_well', commit: commit, ref: ref, project: project
- if is_project_overview
- .project-buttons.append-bottom-default{ class: ("js-hide-on-navigation" if vue_file_list) }
+ .project-buttons.append-bottom-default{ class: ("js-keep-hidden-on-navigation" if vue_file_list_enabled?) }
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
- - if vue_file_list
- #js-tree-list{ data: { project_path: @project.full_path, ref: ref } }
+ - if vue_file_list_enabled?
+ #js-tree-list{ data: { project_path: @project.full_path, project_short_path: @project.path, ref: ref, full_name: @project.name_with_namespace } }
- if @tree.readme
= render "projects/tree/readme", readme: @tree.readme
- else
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index a97322dace4..9f5241344a7 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,7 +1,7 @@
- empty_repo = @project.empty_repo?
- show_auto_devops_callout = show_auto_devops_callout?(@project)
- max_project_topic_length = 15
-.project-home-panel{ class: [("empty-project" if empty_repo), ("js-hide-on-navigation" if Feature.enabled?(:vue_file_list, @project))] }
+.project-home-panel{ class: [("empty-project" if empty_repo), ("js-keep-hidden-on-navigation" if vue_file_list_enabled?)] }
.row.append-bottom-8
.home-panel-title-row.col-md-12.col-lg-6.d-flex
.avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index 9c854369c93..28d4f8eb201 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -8,61 +8,67 @@
.import-buttons
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
- = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "gitlab_export" } do
+ = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit', **tracking_attrs(track_label, 'click_button', 'gitlab_export') do
= icon('gitlab', text: 'GitLab export')
- if github_import_enabled?
%div
- = link_to new_import_github_path, class: 'btn js-import-github', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "github" } do
+ = link_to new_import_github_path, class: 'btn js-import-github', **tracking_attrs(track_label, 'click_button', 'github') do
= icon('github', text: 'GitHub')
- if bitbucket_import_enabled?
%div
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}",
- data: { track_label: "#{track_label}", track_event: "click_button", track_property: "bitbucket_cloud" } do
+ **tracking_attrs(track_label, 'click_button', 'bitbucket_cloud') do
= icon('bitbucket', text: 'Bitbucket Cloud')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
- if bitbucket_server_import_enabled?
%div
- = link_to status_import_bitbucket_server_path, class: "btn import_bitbucket",
- data: { track_label: "#{track_label}", track_event: "click_button", track_property: "bitbucket_server" } do
+ = link_to status_import_bitbucket_server_path, class: "btn import_bitbucket", **tracking_attrs(track_label, 'click_button', 'bitbucket_server') do
= icon('bitbucket-square', text: 'Bitbucket Server')
%div
- if gitlab_import_enabled?
%div
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}",
- data: { track_label: "#{track_label}", track_event: "click_button", track_property: "gitlab_com" } do
+ **tracking_attrs(track_label, 'click_button', 'gitlab_com') do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
- if google_code_import_enabled?
%div
- = link_to new_import_google_code_path, class: 'btn import_google_code', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "google_code" } do
+ = link_to new_import_google_code_path, class: 'btn import_google_code', **tracking_attrs(track_label, 'click_button', 'google_code') do
= icon('google', text: 'Google Code')
- if fogbugz_import_enabled?
%div
- = link_to new_import_fogbugz_path, class: 'btn import_fogbugz', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "fogbugz" } do
+ = link_to new_import_fogbugz_path, class: 'btn import_fogbugz', **tracking_attrs(track_label, 'click_button', 'fogbugz') do
= icon('bug', text: 'Fogbugz')
- if gitea_import_enabled?
%div
- = link_to new_import_gitea_path, class: 'btn import_gitea', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "gitea" } do
+ = link_to new_import_gitea_path, class: 'btn import_gitea', **tracking_attrs(track_label, 'click_button', 'gitea') do
= custom_icon('gitea_logo')
Gitea
- if git_import_enabled?
%div
- %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active', data: { toggle_open_class: 'active', track_label: "#{track_label}" , track_event: "click_button", track_property: "repo_url" } } }
+ %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' }, **tracking_attrs(track_label, 'click_button', 'repo_url') }
= icon('git', text: 'Repo by URL')
- if manifest_import_enabled?
%div
- = link_to new_import_manifest_path, class: 'btn import_manifest', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "manifest_file" } do
+ = link_to new_import_manifest_path, class: 'btn import_manifest', **tracking_attrs(track_label, 'click_button', 'manifest_file') do
= icon('file-text-o', text: 'Manifest file')
+ - if phabricator_import_enabled?
+ %div
+ = link_to new_import_phabricator_path, class: 'btn import_phabricator', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "phabricator" } do
+ = custom_icon('issues')
+ = _("Phabricator Tasks")
+
+
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
= form_for @project, html: { class: 'new_project' } do |f|
%hr
diff --git a/app/views/projects/_merge_request_merge_checks_settings.html.haml b/app/views/projects/_merge_request_merge_checks_settings.html.haml
index 1ab467a3710..c21d333f21a 100644
--- a/app/views/projects/_merge_request_merge_checks_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_checks_settings.html.haml
@@ -3,7 +3,7 @@
.form-group
%b= s_('ProjectSettings|Merge checks')
%p.text-secondary= s_('ProjectSettings|These checks must pass before merge requests can be merged')
- .form-check.mb-2.builds-feature{ class: ("hidden" if @project && @project.project_feature.send(:builds_access_level) == 0) }
+ .form-check.mb-2.builds-feature
= form.check_box :only_allow_merge_if_pipeline_succeeds, class: 'form-check-input'
= form.label :only_allow_merge_if_pipeline_succeeds, class: 'form-check-label' do
= s_('ProjectSettings|Pipelines must succeed')
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index 1c1c7d832bd..e423631ec99 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -1,4 +1,4 @@
-- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
+- visibility_level = selected_visibility_level(@project, params.dig(:project, :visibility_level))
- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
- hide_init_with_readme = local_assigns.fetch(:hide_init_with_readme, false)
- track_label = local_assigns.fetch(:track_label, 'blank_project')
@@ -54,7 +54,7 @@
.form-group.row.initialize-with-readme-setting
%div{ :class => "col-sm-12" }
.form-check
- = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme" }
+ = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme" }
= label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
.option-title
%strong Initialize repository with a README
diff --git a/app/views/projects/blob/_header_content.html.haml b/app/views/projects/blob/_header_content.html.haml
index 88fa31a73b0..7ed71a7d43c 100644
--- a/app/views/projects/blob/_header_content.html.haml
+++ b/app/views/projects/blob/_header_content.html.haml
@@ -6,7 +6,7 @@
= copy_file_path_button(blob.path)
- %small
+ %small.mr-1
= number_to_human_size(blob.raw_size)
- if blob.stored_externally? && blob.external_storage == :lfs
diff --git a/app/views/projects/buttons/_download_links.html.haml b/app/views/projects/buttons/_download_links.html.haml
index 7f2cd8e109e..d344167a6c5 100644
--- a/app/views/projects/buttons/_download_links.html.haml
+++ b/app/views/projects/buttons/_download_links.html.haml
@@ -1,5 +1,5 @@
- formats = [['zip', 'btn-primary'], ['tar.gz'], ['tar.bz2'], ['tar']]
-.d-flex.justify-content-between
+.btn-group.ml-0.w-100
- formats.each do |(fmt, extra_class)|
= link_to fmt, project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: fmt), rel: 'nofollow', download: '', class: "btn btn-xs #{extra_class}"
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index f4560404c03..bdf7b933ab8 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -53,9 +53,10 @@
%span.badge.badge-info= _('manual')
- if pipeline_link
- %td
- = link_to pipeline_path(pipeline) do
+ %td.pipeline-link
+ = link_to pipeline_path(pipeline), class: 'has-tooltip', title: _('Pipeline ID (IID)') do
%span.pipeline-id ##{pipeline.id}
+ %span.pipeline-iid (##{pipeline.iid})
%span by
- if pipeline.user
= user_avatar(user: pipeline.user, size: 20)
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index a0db48bf8ff..77ea2c04b28 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -28,7 +28,7 @@
= link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 d-none d-sm-none d-md-inline" do
#{ _('Browse files') }
.dropdown.inline
- %a.btn.btn-default.dropdown-toggle.qa-options-button{ data: { toggle: "dropdown" } }
+ %a.btn.btn-default.dropdown-toggle.qa-options-button.d-md-inline{ data: { toggle: "dropdown" } }
%span= _('Options')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-right
@@ -81,7 +81,7 @@
= link_to project_pipeline_path(@project, last_pipeline.id), class: "ci-status-icon-#{last_pipeline.status}" do
= ci_icon_for_status(last_pipeline.status)
#{ _('Pipeline') }
- = link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id)
+ = link_to "##{last_pipeline.id} (##{last_pipeline.iid})", project_pipeline_path(@project, last_pipeline.id), class: "has-tooltip", title: _('Pipeline ID (IID)')
= ci_label_for_status(last_pipeline.status)
- if last_pipeline.stages_count.nonzero?
#{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) }
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index e2d078855d9..87b9920e8b4 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -9,10 +9,13 @@
- commit_status = commit.present(current_user: current_user).status_for(ref)
- link = commit_path(project, commit, merge_request: merge_request)
+
+- show_project_name = local_assigns.fetch(:show_project_name, false)
+
%li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
.avatar-cell.d-none.d-sm-block
- = author_avatar(commit, size: 36, has_tooltip: false)
+ = author_avatar(commit, size: 40, has_tooltip: false)
.commit-detail.flex-list
.commit-content.qa-commit-content
@@ -32,6 +35,7 @@
- commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom')
- commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe }
+ = render_if_exists 'projects/commits/project_namespace', show_project_name: show_project_name, project: project
- if commit.description?
%pre.commit-row-description.js-toggle-content.append-bottom-8
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 311b0be19ab..9587ea4696b 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,7 +1,7 @@
/ Side-by-side diff view
.text-file{ data: diff_view_data }
- %table.diff-wrap-lines.code.js-syntax-highlight
+ %table.diff-wrap-lines.code.code-commit.js-syntax-highlight
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 018c5b38536..641a0689c26 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -3,7 +3,7 @@
.suppressed-container
%a.show-suppressed-diff.cursor-pointer.js-show-suppressed-diff= _("Changes suppressed. Click to show.")
-%table.text-file.diff-wrap-lines.code.js-syntax-highlight.commit-diff{ data: diff_view_data, class: too_big ? 'hide' : '' }
+%table.text-file.diff-wrap-lines.code.code-commit.js-syntax-highlight.commit-diff{ data: diff_view_data, class: too_big ? 'hide' : '' }
= render partial: "projects/diffs/line",
collection: diff_file.highlighted_diff_lines,
as: :line,
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index fbd70cd1906..457b2936278 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -8,18 +8,18 @@
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
- refs_path = refs_namespace_project_path(@project.namespace, @project, search: '')
- .create-mr-dropdown-wrap.d-inline-block{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
- .btn-group.unavailable
+ .create-mr-dropdown-wrap.d-inline-block.full-width-mobile{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
+ .btn-group.btn-group-sm.unavailable
%button.btn.btn-grouped{ type: 'button', disabled: 'disabled' }
= icon('spinner', class: 'fa-spin')
%span.text
Checking branch availability…
- .btn-group.available.hidden
+ .btn-group.btn-group-sm.available.hidden
%button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } }
= value
- %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' }, display: 'static' } }
+ %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle.flex-grow-0{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' }, display: 'static' } }
= icon('caret-down')
.droplab-dropdown
diff --git a/app/views/projects/jobs/_table.html.haml b/app/views/projects/jobs/_table.html.haml
index d124d3ebfc1..b08223546f7 100644
--- a/app/views/projects/jobs/_table.html.haml
+++ b/app/views/projects/jobs/_table.html.haml
@@ -16,7 +16,7 @@
%th Runner
%th Stage
%th Name
- %th
+ %th Timing
%th Coverage
%th
diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
index 03226de120d..7bd5c437942 100644
--- a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
+++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
@@ -1,5 +1,5 @@
%inline-conflict-lines{ "inline-template" => "true", ":file" => "file" }
- %table.diff-wrap-lines.code.js-syntax-highlight
+ %table.diff-wrap-lines.code.code-commit.js-syntax-highlight
%tr.line_holder.diff-inline{ "v-for" => "line in file.inlineLines" }
%td.diff-line-num.new_line{ ":class" => "lineCssClass(line)", "v-if" => "!line.isHeader" }
%a {{line.new_line}}
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 05aeb5d972b..a201fafb949 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -31,29 +31,26 @@
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
.merge-request-tabs-container
- .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
- %ul.merge-request-tabs.nav-tabs.nav.nav-links.scrolling-tabs
- %li.notes-tab.qa-notes-tab
- = tab_link_for @merge_request, :show, force_link: @commit.present? do
- = _("Discussion")
- %span.badge.badge-pill= @merge_request.related_notes.user.count
- - if @merge_request.source_project
- %li.commits-tab
- = tab_link_for @merge_request, :commits do
- = _("Commits")
- %span.badge.badge-pill= @commits_count
- - if @pipelines.any?
- %li.pipelines-tab
- = tab_link_for @merge_request, :pipelines do
- = _("Pipelines")
- %span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
- %li.diffs-tab.qa-diffs-tab
- = tab_link_for @merge_request, :diffs do
- = _("Changes")
- %span.badge.badge-pill= @merge_request.diff_size
- .d-inline-flex.flex-wrap
+ %ul.merge-request-tabs.nav-tabs.nav.nav-links
+ %li.notes-tab.qa-notes-tab
+ = tab_link_for @merge_request, :show, force_link: @commit.present? do
+ = _("Discussion")
+ %span.badge.badge-pill= @merge_request.related_notes.user.count
+ - if @merge_request.source_project
+ %li.commits-tab
+ = tab_link_for @merge_request, :commits do
+ = _("Commits")
+ %span.badge.badge-pill= @commits_count
+ - if @pipelines.any?
+ %li.pipelines-tab
+ = tab_link_for @merge_request, :pipelines do
+ = _("Pipelines")
+ %span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
+ %li.diffs-tab.qa-diffs-tab
+ = tab_link_for @merge_request, :diffs do
+ = _("Changes")
+ %span.badge.badge-pill= @merge_request.diff_size
+ .d-flex.flex-wrap.align-items-center.justify-content-lg-end
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@merge_request),
notes_filters: UserPreference.notes_filters.to_json } }
#js-vue-discussion-counter
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 78b416edd5c..1cee8be604a 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -59,7 +59,7 @@
= render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
- - if can?(current_user, :read_issue, @project) && @milestone.total_items_count(current_user).zero?
+ - if can?(current_user, :read_issue, @project) && @milestone.total_issues_count(current_user).zero?
.alert.alert-success.prepend-top-default
%span= _('Assign some issues to this milestone.')
- elsif @milestone.complete?(current_user) && @milestone.active?
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index eb6838cec8d..044adb75bea 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -41,9 +41,9 @@
.note-actions-item
= button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
= icon('spinner spin')
- %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
- %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
- %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+ %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
+ %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
+ %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
- if note_editable
.note-actions-item
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index 8de84f82e9f..8a6e5fde99b 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -11,7 +11,7 @@
- unless is_current_user
%li
= link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
- = _('Report abuse to GitLab')
+ = _('Report abuse to admin')
- if note_editable
%li
= link_to note_url(note), method: :delete, data: { confirm: 'Are you sure you want to delete this comment?' }, remote: true, class: 'js-note-delete' do
diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml
index b7b46c56c37..33f2166480b 100644
--- a/app/views/projects/pages_domains/_form.html.haml
+++ b/app/views/projects/pages_domains/_form.html.haml
@@ -1,29 +1,80 @@
- if @domain.errors.any?
- #error_explanation
- .alert.alert-danger
- - @domain.errors.full_messages.each do |msg|
- %p= msg
+ .alert.alert-danger
+ - @domain.errors.full_messages.each do |msg|
+ = msg
.form-group.row
- = f.label :domain, class: 'col-form-label col-sm-2' do
- = _("Domain")
+ .col-sm-2.col-form-label
+ = f.label :domain, _("Domain")
.col-sm-10
- = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control', disabled: @domain.persisted?
+ = f.text_field :domain, required: true, autocomplete: "off", class: "form-control", disabled: @domain.persisted?
- if Gitlab.config.pages.external_https
- .form-group.row
- = f.label :certificate, class: 'col-form-label col-sm-2' do
- = _("Certificate (PEM)")
- .col-sm-10
- = f.text_area :certificate, rows: 5, class: 'form-control'
- %span.help-inline= _("Upload a certificate for your domain with all intermediates")
-
- .form-group.row
- = f.label :key, class: 'col-form-label col-sm-2' do
- = _("Key (PEM)")
- .col-sm-10
- = f.text_area :key, rows: 5, class: 'form-control'
- %span.help-inline= _("Upload a private key for your certificate")
+
+ - auto_ssl_available = Feature.enabled?(:pages_auto_ssl)
+ - auto_ssl_enabled = @domain.auto_ssl_enabled?
+ - auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled
+
+ - if auto_ssl_available
+ .form-group.row
+ .col-sm-2.col-form-label
+ %label{ for: "pages_domain_auto_ssl_enabled_button" }
+ - lets_encrypt_link_url = "https://letsencrypt.org/"
+ - lets_encrypt_link_start = "<a href=\"%{lets_encrypt_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { lets_encrypt_link_url: lets_encrypt_link_url }
+ - lets_encrypt_link_end = "</a>".html_safe
+ = _("Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: lets_encrypt_link_end }
+
+ .col-sm-10.js-auto-ssl-toggle-container
+ %button{ type: "button", id: "pages_domain_auto_ssl_enabled_button",
+ class: "js-project-feature-toggle project-feature-toggle mt-2 #{"is-checked" if auto_ssl_available_and_enabled}",
+ "aria-label": _("Automatic certificate management using Let's Encrypt") }
+ = f.hidden_field :auto_ssl_enabled?, class: "js-project-feature-toggle-input"
+ %span.toggle-icon
+ = sprite_icon("status_success_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-checked")
+ = sprite_icon("status_failed_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-unchecked")
+ %p.text-secondary.mt-3
+ - docs_link_url = help_page_path("user/project/pages/lets_encrypt_for_gitlab_pages.md", anchor: "lets-encrypt-for-gitlab-pages")
+ - docs_link_start = "<a href=\"%{docs_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { docs_link_url: docs_link_url }
+ - docs_link_end = "</a>".html_safe
+ = _("Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
+
+ .js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
+ .form-group.row
+ .col-sm-2.col-form-label
+ = f.label :certificate, _("Certificate (PEM)")
+ .col-sm-10
+ - if auto_ssl_available_and_enabled && !@domain.certificate.empty?
+ = f.text_area :certificate,
+ rows: 5,
+ class: "form-control",
+ disabled: true
+ %span.help-inline.text-muted= _("This certificate is automatically managed by Let's Encrypt")
+ - else
+ %p.text-secondary.form-control-plaintext= _("The certificate will be shown here once it has been obtained from Let's Encrypt. This process may take up to an hour to complete.")
+
+ .js-shown-unless-auto-ssl{ class: ("d-none" if auto_ssl_available_and_enabled) }
+ .form-group.row
+ .col-sm-2.col-form-label
+ = f.label :certificate, _("Certificate (PEM)")
+ .col-sm-10
+ = f.text_area :certificate,
+ rows: 5,
+ class: "form-control js-enabled-unless-auto-ssl",
+ value: (@domain.certificate unless auto_ssl_available_and_enabled),
+ disabled: auto_ssl_available_and_enabled
+ %span.help-inline.text-muted= _("Upload a certificate for your domain with all intermediates")
+
+ .form-group.row
+ .col-sm-2.col-form-label
+ = f.label :key, _("Key (PEM)")
+ .col-sm-10
+ = f.text_area :key,
+ rows: 5,
+ class: "form-control js-enabled-unless-auto-ssl",
+ value: (@domain.key unless auto_ssl_available_and_enabled),
+ disabled: auto_ssl_available_and_enabled
+ %span.help-inline.text-muted= _("Upload a private key for your certificate")
+
- else
.nothing-here-block
= _("Support for custom certificates is disabled. Ask your system's administrator to enable it.")
diff --git a/app/views/projects/pages_domains/_helper_text.html.haml b/app/views/projects/pages_domains/_helper_text.html.haml
new file mode 100644
index 00000000000..5a79fefabfc
--- /dev/null
+++ b/app/views/projects/pages_domains/_helper_text.html.haml
@@ -0,0 +1,9 @@
+- docs_link_url = help_page_path("user/project/pages/getting_started_part_three.md", anchor: "adding-certificates-to-your-project")
+- docs_link_start = "<a href=\"%{docs_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { docs_link_url: docs_link_url }
+- docs_link_end = "</a>".html_safe
+
+-# Hiding behind a feature flag to avoid any changes to this feature's implemention
+-# when the :pages_auto_ssl feature flag is disabled. This check should be removed
+-# once the :pages_auto_ssl feature flag is removed.
+- if Feature.enabled?(:pages_auto_ssl)
+ %p= _("Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml
index e11387ae742..7c0777e5496 100644
--- a/app/views/projects/pages_domains/edit.html.haml
+++ b/app/views/projects/pages_domains/edit.html.haml
@@ -3,6 +3,7 @@
- page_title @domain.domain
%h3.page-title
= @domain.domain
+= render 'projects/pages_domains/helper_text'
%hr.clearfix
%div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml
index c7cefa87c76..e23ccb5d4c6 100644
--- a/app/views/projects/pages_domains/new.html.haml
+++ b/app/views/projects/pages_domains/new.html.haml
@@ -2,6 +2,7 @@
- page_title _('New Pages Domain')
%h3.page-title
= _("New Pages Domain")
+= render 'projects/pages_domains/helper_text'
%hr.clearfix
%div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 8373903443e..cc98ba64f08 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,29 +1,35 @@
- page_title _("Members")
+- can_admin_project_members = can?(current_user, :admin_project_member, @project)
.row.prepend-top-default
.col-lg-12
- %h4
- = _("Project members")
- - if can?(current_user, :admin_project_member, @project)
- %p
- = _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.").html_safe % { project_name: sanitize(@project.name, tags: []) }
- - else
- %p
- = _("Members can be added by project <i>Maintainers</i> or <i>Owners</i>").html_safe
+ - if project_can_be_shared?
+ %h4
+ = _("Project members")
+ - if can_admin_project_members
+ %p= share_project_description(@project)
+ - else
+ %p
+ = _("Members can be added by project <i>Maintainers</i> or <i>Owners</i>").html_safe
+
.light
- - if can?(current_user, :admin_project_member, @project)
- %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
- %li.nav-tab{ role: 'presentation' }
- %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite member")
- - if @project.allowed_to_share_with_group?
+ - if can_admin_project_members && project_can_be_shared?
+ - if !membership_locked? && @project.allowed_to_share_with_group?
+ %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
+ %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite member")
+ %li.nav-tab{ role: 'presentation', class: ('active' if membership_locked?) }
%a.nav-link{ href: '#invite-group-pane', id: 'invite-group-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite group")
- .tab-content.gitlab-tab-content
- .tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_project_member', tab_title: _('Invite member')
- .tab-pane{ id: 'invite-group-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_project_group', tab_title: _('Invite group')
+ .tab-content.gitlab-tab-content
+ .tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
+ = render 'projects/project_members/new_project_member', tab_title: _('Invite member')
+ .tab-pane{ id: 'invite-group-pane', role: 'tabpanel', class: ('active' if membership_locked?) }
+ = render 'projects/project_members/new_project_group', tab_title: _('Invite group')
+ - elsif !membership_locked?
+ .invite-member= render 'projects/project_members/new_project_member', tab_title: _('Invite member')
+ - elsif @project.allowed_to_share_with_group?
+ .invite-group= render 'projects/project_members/new_project_group', tab_title: _('Invite group')
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
.clearfix
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
index d617d85afc2..3644a623d2c 100644
--- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -6,8 +6,8 @@
.card-body
= form_errors(@protected_branch)
.form-group.row
- = f.label :name, class: 'col-md-2 text-right' do
- Branch:
+ .col-md-2.text-right
+ = f.label :name, 'Branch:'
.col-md-10
= render partial: "projects/protected_branches/shared/dropdown", locals: { f: f }
.form-text.text-muted
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
index cbf1938664c..020e6e187a6 100644
--- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -6,8 +6,8 @@
.card-body
= form_errors(@protected_tag)
.form-group.row
- = f.label :name, class: 'col-md-2 text-right' do
- Tag:
+ .col-md-2.text-right
+ = f.label :name, 'Tag:'
.col-md-10.protected-tags-dropdown
= render partial: "projects/protected_tags/shared/dropdown", locals: { f: f }
.form-text.text-muted
diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml
index a4cde53e8c6..9594c9184a2 100644
--- a/app/views/projects/registry/repositories/_tag.html.haml
+++ b/app/views/projects/registry/repositories/_tag.html.haml
@@ -1,7 +1,7 @@
%tr.tag
%td
= escape_once(tag.name)
- = clipboard_button(text: "docker pull #{tag.location}")
+ = clipboard_button(text: "#{tag.location}")
%td
- if tag.revision
%span.has-tooltip{ title: "#{tag.revision}" }
diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
index 9409418bbcc..82c1d57c97e 100644
--- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
@@ -18,22 +18,22 @@
.help-form
.form-group
- = label_tag :display_name, 'Display name', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :display_name, 'Display name', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#display_name', class: 'input-group-text')
.form-group
- = label_tag :description, 'Description', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :description, 'Description', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#description', class: 'input-group-text')
.form-group
- = label_tag nil, 'Command trigger word', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block
+ = label_tag nil, 'Command trigger word', class: 'col-12 col-form-label label-bold'
+ .col-12
%p Fill in the word that works best for your team.
%p
Suggestions:
@@ -42,44 +42,44 @@
%code= @project.full_path
.form-group
- = label_tag :request_url, 'Request URL', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :request_url, 'Request URL', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :request_url, service_trigger_url(subject), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#request_url', class: 'input-group-text')
.form-group
- = label_tag nil, 'Request method', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block POST
+ = label_tag nil, 'Request method', class: 'col-12 col-form-label label-bold'
+ .col-12 POST
.form-group
- = label_tag :response_username, 'Response username', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :response_username, 'Response username', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :response_username, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#response_username', class: 'input-group-text')
.form-group
- = label_tag :response_icon, 'Response icon', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :response_icon, 'Response icon', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#response_icon', class: 'input-group-text')
.form-group
- = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block Yes
+ = label_tag nil, 'Autocomplete', class: 'col-12 col-form-label label-bold'
+ .col-12 Yes
.form-group
- = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-12 col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :autocomplete_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_hint', class: 'input-group-text')
.form-group
- = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_description', class: 'input-group-text')
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml
index 9a7004f89c0..9b7732abc62 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/projects/services/slack_slash_commands/_help.html.haml
@@ -27,8 +27,8 @@
.help-form
.form-group
- = label_tag nil, 'Command', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block
+ = label_tag nil, 'Command', class: 'col-12 col-form-label label-bold'
+ .col-12
%p Fill in the word that works best for your team.
%p
Suggestions:
@@ -37,50 +37,50 @@
%code= @project.full_path
.form-group
- = label_tag :url, 'URL', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :url, 'URL', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :url, service_trigger_url(subject), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#url', class: 'input-group-text')
.form-group
- = label_tag nil, 'Method', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block POST
+ = label_tag nil, 'Method', class: 'col-12 col-form-label label-bold'
+ .col-12 POST
.form-group
- = label_tag :customize_name, 'Customize name', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :customize_name, 'Customize name', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :customize_name, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#customize_name', class: 'input-group-text')
.form-group
- = label_tag nil, 'Customize icon', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block
- = image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36)
+ = label_tag nil, 'Customize icon', class: 'col-12 col-form-label label-bold'
+ .col-12
+ = image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36, class: 'mr-3')
= link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer')
.form-group
- = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.text-block Show this command in the autocomplete list
+ = label_tag nil, 'Autocomplete', class: 'col-12 col-form-label label-bold'
+ .col-12 Show this command in the autocomplete list
.form-group
- = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_description', class: 'input-group-text')
.form-group
- = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_usage_hint', class: 'input-group-text')
.form-group
- = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-12 col-form-label'
- .col-sm-10.col-12.input-group
+ = label_tag :descriptive_label, 'Descriptive label', class: 'col-12 col-form-label label-bold'
+ .col-12.input-group
= text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#descriptive_label', class: 'input-group-text')
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index b38b8e3f686..2d108a1cba5 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -26,6 +26,14 @@
%hr
.form-group
+ = f.fields_for :ci_cd_settings_attributes, @project.ci_cd_settings do |form|
+ = form.label :default_git_depth, _('Git shallow clone'), class: 'label-bold'
+ = form.number_field :default_git_depth, { class: 'form-control', min: 0, max: 1000 }
+ %p.form-text.text-muted
+ = _('The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time.')
+
+ %hr
+ .form-group
= f.label :build_timeout_human_readable, _('Timeout'), class: 'label-bold'
= f.text_field :build_timeout_human_readable, class: 'form-control'
%p.form-text.text-muted
diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml
index 451a79becc3..583fc08f375 100644
--- a/app/views/projects/settings/operations/_error_tracking.html.haml
+++ b/app/views/projects/settings/operations/_error_tracking.html.haml
@@ -2,10 +2,12 @@
- setting = error_tracking_setting
-%section.settings.expanded.no-animate
+%section.settings.no-animate.js-error-tracking-settings
.settings-header
%h4
= _('Error Tracking')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = _('Expand')
%p
= _('To link Sentry to GitLab, enter your Sentry URL and Auth Token.')
= link_to _('More information'), help_page_path('user/project/operations/error_tracking'), target: '_blank', rel: 'noopener noreferrer'
diff --git a/app/views/projects/settings/operations/_external_dashboard.html.haml b/app/views/projects/settings/operations/_external_dashboard.html.haml
index 2fbb9195a04..a124283921d 100644
--- a/app/views/projects/settings/operations/_external_dashboard.html.haml
+++ b/app/views/projects/settings/operations/_external_dashboard.html.haml
@@ -1,2 +1,3 @@
-.js-operation-settings{ data: { external_dashboard: { path: '',
+.js-operation-settings{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
+ external_dashboard: { url: metrics_external_dashboard_url,
help_page_path: help_page_path('user/project/operations/link_to_external_dashboard') } } }
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
index edc2c58a8ed..0a7a155bc12 100644
--- a/app/views/projects/settings/operations/show.html.haml
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -3,6 +3,6 @@
- breadcrumb_title _('Operations Settings')
= render_if_exists 'projects/settings/operations/incidents'
-= render 'projects/settings/operations/error_tracking', expanded: true
+= render 'projects/settings/operations/error_tracking'
= render 'projects/settings/operations/external_dashboard'
= render_if_exists 'projects/settings/operations/tracing'
diff --git a/app/views/projects/settings/repository/_protected_branches.html.haml b/app/views/projects/settings/repository/_protected_branches.html.haml
new file mode 100644
index 00000000000..31630828571
--- /dev/null
+++ b/app/views/projects/settings/repository/_protected_branches.html.haml
@@ -0,0 +1,2 @@
+= render "projects/protected_branches/index"
+= render "projects/protected_tags/index"
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index cb3a035c49e..ff30cc4f6db 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -3,14 +3,17 @@
- @content_class = "limit-container-width" unless fluid_layout
= render "projects/default_branch/show"
+= render_if_exists "projects/push_rules/index"
= render "projects/mirrors/mirror_repos"
-# Protected branches & tags use a lot of nested partials.
-# The shared parts of the views can be found in the `shared` directory.
-# Those are used throughout the actual views. These `shared` views are then
-# reused in EE.
-= render "projects/protected_branches/index"
-= render "projects/protected_tags/index"
+= render "projects/settings/repository/protected_branches"
+
= render @deploy_keys
= render "projects/deploy_tokens/index"
= render "projects/cleanup/show"
+
+= render_if_exists 'shared/promotions/promote_repository_features'
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 458096f9dd6..2e78b0bff3e 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -9,7 +9,7 @@
.nav-text.row-main-content
= s_('TagsPage|Tags give the ability to mark specific points in history as being important')
- .nav-controls.row-fixed-content
+ .nav-controls
= form_tag(filter_tags_path, method: :get) do
= search_field_tag :search, params[:search], { placeholder: s_('TagsPage|Filter by tag name'), id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false }
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 0be62bc5612..59232372150 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -18,7 +18,7 @@
- else
= s_("TagsPage|Can't find HEAD commit for this tag")
- .nav-controls.controls-flex
+ .nav-controls
- if can?(current_user, :push_code, @project)
= link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do
= icon("pencil")
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index e935af23659..4f6c7e1f9a6 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,5 +1,5 @@
- if readme.rich_viewer
- %article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout), ("js-hide-on-navigation" if Feature.enabled?(:vue_file_list, @project))] }
+ %article.file-holder.readme-holder{ id: 'readme', class: [("limited-width-container" unless fluid_layout), ("js-hide-on-navigation" if vue_file_list_enabled?)] }
.js-file-title.file-title
= blob_icon readme.mode, readme.name
= link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index e37fd7624be..065fef606d5 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,3 @@
+- full_title = markdown_field(commit, :full_title)
%span.str-truncated
- = link_to_html commit.redacted_full_title_html, project_commit_path(@project, commit.id), title: commit.redacted_full_title_html, class: 'tree-commit-link'
+ = link_to_html full_title, project_commit_path(@project, commit.id), title: full_title, class: 'tree-commit-link'
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index ec8e5234bd4..ea6349f2f57 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -6,71 +6,74 @@
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
- if on_top_of_branch?
- - addtotree_toggle_attributes = { href: '#', 'data-toggle': 'dropdown', 'data-target': '.add-to-tree-dropdown', 'data-boundary': 'window' }
+ - addtotree_toggle_attributes = { 'data-toggle': 'dropdown', 'data-target': '.add-to-tree-dropdown', 'data-boundary': 'window' }
- else
- addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' }
- %ul.breadcrumb.repo-breadcrumb
- %li.breadcrumb-item
- = link_to project_tree_path(@project, @ref) do
- = @project.path
- - path_breadcrumbs do |title, path|
+ - if vue_file_list_enabled?
+ #js-repo-breadcrumb
+ - else
+ %ul.breadcrumb.repo-breadcrumb
%li.breadcrumb-item
- = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
+ = link_to project_tree_path(@project, @ref) do
+ = @project.path
+ - path_breadcrumbs do |title, path|
+ %li.breadcrumb-item
+ = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
- - if can_collaborate || can_create_mr_from_fork
- %li.breadcrumb-item
- %a.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes }
- = sprite_icon('plus', size: 16, css_class: 'float-left')
- = sprite_icon('arrow-down', size: 16, css_class: 'float-left')
- - if on_top_of_branch?
- .add-to-tree-dropdown
- %ul.dropdown-menu
- - if can_edit_tree?
- %li.dropdown-header
- #{ _('This directory') }
- %li
- = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do
- #{ _('New file') }
- %li
- = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
- #{ _('Upload file') }
- %li
- = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
- #{ _('New directory') }
- - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
- %li
- - continue_params = { to: project_new_blob_path(@project, @id),
- notice: edit_in_new_fork_notice,
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('New file') }
- %li
- - continue_params = { to: request.fullpath,
- notice: edit_in_new_fork_notice + " Try to upload a file again.",
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('Upload file') }
- %li
- - continue_params = { to: request.fullpath,
- notice: edit_in_new_fork_notice + " Try to create a new directory again.",
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
- = link_to fork_path, method: :post do
- #{ _('New directory') }
+ - if can_collaborate || can_create_mr_from_fork
+ %li.breadcrumb-item
+ %button.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes, type: 'button' }
+ = sprite_icon('plus', size: 16, css_class: 'float-left')
+ = sprite_icon('arrow-down', size: 16, css_class: 'float-left')
+ - if on_top_of_branch?
+ .add-to-tree-dropdown
+ %ul.dropdown-menu
+ - if can_edit_tree?
+ %li.dropdown-header
+ #{ _('This directory') }
+ %li
+ = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do
+ #{ _('New file') }
+ %li
+ = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
+ #{ _('Upload file') }
+ %li
+ = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
+ #{ _('New directory') }
+ - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
+ %li
+ - continue_params = { to: project_new_blob_path(@project, @id),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
+ = link_to fork_path, method: :post do
+ #{ _('New file') }
+ %li
+ - continue_params = { to: request.fullpath,
+ notice: edit_in_new_fork_notice + " Try to upload a file again.",
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
+ = link_to fork_path, method: :post do
+ #{ _('Upload file') }
+ %li
+ - continue_params = { to: request.fullpath,
+ notice: edit_in_new_fork_notice + " Try to create a new directory again.",
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
+ = link_to fork_path, method: :post do
+ #{ _('New directory') }
- - if can?(current_user, :push_code, @project)
- %li.divider
- %li.dropdown-header
- #{ _('This repository') }
- %li
- = link_to new_project_branch_path(@project) do
- #{ _('New branch') }
- %li
- = link_to new_project_tag_path(@project) do
- #{ _('New tag') }
+ - if can?(current_user, :push_code, @project)
+ %li.divider
+ %li.dropdown-header
+ #{ _('This repository') }
+ %li
+ = link_to new_project_branch_path(@project) do
+ #{ _('New branch') }
+ %li
+ = link_to new_project_tag_path(@project) do
+ #{ _('New tag') }
.tree-controls
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml
index d5327a2b4cc..dfcd1c6b19f 100644
--- a/app/views/repository_check_mailer/notify.html.haml
+++ b/app/views/repository_check_mailer/notify.html.haml
@@ -6,3 +6,5 @@
%p
= _("You are receiving this message because you are a GitLab administrator for %{url}.") % { url: Gitlab.config.gitlab.url }
+
+= render_if_exists 'repository_check_mailer/email_additional_text'
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index 4af0c6bf84a..db0dcc8adfb 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -13,3 +13,4 @@
- unless params[:snippets].eql? 'true'
= render 'filter'
= button_tag _("Search"), class: "btn btn-success btn-search"
+ = render_if_exists 'search/form_elasticsearch'
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 8ae2807729b..12eb8d7fa81 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,5 +1,6 @@
- if @search_objects.to_a.empty?
= render partial: "search/results/empty"
+ = render_if_exists 'shared/promotions/promote_advanced_search'
- else
.row-content-block
- unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount)
@@ -11,7 +12,7 @@
- elsif @group
- link_to_group = link_to(@group.name, @group)
= _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
-
+ = render_if_exists 'shared/promotions/promote_advanced_search'
.results.prepend-top-10
- if @scope == 'commits'
%ul.content-list.commit-list
@@ -20,7 +21,7 @@
.search-results
- if @scope == 'projects'
.term
- = render 'shared/projects/list', projects: @search_objects, pipeline_status: false
+ = render 'shared/projects/list', { projects: @search_objects, pipeline_status: false }.merge(@display_options)
- else
- locals = { projects: blob_projects(@search_objects) } if %w[blobs wiki_blobs].include?(@scope)
= render partial: "search/results/#{@scope.singularize}", collection: @search_objects, locals: locals
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 796782035f2..1f055cdfa31 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -1,7 +1,7 @@
.search-result-row
%h4
= confidential_icon(issue)
- = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
+ = link_to namespace_project_issue_path(issue.project.namespace.becomes(Namespace), issue.project, issue) do
%span.term.str-truncated= issue.title
- if issue.closed?
%span.badge.badge-danger.prepend-left-5= _("Closed")
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index f0e0af11f27..074bb9bce8d 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -1,6 +1,6 @@
.search-result-row
%h4
- = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
+ = link_to namespace_project_merge_request_path(merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request) do
%span.term.str-truncated= merge_request.title
- if merge_request.merged?
%span.badge.badge-primary.prepend-left-5= _("Merged")
diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml
index 2daa96e34d1..3201f1a7815 100644
--- a/app/views/search/results/_milestone.html.haml
+++ b/app/views/search/results/_milestone.html.haml
@@ -1,6 +1,6 @@
.search-result-row
%h4
- = link_to [milestone.project.namespace.becomes(Namespace), milestone.project, milestone] do
+ = link_to namespace_project_milestone_path(milestone.project.namespace.becomes(Namespace), milestone.project, milestone) do
%span.term.str-truncated= milestone.title
- if milestone.description.present?
diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml
index ca392e1adfc..22fcfcda297 100644
--- a/app/views/sent_notifications/unsubscribe.html.haml
+++ b/app/views/sent_notifications/unsubscribe.html.haml
@@ -1,6 +1,6 @@
- noteable = @sent_notification.noteable
- noteable_type = @sent_notification.noteable_type.titleize.downcase
-- noteable_text = %(#{noteable.title} (#{noteable.to_reference}))
+- noteable_text = show_unsubscribe_title?(noteable) ? %(#{noteable.title} (#{noteable.to_reference})) : %(#{noteable.to_reference})
- page_title _("Unsubscribe"), noteable_text, noteable_type.pluralize, @sent_notification.project.full_name
%h3.page-title
diff --git a/app/views/shared/_delete_label_modal.html.haml b/app/views/shared/_delete_label_modal.html.haml
index 6bd8cadd7d9..f37dd2cdf02 100644
--- a/app/views/shared/_delete_label_modal.html.haml
+++ b/app/views/shared/_delete_label_modal.html.haml
@@ -9,7 +9,7 @@
.modal-body
%p
%strong= label.name
- %span will be permanently deleted from #{label.subject.name}. This cannot be undone.
+ %span will be permanently deleted from #{label.subject_name}. This cannot be undone.
.modal-footer
%a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
diff --git a/app/views/shared/_email_with_badge.html.haml b/app/views/shared/_email_with_badge.html.haml
index ad863b1967d..294fe74a5ca 100644
--- a/app/views/shared/_email_with_badge.html.haml
+++ b/app/views/shared/_email_with_badge.html.haml
@@ -1,6 +1,6 @@
- css_classes = %w(badge badge-verification-status)
- css_classes << (verified ? 'verified': 'unverified')
-- text = verified ? 'Verified' : 'Unverified'
+- text = verified ? _('Verified') : _('Unverified')
.email-badge
.email-badge-email= email
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 3ee713cf499..d0f9374e832 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -1,11 +1,26 @@
- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
+- import_url = Gitlab::UrlSanitizer.new(f.object.import_url)
-.form-group.import-url-data
- = f.label :import_url, class: 'label-bold' do
- %span
- = _('Git repository URL')
+.import-url-data
+ .form-group
+ = f.label :import_url, class: 'label-bold' do
+ %span
+ = _('Git repository URL')
+ = f.text_field :import_url, value: import_url.sanitized_url,
+ autocomplete: 'off', class: 'form-control', placeholder: 'https://gitlab.company.com/group/project.git', required: true
- = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', required: true
+ .row
+ .form-group.col-md-6
+ = f.label :import_url_user, class: 'label-bold' do
+ %span
+ = _('Username (optional)')
+ = f.text_field :import_url_user, value: import_url.user, class: 'form-control', required: false, autocomplete: 'new-password'
+
+ .form-group.col-md-6
+ = f.label :import_url_password, class: 'label-bold' do
+ %span
+ = _('Password (optional)')
+ = f.password_field :import_url_password, class: 'form-control', required: false, autocomplete: 'new-password'
.info-well.prepend-top-20
.well-segment
@@ -13,7 +28,7 @@
%li
= _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe
%li
- = _('If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.').html_safe
+ = _('If your HTTP repository is not publicly accessible, add your credentials.')
%li
= import_will_timeout_message(ci_cd_only)
%li
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 2b4a24a001f..c4b7ef481fd 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -30,7 +30,7 @@
= sprite_icon('ellipsis_v')
.dropdown-menu.dropdown-open-left
%ul
- - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
+ - if label.project_label? && label.project.group && can?(current_user, :admin_label, label.project.group)
%li
%button.js-promote-project-label-button.btn.btn-transparent.btn-action{ disabled: true, type: 'button',
data: { url: promote_project_label_path(label.project, label),
diff --git a/app/views/shared/_old_visibility_level.html.haml b/app/views/shared/_old_visibility_level.html.haml
index fd576e4fbea..e8f3d888cce 100644
--- a/app/views/shared/_old_visibility_level.html.haml
+++ b/app/views/shared/_old_visibility_level.html.haml
@@ -1,6 +1,6 @@
.form-group.row
.col-sm-2.col-form-label
= _('Visibility level')
- = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ = link_to icon('question-circle'), help_page_path("public_access/public_access"), target: '_blank'
.col-sm-10
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_visibility_level, form_model: form_model, with_label: with_label
diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index 311dc69d213..c50826a7cda 100644
--- a/app/views/shared/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -32,7 +32,7 @@
%span.dropdown-toggle-text
{{ labelDropdownTitle }}
= icon('chevron-down')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
= render partial: "shared/issuable/label_page_default"
- if can?(current_user, :admin_label, current_board_parent)
= render partial: "shared/issuable/label_page_create", locals: { show_add_list: true }
diff --git a/app/views/shared/icons/_emoji_slightly_smiling_face.svg b/app/views/shared/icons/_emoji_slightly_smiling_face.svg
deleted file mode 100644
index 56dbad91554..00000000000
--- a/app/views/shared/icons/_emoji_slightly_smiling_face.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369.721.721 0 0 1 .568.047.715.715 0 0 1 .37.445c.195.625.556 1.131 1.084 1.518A2.93 2.93 0 0 0 9 12.75a2.93 2.93 0 0 0 1.775-.58 2.913 2.913 0 0 0 1.084-1.518.711.711 0 0 1 .375-.445.737.737 0 0 1 .575-.047c.195.063.34.186.433.37.094.183.11.372.047.568zM7.5 6c0 .414-.146.768-.44 1.06-.292.294-.646.44-1.06.44-.414 0-.768-.146-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06.292-.294.646-.44 1.06-.44.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06-.292.294-.646.44-1.06.44-.414 0-.768-.146-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06.292-.294.646-.44 1.06-.44.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></svg>
diff --git a/app/views/shared/icons/_emoji_smile.svg b/app/views/shared/icons/_emoji_smile.svg
deleted file mode 100644
index ce645fee46f..00000000000
--- a/app/views/shared/icons/_emoji_smile.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568zM14 6.37c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm-6.5 0c0 .398-.04.755-.513.755-.473 0-.498-.272-1.237-.272-.74 0-.74.215-1.165.215-.425 0-.585-.3-.585-.698 0-.397.17-.736.513-1.017.341-.281.754-.422 1.237-.422.483 0 .896.14 1.237.422.342.28.513.62.513 1.017zm9 2.63a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6A7.29 7.29 0 0 0 9 16.5a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39A7.29 7.29 0 0 0 16.5 9zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="evenodd"/></svg>
diff --git a/app/views/shared/icons/_emoji_smiley.svg b/app/views/shared/icons/_emoji_smiley.svg
deleted file mode 100644
index ddfae50e566..00000000000
--- a/app/views/shared/icons/_emoji_smiley.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M13.29 11.098a4.328 4.328 0 0 1-1.618 2.285c-.79.578-1.68.867-2.672.867-.992 0-1.883-.29-2.672-.867a4.328 4.328 0 0 1-1.617-2.285.721.721 0 0 1 .047-.569.715.715 0 0 1 .445-.369c.195-.062 7.41-.062 7.606 0 .195.063.34.186.433.37.094.183.11.372.047.568h.001zM7.5 6c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 6 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 4.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 6 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm6 0c0 .414-.146.768-.44 1.06A1.44 1.44 0 0 1 12 7.5a1.44 1.44 0 0 1-1.06-.44A1.445 1.445 0 0 1 10.5 6c0-.414.146-.768.44-1.06A1.44 1.44 0 0 1 12 4.5c.414 0 .768.146 1.06.44.294.292.44.646.44 1.06zm3 3a7.29 7.29 0 0 0-.598-2.912 7.574 7.574 0 0 0-1.6-2.39 7.574 7.574 0 0 0-2.39-1.6A7.29 7.29 0 0 0 9 1.5a7.29 7.29 0 0 0-2.912.598 7.574 7.574 0 0 0-2.39 1.6 7.574 7.574 0 0 0-1.6 2.39A7.29 7.29 0 0 0 1.5 9c0 1.016.2 1.986.598 2.912a7.574 7.574 0 0 0 1.6 2.39 7.574 7.574 0 0 0 2.39 1.6c.92.397 1.91.6 2.912.598a7.29 7.29 0 0 0 2.912-.598 7.574 7.574 0 0 0 2.39-1.6 7.574 7.574 0 0 0 1.6-2.39c.397-.92.6-1.91.598-2.912zM18 9a8.804 8.804 0 0 1-1.207 4.518 8.96 8.96 0 0 1-3.275 3.275A8.804 8.804 0 0 1 9 18a8.804 8.804 0 0 1-4.518-1.207 8.96 8.96 0 0 1-3.275-3.275A8.804 8.804 0 0 1 0 9c0-1.633.402-3.139 1.207-4.518a8.96 8.96 0 0 1 3.275-3.275A8.804 8.804 0 0 1 9 0c1.633 0 3.139.402 4.518 1.207a8.96 8.96 0 0 1 3.275 3.275A8.804 8.804 0 0 1 18 9z" fill-rule="nonzero"/></svg>
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index f2c0c77a583..483652852b6 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -25,7 +25,7 @@
%span.dropdown-toggle-text{ class: ("is-default" if apply_is_default_styles) }
= multi_label_name(selected, label_name)
= icon('chevron-down')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
= render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create }
- if show_create && project && can?(current_user, :admin_label, project)
= render partial: "shared/issuable/label_page_create"
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
index d173e3c0192..a0d3bc64f1f 100644
--- a/app/views/shared/issuable/_label_page_create.html.haml
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -9,9 +9,7 @@
.dropdown-labels-error.js-label-error
%input#new_label_name.default-dropdown-input{ type: "text", placeholder: _('Name new label') }
.suggest-colors.suggest-colors-dropdown
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp
+ = render_suggested_colors
.dropdown-label-color-input
.dropdown-label-color-preview.js-dropdown-label-color-preview
%input#new_label_color.default-dropdown-input{ type: "text", placeholder: _('Assign custom color like #FF0000') }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 63557c882f4..3a5adb34ad1 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -118,7 +118,7 @@
%span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
= multi_label_name(selected_labels, "Labels")
= icon('chevron-down', 'aria-hidden': 'true')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height
= render partial: "shared/issuable/label_page_default"
- if issuable_sidebar.dig(:current_user, :can_admin_label)
= render partial: "shared/issuable/label_page_create"
@@ -158,7 +158,7 @@
%button.btn.btn-default.btn-block.js-sidebar-dropdown-toggle.js-move-issue{ type: 'button',
data: { toggle: 'dropdown', display: 'static' } }
= _('Move issue')
- .dropdown-menu.dropdown-menu-selectable
+ .dropdown-menu.dropdown-menu-selectable.dropdown-extended-height
= dropdown_title(_('Move issue'))
= dropdown_filter(_('Search project'), search_id: 'sidebar-move-issue-dropdown-search')
= dropdown_content
diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
index 967f31c8325..1dd97bc4ed1 100644
--- a/app/views/shared/issuable/_sort_dropdown.html.haml
+++ b/app/views/shared/issuable/_sort_dropdown.html.haml
@@ -10,12 +10,13 @@
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
%li
- = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title)
- = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sort_title)
- = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sort_title)
- = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone), sort_title)
- = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
- = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
- = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
+ = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title)
+ = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sort_title)
+ = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sort_title)
+ = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone), sort_title)
+ = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
+ = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
+ = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
+ = sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues && Feature.enabled?(:manual_sorting)
= render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
= issuable_sort_direction_button(sort_value)
diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml
index 743ee1435e8..78ff225daad 100644
--- a/app/views/shared/labels/_form.html.haml
+++ b/app/views/shared/labels/_form.html.haml
@@ -2,17 +2,20 @@
= form_errors(@label)
.form-group.row
- = f.label :title, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :title
.col-sm-10
= f.text_field :title, class: "form-control js-label-title qa-label-title", required: true, autofocus: true
= render_if_exists 'shared/labels/create_label_help_text'
.form-group.row
- = f.label :description, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :description
.col-sm-10
= f.text_field :description, class: "form-control js-quick-submit qa-label-description"
.form-group.row
- = f.label :color, "Background color", class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :color, "Background color"
.col-sm-10
.input-group
.input-group-prepend
@@ -22,12 +25,7 @@
Choose any color.
%br
Or you can choose one of the suggested colors below
-
- .suggest-colors
- - suggested_colors.each do |color|
- = link_to '#', style: "background-color: #{color}", data: { color: color } do
- &nbsp;
-
+ = render_suggested_colors
.form-actions
- if @label.persisted?
= f.submit 'Save changes', class: 'btn btn-success js-save-button'
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index 9ec76d82d18..e83ca5eaab8 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -2,9 +2,12 @@
- group = group_link.group
- can_admin_member = can?(current_user, :admin_project_member, @project)
- dom_id = "group_member_#{group_link.id}"
-%li.member.group_member{ id: dom_id }
- %span.list-item-name
- = group_icon(group, class: "avatar s40", alt: '')
+
+-# Note this is just for groups. For individual members please see shared/members/_member
+
+%li.member.group_member.py-2.px-3.d-flex.flex-column.flex-md-row{ id: dom_id }
+ %span.list-item-name.mb-2.m-md-0
+ = group_icon(group, class: "avatar s40 flex-shrink-0 flex-grow-0", alt: '')
.user-info
= link_to group.full_name, group_path(group), class: 'member'
.cgray
@@ -13,10 +16,10 @@
·
%span{ class: ('text-warning' if group_link.expires_soon?) }
= _("Expires in %{expires_at}").html_safe % { expires_at: distance_of_time_in_words_to_now(group_link.expires_at) }
- .controls.member-controls
- = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'js-edit-member-form form-group row append-right-5' do
+ .controls.member-controls.align-items-center
+ = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'js-edit-member-form form-group d-sm-flex' do
= hidden_field_tag "group_link[group_access]", group_link.group_access
- .member-form-control.dropdown.append-right-5
+ .member-form-control.dropdown.mr-sm-2.d-sm-inline-block
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
disabled: !can_admin_member,
data: { toggle: "dropdown", field_name: "group_link[group_access]" } }
@@ -32,14 +35,14 @@
= link_to role, "javascript:void(0)",
class: ("is-active" if group_link.group_access == role_id),
data: { id: role_id, el_id: dom_id }
- .prepend-left-5.clearable-input.member-form-control
+ .clearable-input.member-form-control.d-sm-inline-block
= text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: _('Expiration date'), id: "member_expires_at_#{group.id}", disabled: !can_admin_member
%i.clear-icon.js-clear-input
- if can_admin_member
= link_to project_group_link_path(@project, group_link),
method: :delete,
data: { confirm: _("Are you sure you want to remove %{group_name}?") % { group_name: group.name } },
- class: 'btn btn-remove prepend-left-10' do
+ class: 'btn btn-remove m-0 ml-sm-2 align-self-center' do
%span.d-block.d-sm-none
= _("Delete")
= icon('trash', class: 'd-none d-sm-block')
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 2db1f67a793..331283f7eec 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -4,11 +4,14 @@
- member = local_assigns.fetch(:member)
- user = local_assigns.fetch(:user, member.user)
- source = member.source
+- override = member.try(:override)
-%li.member{ class: dom_class(member), id: dom_id(member) }
- %span.list-item-name
+-# Note this is just for individual members. For groups please see shared/members/_group
+
+%li.member.py-2.px-3.d-flex.flex-column{ class: [dom_class(member), ("is-overridden" if override), ("flex-md-row" unless force_mobile_view)], id: dom_id(member) }
+ %span.list-item-name.mb-2.m-md-0
- if user
- = image_tag avatar_icon_for_user(user, 40), class: "avatar s40", alt: ''
+ = image_tag avatar_icon_for_user(user, 40), class: "avatar s40 flex-shrink-0 flex-grow-0", alt: ''
.user-info
= link_to user.name, user_path(user), class: 'member js-user-link', data: { user_id: user.id }
= user_status(user)
@@ -42,7 +45,7 @@
= _("Expires in %{expires_at}").html_safe % { expires_at: distance_of_time_in_words_to_now(member.expires_at) }
- else
- = image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40", alt: ''
+ = image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40 flex-shrink-0 flex-grow-0", alt: ''
.user-info
.member= member.invite_email
.cgray
@@ -53,20 +56,22 @@
= time_ago_with_tooltip(member.created_at)
- if show_roles
- current_resource = @project || @group
- .controls.member-controls
+ .controls.member-controls.align-items-center
+ = render_if_exists 'shared/members/ee/ldap_tag', can_override: member.can_override?
- if show_controls && member.source == current_resource
- if member.can_resend_invite?
= link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
method: :post,
- class: 'btn btn-default prepend-left-10 d-none d-sm-block',
+ class: 'btn btn-default align-self-center mr-sm-2',
title: _('Resend invite')
- if user != current_user && member.can_update?
- = form_for member, remote: true, html: { class: 'js-edit-member-form form-group row append-right-5' } do |f|
+ = form_for member, remote: true, html: { class: "js-edit-member-form form-group #{'d-sm-flex' unless force_mobile_view}" } do |f|
= f.hidden_field :access_level
- .member-form-control.dropdown.append-right-5
+ .member-form-control.dropdown{ class: [("mr-sm-2 d-sm-inline-block" unless force_mobile_view)] }
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
+ disabled: member.can_override? && !override,
data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } }
%span.dropdown-toggle-text
= member.human_access
@@ -80,20 +85,25 @@
= link_to role, "javascript:void(0)",
class: ("is-active" if member.access_level == role_id),
data: { id: role_id, el_id: dom_id(member) }
- .prepend-left-5.clearable-input.member-form-control
+ = render_if_exists 'shared/members/ee/revert_ldap_group_sync_option',
+ group: @group,
+ member: member,
+ can_override: member.can_override?
+ .clearable-input.member-form-control{ class: [("d-sm-inline-block" unless force_mobile_view)] }
= f.text_field :expires_at,
+ disabled: member.can_override? && !override,
class: 'form-control js-access-expiration-date js-member-update-control',
placeholder: _('Expiration date'),
id: "member_expires_at_#{member.id}",
data: { el_id: dom_id(member) }
%i.clear-icon.js-clear-input
- else
- %span.member-access-text= member.human_access
+ %span.member-access-text.user-access-role= member.human_access
- if member.can_approve?
= link_to polymorphic_path([:approve_access_request, member]),
method: :post,
- class: 'btn btn-success prepend-left-10',
+ class: "btn btn-success align-self-center m-0 mb-2 #{'mb-sm-0 ml-sm-2' unless force_mobile_view}",
title: _('Grant access') do
%span{ class: ('d-block d-sm-none' unless force_mobile_view) }
= _('Grant access')
@@ -105,16 +115,19 @@
= link_to icon('sign-out', text: _('Leave')), polymorphic_path([:leave, member.source, :members]),
method: :delete,
data: { confirm: leave_confirmation_message(member.source) },
- class: 'btn btn-remove prepend-left-10'
+ class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}"
- else
= link_to member,
method: :delete,
data: { confirm: remove_member_message(member) },
- class: 'btn btn-remove prepend-left-10',
+ class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}",
title: remove_member_title(member) do
%span{ class: ('d-block d-sm-none' unless force_mobile_view) }
= _("Delete")
- unless force_mobile_view
= icon('trash', class: 'd-none d-sm-block')
+ = render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :edit, can_override: member.can_override?
- else
- %span.member-access-text= member.human_access
+ %span.member-access-text.user-access-role= member.human_access
+
+= render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :confirm, can_override: member.can_override?
diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index 46f3f8428f1..fae7d6526e8 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -28,8 +28,9 @@
or
%button.attach-new-file.markdown-selector{ type: 'button' }= _("attach a new file")
- %button.markdown-selector.button-attach-file{ type: 'button', tabindex: '-1' }
+ %button.markdown-selector.button-attach-file.btn-link{ type: 'button', tabindex: '-1' }
= icon('file-image-o', class: 'toolbar-button-icon')
- = _("Attach a file")
+ %span.text-attach-file<>
+ = _("Attach a file")
%button.btn.btn-default.btn-sm.hide.button-cancel-uploading-files{ type: 'button' }= _("Cancel")
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 6fec435cc87..5c9dd72418e 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -31,7 +31,7 @@
.note-header
.note-header-info
%a{ href: user_path(note.author) }
- %span.note-header-author-name
+ %span.note-header-author-name.bold
= sanitize(note.author.name)
= user_status(note.author)
%span.note-headline-light
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index 2ece7b7f701..749aa258af6 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -1,24 +1,26 @@
- btn_class = local_assigns.fetch(:btn_class, nil)
- if notification_setting
- .js-notification-dropdown.notification-dropdown.home-panel-action-button.dropdown.inline
+ .js-notification-dropdown.notification-dropdown.mr-md-2.home-panel-action-button.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
= hidden_setting_source_input(notification_setting)
= f.hidden_field :level, class: "notification_setting_level"
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon('caret-down')
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", class: "#{btn_class}", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
- = icon("bell", class: "js-notification-loading")
- = notification_title(notification_setting.level)
- = icon("caret-down")
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ .float-left
+ = icon("bell", class: "js-notification-loading")
+ = notification_title(notification_setting.level)
+ .float-right
+ = icon("caret-down")
= render "shared/notifications/notification_dropdown", notification_setting: notification_setting
diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml
index af8ab992f0e..052e6da5bae 100644
--- a/app/views/shared/notifications/_new_button.html.haml
+++ b/app/views/shared/notifications/_new_button.html.haml
@@ -16,7 +16,7 @@
= sprite_icon("arrow-down", css_class: "icon mr-0")
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting - #{notification_title(notification_setting.level)}", class: "#{btn_class}", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden
= sprite_icon("arrow-down", css_class: "icon")
diff --git a/app/views/shared/notifications/_notification_dropdown.html.haml b/app/views/shared/notifications/_notification_dropdown.html.haml
index 85ad74f9a39..a6ef2d51171 100644
--- a/app/views/shared/notifications/_notification_dropdown.html.haml
+++ b/app/views/shared/notifications/_notification_dropdown.html.haml
@@ -8,5 +8,5 @@
%li.divider
%li
%a.update-notification{ href: "#", role: "button", class: ("is-active" if notification_setting.custom?), data: { toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), notification_level: "custom", notification_title: "Custom" } }
- %strong.dropdown-menu-inner-title Custom
+ %strong.dropdown-menu-inner-title= s_('NotificationSetting|Custom')
%span.dropdown-menu-inner-content= notification_description("custom")
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 6f2ddc5bdba..2d2382e469a 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -7,7 +7,8 @@
= form_errors(@snippet)
.form-group.row
- = f.label :title, class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :title
.col-sm-10
= f.text_field :title, class: 'form-control qa-snippet-title', required: true, autofocus: true
@@ -17,7 +18,8 @@
.file-editor
.form-group.row
- = f.label :file_name, "File", class: 'col-form-label col-sm-2'
+ .col-sm-2.col-form-label
+ = f.label :file_name, "File"
.col-sm-10
.file-holder.snippet
.js-file-title.file-title
diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml
index 01b95145937..6e20890a47f 100644
--- a/app/views/snippets/notes/_actions.html.haml
+++ b/app/views/snippets/notes/_actions.html.haml
@@ -3,9 +3,9 @@
.note-actions-item
= link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
= icon('spinner spin')
- %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
- %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
- %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+ %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
+ %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
+ %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
- if note_editable
.note-actions-item
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 6dc61088e65..a71bfd624e4 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -9,7 +9,7 @@
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
.user-profile
- .cover-block.user-cover-block.top-area
+ .cover-block.user-cover-block
.cover-controls
- if @user == current_user
= link_to profile_path, class: 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index e4e85de93da..fd0cc5fb24e 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1,6 +1,8 @@
---
- auto_devops:auto_devops_disable
+- auto_merge:auto_merge_process
+
- cronjob:admin_email
- cronjob:expire_build_artifacts
- cronjob:gitlab_usage_ping
diff --git a/app/workers/auto_merge_process_worker.rb b/app/workers/auto_merge_process_worker.rb
new file mode 100644
index 00000000000..cd81cdbc60c
--- /dev/null
+++ b/app/workers/auto_merge_process_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class AutoMergeProcessWorker
+ include ApplicationWorker
+
+ queue_namespace :auto_merge
+
+ def perform(merge_request_id)
+ MergeRequest.find_by_id(merge_request_id).try do |merge_request|
+ AutoMergeService.new(merge_request.project, merge_request.merge_user)
+ .process(merge_request)
+ end
+ end
+end
diff --git a/app/workers/pages_domain_verification_cron_worker.rb b/app/workers/pages_domain_verification_cron_worker.rb
index 92d62a15aee..60703c83e9e 100644
--- a/app/workers/pages_domain_verification_cron_worker.rb
+++ b/app/workers/pages_domain_verification_cron_worker.rb
@@ -5,6 +5,8 @@ class PagesDomainVerificationCronWorker
include CronjobQueue
def perform
+ return if Gitlab::Database.read_only?
+
PagesDomain.needs_verification.find_each do |domain|
PagesDomainVerificationWorker.perform_async(domain.id)
end
diff --git a/app/workers/pages_domain_verification_worker.rb b/app/workers/pages_domain_verification_worker.rb
index b3319ff5a13..7817b2ee5fc 100644
--- a/app/workers/pages_domain_verification_worker.rb
+++ b/app/workers/pages_domain_verification_worker.rb
@@ -5,6 +5,8 @@ class PagesDomainVerificationWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(domain_id)
+ return if Gitlab::Database.read_only?
+
domain = PagesDomain.find_by(id: domain_id)
return unless domain
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index 8a9ee7808e4..9410fd1a786 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -3,47 +3,12 @@
class PipelineScheduleWorker
include ApplicationWorker
include CronjobQueue
- include ::Gitlab::ExclusiveLeaseHelpers
- EXCLUSIVE_LOCK_KEY = 'pipeline_schedules:run:lock'
- LOCK_TIMEOUT = 50.minutes
-
- # rubocop: disable CodeReuse/ActiveRecord
def perform
- in_lock(EXCLUSIVE_LOCK_KEY, ttl: LOCK_TIMEOUT, retries: 1) do
- Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now)
- .preload(:owner, :project).find_each do |schedule|
-
- schedule.schedule_next_run!
-
- Ci::CreatePipelineService.new(schedule.project,
- schedule.owner,
- ref: schedule.ref)
- .execute!(:schedule, ignore_skip_ci: true, save_on_errors: true, schedule: schedule)
- rescue => e
- error(schedule, e)
+ Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules|
+ schedules.each do |schedule|
+ Ci::PipelineScheduleService.new(schedule.project, schedule.owner).execute(schedule)
end
end
end
- # rubocop: enable CodeReuse/ActiveRecord
-
- private
-
- def error(schedule, error)
- failed_creation_counter.increment
-
- Rails.logger.error "Failed to create a scheduled pipeline. " \
- "schedule_id: #{schedule.id} message: #{error.message}"
-
- Gitlab::Sentry
- .track_exception(error,
- issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
- extra: { schedule_id: schedule.id })
- end
-
- def failed_creation_counter
- @failed_creation_counter ||=
- Gitlab::Metrics.counter(:pipeline_schedule_creation_failed_total,
- "Counter of failed attempts of pipeline schedule creation")
- end
end
diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb
index 4f349ed922c..666331e6cd4 100644
--- a/app/workers/pipeline_success_worker.rb
+++ b/app/workers/pipeline_success_worker.rb
@@ -6,13 +6,7 @@ class PipelineSuccessWorker
queue_namespace :pipeline_processing
- # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id)
- Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
- MergeRequests::MergeWhenPipelineSucceedsService
- .new(pipeline.project, nil)
- .trigger(pipeline)
- end
+ # no-op
end
- # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 9a9c0c9d803..3f1639ec2ed 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -74,6 +74,8 @@ class PostReceive
def process_wiki_changes(post_received)
post_received.project.touch(:last_activity_at, :last_repository_updated_at)
+ post_received.project.wiki.repository.expire_statistics_caches
+ ProjectCacheWorker.perform_async(post_received.project.id, [], [:wiki_size])
end
def log(message)
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index b2e0701008a..4e8ea903139 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -16,10 +16,12 @@ class ProjectCacheWorker
def perform(project_id, files = [], statistics = [])
project = Project.find_by(id: project_id)
- return unless project && project.repository.exists?
+ return unless project
update_statistics(project, statistics)
+ return unless project.repository.exists?
+
project.repository.refresh_method_caches(files.map(&:to_sym))
project.cleanup
diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb
index f72331c003a..43e0b9db22f 100644
--- a/app/workers/run_pipeline_schedule_worker.rb
+++ b/app/workers/run_pipeline_schedule_worker.rb
@@ -21,6 +21,30 @@ class RunPipelineScheduleWorker
Ci::CreatePipelineService.new(schedule.project,
user,
ref: schedule.ref)
- .execute(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: schedule)
+ .execute!(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: schedule)
+ rescue Ci::CreatePipelineService::CreateError
+ # no-op. This is a user operation error such as corrupted .gitlab-ci.yml.
+ rescue => e
+ error(schedule, e)
+ end
+
+ private
+
+ def error(schedule, error)
+ failed_creation_counter.increment
+
+ Rails.logger.error "Failed to create a scheduled pipeline. " \
+ "schedule_id: #{schedule.id} message: #{error.message}"
+
+ Gitlab::Sentry
+ .track_exception(error,
+ issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
+ extra: { schedule_id: schedule.id })
+ end
+
+ def failed_creation_counter
+ @failed_creation_counter ||=
+ Gitlab::Metrics.counter(:pipeline_schedule_creation_failed_total,
+ "Counter of failed attempts of pipeline schedule creation")
end
end
diff --git a/app/workers/todos_destroyer/confidential_issue_worker.rb b/app/workers/todos_destroyer/confidential_issue_worker.rb
index 481fde8c83d..240a5f98ad5 100644
--- a/app/workers/todos_destroyer/confidential_issue_worker.rb
+++ b/app/workers/todos_destroyer/confidential_issue_worker.rb
@@ -5,8 +5,8 @@ module TodosDestroyer
include ApplicationWorker
include TodosDestroyerQueue
- def perform(issue_id)
- ::Todos::Destroy::ConfidentialIssueService.new(issue_id).execute
+ def perform(issue_id = nil, project_id = nil)
+ ::Todos::Destroy::ConfidentialIssueService.new(issue_id: issue_id, project_id: project_id).execute
end
end
end
diff --git a/babel.config.js b/babel.config.js
index df30892731d..05554e8763e 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -39,7 +39,7 @@ if (BABEL_ENV === 'karma' || BABEL_ENV === 'coverage') {
}
// Jest is running in node environment, so we need additional plugins
-const isJest = !!process.env.JEST_WORKER_ID;
+const isJest = Boolean(process.env.JEST_WORKER_ID);
if (isJest) {
plugins.push('@babel/plugin-transform-modules-commonjs');
/*
diff --git a/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml b/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml
new file mode 100644
index 00000000000..1297e9712fa
--- /dev/null
+++ b/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml
@@ -0,0 +1,5 @@
+---
+title: "Added code differnces from EE in file 'app/assets/javascripts/pages/projects/project.js' to CE"
+merge_request: 29271
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml b/changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml
new file mode 100644
index 00000000000..4c85d4f9acb
--- /dev/null
+++ b/changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml
@@ -0,0 +1,5 @@
+---
+title: Added reference, web_path, and relative_position fields to GraphQL Issue
+merge_request: 28998
+author:
+type: changed
diff --git a/changelogs/unreleased/11105-fix-cs-with-proxy.yml b/changelogs/unreleased/11105-fix-cs-with-proxy.yml
new file mode 100644
index 00000000000..ee32427d20e
--- /dev/null
+++ b/changelogs/unreleased/11105-fix-cs-with-proxy.yml
@@ -0,0 +1,5 @@
+---
+title: Fix proxy support in Container Scanning
+merge_request: 27246
+author:
+type: fixed
diff --git a/changelogs/unreleased/11204-turn-off-mask-by-default.yml b/changelogs/unreleased/11204-turn-off-mask-by-default.yml
new file mode 100644
index 00000000000..5c554e04d45
--- /dev/null
+++ b/changelogs/unreleased/11204-turn-off-mask-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Default masked to false for new variables
+merge_request: 28186
+author:
+type: changed
diff --git a/changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml b/changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml
new file mode 100644
index 00000000000..6570cb3e2a3
--- /dev/null
+++ b/changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml
@@ -0,0 +1,5 @@
+---
+title: Remove support for using Geo with an installation from source
+merge_request: 28737
+author:
+type: other
diff --git a/changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml b/changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml
new file mode 100644
index 00000000000..e954b97f05b
--- /dev/null
+++ b/changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml
@@ -0,0 +1,5 @@
+---
+title: Remove `docker pull` prefix when copying a tag from the registry
+merge_request: 28757
+author: Benedikt Franke
+type: changed
diff --git a/changelogs/unreleased/28996-create-mvc-ui-in-haml.yml b/changelogs/unreleased/28996-create-mvc-ui-in-haml.yml
new file mode 100644
index 00000000000..9c6897babb4
--- /dev/null
+++ b/changelogs/unreleased/28996-create-mvc-ui-in-haml.yml
@@ -0,0 +1,5 @@
+---
+title: Add auto SSL toggle option to Pages domain settings page
+merge_request: 26438
+author:
+type: added
diff --git a/changelogs/unreleased/30138-display-cycle-analytics-issue.yml b/changelogs/unreleased/30138-display-cycle-analytics-issue.yml
new file mode 100644
index 00000000000..c7faa0480bd
--- /dev/null
+++ b/changelogs/unreleased/30138-display-cycle-analytics-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Show data on Cycle Analytics page when value is less than a second
+merge_request: 28507
+author:
+type: fixed
diff --git a/changelogs/unreleased/33064-add-labels-to-note-event-payload.yml b/changelogs/unreleased/33064-add-labels-to-note-event-payload.yml
new file mode 100644
index 00000000000..e0a6253e1ef
--- /dev/null
+++ b/changelogs/unreleased/33064-add-labels-to-note-event-payload.yml
@@ -0,0 +1,5 @@
+---
+title: Add labels to note event payload
+merge_request: 29384
+author: Sujay Patel
+type: added
diff --git a/changelogs/unreleased/37495.yml b/changelogs/unreleased/37495.yml
new file mode 100644
index 00000000000..f6d421fc45b
--- /dev/null
+++ b/changelogs/unreleased/37495.yml
@@ -0,0 +1,5 @@
+---
+title: Add documentation links for confidental and locked discussions
+merge_request: 29073
+author:
+type: changed
diff --git a/changelogs/unreleased/38561-border-radii.yml b/changelogs/unreleased/38561-border-radii.yml
new file mode 100644
index 00000000000..510af18d651
--- /dev/null
+++ b/changelogs/unreleased/38561-border-radii.yml
@@ -0,0 +1,5 @@
+---
+title: Fix border radii on diff files and repo files
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/45687-web-ide-empty-state.yml b/changelogs/unreleased/45687-web-ide-empty-state.yml
new file mode 100644
index 00000000000..9ef148275ab
--- /dev/null
+++ b/changelogs/unreleased/45687-web-ide-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Empty project state for Web IDE
+merge_request: 26556
+author:
+type: added
diff --git a/changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml b/changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml
new file mode 100644
index 00000000000..21dc170f1ca
--- /dev/null
+++ b/changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Position is off when visiting files with anchors
+merge_request: 28913
+author:
+type: fixed
diff --git a/changelogs/unreleased/49041-issue-board-input-height.yml b/changelogs/unreleased/49041-issue-board-input-height.yml
new file mode 100644
index 00000000000..de3fbb2ee11
--- /dev/null
+++ b/changelogs/unreleased/49041-issue-board-input-height.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce height of issue board input to align with buttons
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/50106-hide-whitespace-changes.yml b/changelogs/unreleased/50106-hide-whitespace-changes.yml
new file mode 100644
index 00000000000..e95953c8665
--- /dev/null
+++ b/changelogs/unreleased/50106-hide-whitespace-changes.yml
@@ -0,0 +1,5 @@
+---
+title: Fix whitespace changes visibility when the related file was initially collapsed
+merge_request: 28950
+author: Ondřej Budai
+type: fixed
diff --git a/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml b/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml
new file mode 100644
index 00000000000..45770e1012c
--- /dev/null
+++ b/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml
@@ -0,0 +1,5 @@
+---
+title: Add API support for committing changes to different projects in same fork network
+merge_request: 27915
+author:
+type: added
diff --git a/changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml b/changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml
new file mode 100644
index 00000000000..07bf8b04bbe
--- /dev/null
+++ b/changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: "Added the `.extended-height` class to the labels-dropdown"
+merge_request: 28659
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/51636-task-list-api-pderichs.yml b/changelogs/unreleased/51636-task-list-api-pderichs.yml
new file mode 100644
index 00000000000..f18a0936ab2
--- /dev/null
+++ b/changelogs/unreleased/51636-task-list-api-pderichs.yml
@@ -0,0 +1,5 @@
+---
+title: Add task count and completed count to responses of Issue and MR
+merge_request: 28859
+author:
+type: added
diff --git a/changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml b/changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml
new file mode 100644
index 00000000000..4e16b95ec11
--- /dev/null
+++ b/changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml
@@ -0,0 +1,5 @@
+---
+title: Removes duplicated members from api/projects/:id/members/all
+merge_request: 24005
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/53134-multiple-extendes-for-a-job.yml b/changelogs/unreleased/53134-multiple-extendes-for-a-job.yml
new file mode 100644
index 00000000000..e09de8ac8fc
--- /dev/null
+++ b/changelogs/unreleased/53134-multiple-extendes-for-a-job.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for multiple job parents in GitLab CI YAML.
+merge_request: 26801
+author: Wolphin (Nikita)
+type: added
diff --git a/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml b/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml
new file mode 100644
index 00000000000..efda07380a4
--- /dev/null
+++ b/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml
@@ -0,0 +1,5 @@
+---
+title: Use Redis for CacheMarkDownField on non AR models
+merge_request: 29054
+author:
+type: performance
diff --git a/changelogs/unreleased/54169-flash-warning-rebrush.yml b/changelogs/unreleased/54169-flash-warning-rebrush.yml
new file mode 100644
index 00000000000..420cc26a8cc
--- /dev/null
+++ b/changelogs/unreleased/54169-flash-warning-rebrush.yml
@@ -0,0 +1,5 @@
+---
+title: "Rebrush of flash-warning according to the new design (brighter background and darker font)"
+merge_request: 28916
+author: Michel Engelen
+type: changed
diff --git a/changelogs/unreleased/55033-discussion-system-note-alignment.yml b/changelogs/unreleased/55033-discussion-system-note-alignment.yml
new file mode 100644
index 00000000000..27072ec7e12
--- /dev/null
+++ b/changelogs/unreleased/55033-discussion-system-note-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Align system note within discussion with other notes
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/55125-mr-tab-scrolling.yml b/changelogs/unreleased/55125-mr-tab-scrolling.yml
new file mode 100644
index 00000000000..e03ff6c5060
--- /dev/null
+++ b/changelogs/unreleased/55125-mr-tab-scrolling.yml
@@ -0,0 +1,5 @@
+---
+title: Update merge request tabs so they no longer scroll
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml b/changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml
new file mode 100644
index 00000000000..f7dd8c59a7c
--- /dev/null
+++ b/changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml
@@ -0,0 +1,5 @@
+---
+title: Enhance line-height of Activity feed UI
+merge_request: 28856
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/55447-validate-k8s-credentials.yml b/changelogs/unreleased/55447-validate-k8s-credentials.yml
new file mode 100644
index 00000000000..81f0efdb325
--- /dev/null
+++ b/changelogs/unreleased/55447-validate-k8s-credentials.yml
@@ -0,0 +1,5 @@
+---
+title: Validate Kubernetes credentials at cluster creation
+merge_request: 27403
+author:
+type: added
diff --git a/changelogs/unreleased/56959-drop-project_auto_devops_domain.yml b/changelogs/unreleased/56959-drop-project_auto_devops_domain.yml
new file mode 100644
index 00000000000..c529749670d
--- /dev/null
+++ b/changelogs/unreleased/56959-drop-project_auto_devops_domain.yml
@@ -0,0 +1,5 @@
+---
+title: Removes project_auto_devops#domain column
+merge_request: 28574
+author:
+type: other
diff --git a/changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml b/changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml
new file mode 100644
index 00000000000..c1c1708f935
--- /dev/null
+++ b/changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml
@@ -0,0 +1,5 @@
+---
+title: Removes support for AUTO_DEVOPS_DOMAIN
+merge_request: 28460
+author:
+type: removed
diff --git a/changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml b/changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml
new file mode 100644
index 00000000000..a2de6cd6d45
--- /dev/null
+++ b/changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Merge Request merge checkbox alignment on mobile view
+merge_request: 28845
+author:
+type: fixed
diff --git a/changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml b/changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml
new file mode 100644
index 00000000000..86b6bb20253
--- /dev/null
+++ b/changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml
@@ -0,0 +1,5 @@
+---
+title: Fix col-sm-* in forms to keep layout
+merge_request: 24885
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/57414-show-pipeline-iid.yml b/changelogs/unreleased/57414-show-pipeline-iid.yml
new file mode 100644
index 00000000000..596ae00e5a3
--- /dev/null
+++ b/changelogs/unreleased/57414-show-pipeline-iid.yml
@@ -0,0 +1,5 @@
+---
+title: Show Pipeline IID everywhere Pipeline ID is shown
+merge_request: 57414
+author: Mike Scott
+type: added
diff --git a/changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml b/changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml
new file mode 100644
index 00000000000..faa1784ea21
--- /dev/null
+++ b/changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve moving an issue results in broken image links in comments
+merge_request: 28654
+author:
+type: fixed
diff --git a/changelogs/unreleased/58269-separate-update-patch.yml b/changelogs/unreleased/58269-separate-update-patch.yml
new file mode 100644
index 00000000000..e9b44257b07
--- /dev/null
+++ b/changelogs/unreleased/58269-separate-update-patch.yml
@@ -0,0 +1,5 @@
+---
+title: Do not display Update app button when saving Knative domain name
+merge_request: 28904
+author:
+type: changed
diff --git a/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml b/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml
new file mode 100644
index 00000000000..25cc973159f
--- /dev/null
+++ b/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce Gitaly calls to improve performance when rendering suggestions
+merge_request: 29027
+author:
+type: performance
diff --git a/changelogs/unreleased/58632-fix-mr-widget-padding.yml b/changelogs/unreleased/58632-fix-mr-widget-padding.yml
new file mode 100644
index 00000000000..fb4e1791359
--- /dev/null
+++ b/changelogs/unreleased/58632-fix-mr-widget-padding.yml
@@ -0,0 +1,5 @@
+---
+title: Fix padding in MR widget
+merge_request: 28472
+author:
+type: fixed
diff --git a/changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml b/changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml
new file mode 100644
index 00000000000..53be008816d
--- /dev/null
+++ b/changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml
@@ -0,0 +1,5 @@
+---
+title: Enable function features for external Knative installations
+merge_request: 27173
+author:
+type: changed
diff --git a/changelogs/unreleased/58984-doc-missing-milestones-and-labels-links.yml b/changelogs/unreleased/58984-doc-missing-milestones-and-labels-links.yml
new file mode 100644
index 00000000000..6b74303c16e
--- /dev/null
+++ b/changelogs/unreleased/58984-doc-missing-milestones-and-labels-links.yml
@@ -0,0 +1,5 @@
+---
+title: Document when milestones and labels links are missing
+merge_request: 29355
+author:
+type: other
diff --git a/changelogs/unreleased/59026-replace-favicon.yml b/changelogs/unreleased/59026-replace-favicon.yml
new file mode 100644
index 00000000000..34228bb8438
--- /dev/null
+++ b/changelogs/unreleased/59026-replace-favicon.yml
@@ -0,0 +1,5 @@
+---
+title: Update favicon from next
+merge_request: 28601
+author: Jarek Ostrowski @jareko
+type: fixed
diff --git a/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml b/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml
new file mode 100644
index 00000000000..0904f788b6f
--- /dev/null
+++ b/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml
@@ -0,0 +1,5 @@
+---
+title: Change "Report abuse to GitLab" to more generic wording
+merge_request: 28884
+author: Marc Schwede
+type: other
diff --git a/changelogs/unreleased/59587-add-graphql-logging.yml b/changelogs/unreleased/59587-add-graphql-logging.yml
new file mode 100644
index 00000000000..74c2a734f37
--- /dev/null
+++ b/changelogs/unreleased/59587-add-graphql-logging.yml
@@ -0,0 +1,5 @@
+---
+title: Add dedicated logging for GraphQL queries
+merge_request: 27885
+author:
+type: other
diff --git a/changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml b/changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml
new file mode 100644
index 00000000000..7863f1f7bbe
--- /dev/null
+++ b/changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Prioritize login form on mobile breakpoint
+merge_request: 28360
+author:
+type: changed
diff --git a/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml b/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml
new file mode 100644
index 00000000000..fdf80c660f7
--- /dev/null
+++ b/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml
@@ -0,0 +1,5 @@
+---
+title: Default MR checkbox to true in most cases
+merge_request: !28665
+author:
+type: changed
diff --git a/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml b/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml
new file mode 100644
index 00000000000..90b72ec05c7
--- /dev/null
+++ b/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Replaces sidekiq mtail metrics with ruby instrumentation metrics
+merge_request: 29215
+author:
+type: changed
diff --git a/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml b/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml
new file mode 100644
index 00000000000..83b7bd3433e
--- /dev/null
+++ b/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml
@@ -0,0 +1,5 @@
+---
+title: Update registration form to indicate invalid name or username length on input
+merge_request: 28095
+author: Jiaan Louw
+type: changed
diff --git a/changelogs/unreleased/60819_yamllint_gitlabci.yml b/changelogs/unreleased/60819_yamllint_gitlabci.yml
new file mode 100644
index 00000000000..aba3b206f7e
--- /dev/null
+++ b/changelogs/unreleased/60819_yamllint_gitlabci.yml
@@ -0,0 +1,5 @@
+---
+title: Fix yaml linting for GitLab CI inside project (.gitlab/ci) *.yml files and CI template files
+merge_request: 27576
+author: Will Hall
+type: fixed
diff --git a/changelogs/unreleased/609120-ref-link.yml b/changelogs/unreleased/609120-ref-link.yml
new file mode 100644
index 00000000000..97c93b7ff53
--- /dev/null
+++ b/changelogs/unreleased/609120-ref-link.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes Ref link being displayed as raw HTML in the Pipelines page
+merge_request: 28823
+author:
+type: fixed
diff --git a/changelogs/unreleased/60987-emoji-picker-popup.yml b/changelogs/unreleased/60987-emoji-picker-popup.yml
new file mode 100644
index 00000000000..3bccec8e164
--- /dev/null
+++ b/changelogs/unreleased/60987-emoji-picker-popup.yml
@@ -0,0 +1,5 @@
+---
+title: Fix emoji picker visibility issue
+merge_request: 28984
+author:
+type: fixed
diff --git a/changelogs/unreleased/61024-update-resolved-icon.yml b/changelogs/unreleased/61024-update-resolved-icon.yml
new file mode 100644
index 00000000000..4a4de9eb13a
--- /dev/null
+++ b/changelogs/unreleased/61024-update-resolved-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Add check circle filled icon for resolved comments
+merge_request: 28663
+author:
+type: changed
diff --git a/changelogs/unreleased/6104-ee-ce-difference.yml b/changelogs/unreleased/6104-ee-ce-difference.yml
new file mode 100644
index 00000000000..59d31daf0eb
--- /dev/null
+++ b/changelogs/unreleased/6104-ee-ce-difference.yml
@@ -0,0 +1,5 @@
+---
+title: Unified EE/CS differences in repository/show.html
+merge_request: 13562
+author:
+type: other
diff --git a/changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml b/changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml
new file mode 100644
index 00000000000..53cc0a15417
--- /dev/null
+++ b/changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml
@@ -0,0 +1,5 @@
+---
+title: Eliminate color inconsistencies in metric graphs
+merge_request: 29127
+author:
+type: fixed
diff --git a/changelogs/unreleased/61072-link-to-user-profile-not-distinguishable-on-latest-commit-widget.yml b/changelogs/unreleased/61072-link-to-user-profile-not-distinguishable-on-latest-commit-widget.yml
new file mode 100644
index 00000000000..5285ac767cc
--- /dev/null
+++ b/changelogs/unreleased/61072-link-to-user-profile-not-distinguishable-on-latest-commit-widget.yml
@@ -0,0 +1,5 @@
+---
+title: User link styling for commits
+merge_request: 29150
+author:
+type: other
diff --git a/changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml b/changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml
new file mode 100644
index 00000000000..ed4cf0507c7
--- /dev/null
+++ b/changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml
@@ -0,0 +1,5 @@
+---
+title: Bring secondary button styles up to design standard
+merge_request: 27920
+author:
+type: fixed
diff --git a/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml b/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml
new file mode 100644
index 00000000000..94666ac12ec
--- /dev/null
+++ b/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Snippet icon button is misaligned
+merge_request: 28522
+author:
+type: other
diff --git a/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml b/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml
index 350fd525a30..a7f5706058d 100644
--- a/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml
+++ b/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml
@@ -1,5 +1,5 @@
---
title: Give New Snippet button green outline
merge_request: 28559
-author: Marcel van Remmerden
+author:
type: other
diff --git a/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml b/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml
new file mode 100644
index 00000000000..e446459ffc8
--- /dev/null
+++ b/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml
@@ -0,0 +1,5 @@
+---
+title: Add hover and focus to Attach a file
+merge_request: 28682
+author:
+type: fixed
diff --git a/changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml b/changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml
new file mode 100644
index 00000000000..1a3a019e1f4
--- /dev/null
+++ b/changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Add expand/collapse to error tracking settings
+merge_request: 28619
+author:
+type: added
diff --git a/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml b/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml
new file mode 100644
index 00000000000..718604c9ceb
--- /dev/null
+++ b/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Merge request discussion text jumps when resolved
+merge_request: 28995
+author:
+type: fixed
diff --git a/changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml b/changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml
new file mode 100644
index 00000000000..9b4f13353f5
--- /dev/null
+++ b/changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml
@@ -0,0 +1,5 @@
+---
+title: Fix dropdown position when loading remote data
+merge_request: 28526
+author:
+type: fixed
diff --git a/changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml b/changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml
new file mode 100644
index 00000000000..25c83d24007
--- /dev/null
+++ b/changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Adds a text label to color pickers to improve accessibility.
+merge_request: 28343
+author: Chris Toynbee
+type: changed
diff --git a/changelogs/unreleased/61821-tooltip-consistency.yml b/changelogs/unreleased/61821-tooltip-consistency.yml
new file mode 100644
index 00000000000..9b131907ebf
--- /dev/null
+++ b/changelogs/unreleased/61821-tooltip-consistency.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Tooltip Consistency
+merge_request: 28839
+author:
+type: fixed
diff --git a/changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml b/changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml
new file mode 100644
index 00000000000..4d6464eed52
--- /dev/null
+++ b/changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent icons from shrinking in User popover when contents exceed container
+merge_request: 28696
+author:
+type: fixed
diff --git a/changelogs/unreleased/61880-download-btn-group.yml b/changelogs/unreleased/61880-download-btn-group.yml
new file mode 100644
index 00000000000..c1f6b2767c6
--- /dev/null
+++ b/changelogs/unreleased/61880-download-btn-group.yml
@@ -0,0 +1,5 @@
+---
+title: Group download buttons into a .btn-group
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml b/changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml
new file mode 100644
index 00000000000..50b3efba0a5
--- /dev/null
+++ b/changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml
@@ -0,0 +1,5 @@
+---
+title: I18n for issue closure reason in emails
+merge_request: 28489
+author: Michał Zając
+type: changed
diff --git a/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml
new file mode 100644
index 00000000000..4d2f73ce2ff
--- /dev/null
+++ b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml
@@ -0,0 +1,5 @@
+---
+title: Change collapse icon size to size of profile picture
+merge_request: 28512
+author:
+type: other
diff --git a/changelogs/unreleased/62091-remove-time-windows-flag.yml b/changelogs/unreleased/62091-remove-time-windows-flag.yml
new file mode 100644
index 00000000000..c6c11328312
--- /dev/null
+++ b/changelogs/unreleased/62091-remove-time-windows-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Allow users to specify a time range on metrics dashboard
+merge_request: 28670
+author:
+type: added
diff --git a/changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml b/changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml
new file mode 100644
index 00000000000..3317d505924
--- /dev/null
+++ b/changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Added padding to time window dropdown in monitor dashboard
+merge_request: 28897
+author:
+type: fixed
diff --git a/changelogs/unreleased/62107-fix-detail-page-header-height.yml b/changelogs/unreleased/62107-fix-detail-page-header-height.yml
new file mode 100644
index 00000000000..3723f323bcc
--- /dev/null
+++ b/changelogs/unreleased/62107-fix-detail-page-header-height.yml
@@ -0,0 +1,5 @@
+---
+title: Fix the height of the page headers on issues/merge request/snippets pages
+merge_request: 28650
+author: Erik van der Gaag
+type: fixed
diff --git a/changelogs/unreleased/62124-new-threaded-discussion-design.yml b/changelogs/unreleased/62124-new-threaded-discussion-design.yml
new file mode 100644
index 00000000000..6614e05be74
--- /dev/null
+++ b/changelogs/unreleased/62124-new-threaded-discussion-design.yml
@@ -0,0 +1,5 @@
+---
+title: Implement borderless discussion design with new reply field
+merge_request: 28580
+author:
+type: added
diff --git a/changelogs/unreleased/62144-fix-option-dropdown-button-size.yml b/changelogs/unreleased/62144-fix-option-dropdown-button-size.yml
new file mode 100644
index 00000000000..86d8f4536f9
--- /dev/null
+++ b/changelogs/unreleased/62144-fix-option-dropdown-button-size.yml
@@ -0,0 +1,5 @@
+---
+title: Fix inconsistent option dropdown button height to match adjacent button
+merge_request: 29096
+author:
+type: fixed
diff --git a/changelogs/unreleased/62154-fe-create-fix-long-branch-name-in-dropdown.yml b/changelogs/unreleased/62154-fe-create-fix-long-branch-name-in-dropdown.yml
new file mode 100644
index 00000000000..1d951c1dc24
--- /dev/null
+++ b/changelogs/unreleased/62154-fe-create-fix-long-branch-name-in-dropdown.yml
@@ -0,0 +1,6 @@
+---
+title: Add support to view entirety of long branch name in dropdown instead of it
+ being cut off
+merge_request: 29069
+author:
+type: fixed
diff --git a/changelogs/unreleased/62227-webkit-icon-overlap.yml b/changelogs/unreleased/62227-webkit-icon-overlap.yml
new file mode 100644
index 00000000000..47d7583f4c2
--- /dev/null
+++ b/changelogs/unreleased/62227-webkit-icon-overlap.yml
@@ -0,0 +1,5 @@
+---
+title: Add style to disable webkit icons for search inputs
+merge_request: 28833
+author: Jarek Ostrowski @jareko
+type: fixed
diff --git a/changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml b/changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml
new file mode 100644
index 00000000000..c01106a15ec
--- /dev/null
+++ b/changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Add Kubernetes logs to Admin Logs UI
+merge_request: 28685
+author:
+type: added
diff --git a/changelogs/unreleased/62408-dropdown-truncate.yml b/changelogs/unreleased/62408-dropdown-truncate.yml
new file mode 100644
index 00000000000..7204016efdf
--- /dev/null
+++ b/changelogs/unreleased/62408-dropdown-truncate.yml
@@ -0,0 +1,5 @@
+---
+title: Fix job name in graph dropdown overflowing
+merge_request: 28824
+author:
+type: fixed
diff --git a/changelogs/unreleased/62418-project-default-git-depth.yml b/changelogs/unreleased/62418-project-default-git-depth.yml
new file mode 100644
index 00000000000..b5647cd0859
--- /dev/null
+++ b/changelogs/unreleased/62418-project-default-git-depth.yml
@@ -0,0 +1,5 @@
+---
+title: Add project level git depth CI/CD setting
+merge_request: 28919
+author:
+type: added
diff --git a/changelogs/unreleased/62432-fix-participants-wrapping.yml b/changelogs/unreleased/62432-fix-participants-wrapping.yml
new file mode 100644
index 00000000000..a7e4bd372de
--- /dev/null
+++ b/changelogs/unreleased/62432-fix-participants-wrapping.yml
@@ -0,0 +1,5 @@
+---
+title: Fix participants list wrapping
+merge_request: 28873
+author:
+type: fixed
diff --git a/changelogs/unreleased/62485-label-weights.yml b/changelogs/unreleased/62485-label-weights.yml
new file mode 100644
index 00000000000..354b18be11e
--- /dev/null
+++ b/changelogs/unreleased/62485-label-weights.yml
@@ -0,0 +1,5 @@
+---
+title: Give labels consistent weight
+merge_request: 28895
+author:
+type: fixed
diff --git a/changelogs/unreleased/62487-external-policy-desc.yml b/changelogs/unreleased/62487-external-policy-desc.yml
new file mode 100644
index 00000000000..2e787b89db1
--- /dev/null
+++ b/changelogs/unreleased/62487-external-policy-desc.yml
@@ -0,0 +1,5 @@
+---
+title: Move text under p tag
+merge_request: 28901
+author:
+type: fixed
diff --git a/changelogs/unreleased/62656-adjusted-dropdown-styles.yml b/changelogs/unreleased/62656-adjusted-dropdown-styles.yml
new file mode 100644
index 00000000000..36f14ae2741
--- /dev/null
+++ b/changelogs/unreleased/62656-adjusted-dropdown-styles.yml
@@ -0,0 +1,5 @@
+---
+title: "changed the styles on `Add List` dropdown to look more like the EE vesion"
+merge_request: 29338
+author: Michel Engelen
+type: changed
diff --git a/changelogs/unreleased/62788-graphql-pagination.yml b/changelogs/unreleased/62788-graphql-pagination.yml
new file mode 100644
index 00000000000..a7bc317a08f
--- /dev/null
+++ b/changelogs/unreleased/62788-graphql-pagination.yml
@@ -0,0 +1,5 @@
+---
+title: Adds pagination component for graphql api
+merge_request: 29277
+author:
+type: added
diff --git a/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml b/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml
new file mode 100644
index 00000000000..fca92a0d4dc
--- /dev/null
+++ b/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml
@@ -0,0 +1,5 @@
+---
+title: Include the port in the URLs of the API Link headers
+merge_request: 29267
+author:
+type: fixed
diff --git a/changelogs/unreleased/62974-follow-up-from-wip-align-merge-request-icons-and-text.yml b/changelogs/unreleased/62974-follow-up-from-wip-align-merge-request-icons-and-text.yml
new file mode 100644
index 00000000000..811986e6857
--- /dev/null
+++ b/changelogs/unreleased/62974-follow-up-from-wip-align-merge-request-icons-and-text.yml
@@ -0,0 +1,5 @@
+---
+title: 'Make margin between buttons consistent'
+merge_request: 29378
+author:
+type: other
diff --git a/changelogs/unreleased/9121-sort-relative-position.yml b/changelogs/unreleased/9121-sort-relative-position.yml
new file mode 100644
index 00000000000..adc9e87e5bb
--- /dev/null
+++ b/changelogs/unreleased/9121-sort-relative-position.yml
@@ -0,0 +1,5 @@
+---
+title: Allow issue list to be sorted by relative order
+merge_request: 28566
+author:
+type: added
diff --git a/changelogs/unreleased/9578-adjust-milestone-completion-rate.yml b/changelogs/unreleased/9578-adjust-milestone-completion-rate.yml
new file mode 100644
index 00000000000..0694e1462cf
--- /dev/null
+++ b/changelogs/unreleased/9578-adjust-milestone-completion-rate.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust milestone completion rate to be based on issues count.
+merge_request: 28777
+author:
+type: changed
diff --git a/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml b/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml
new file mode 100644
index 00000000000..821e5d70dca
--- /dev/null
+++ b/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml
@@ -0,0 +1,5 @@
+---
+title: "Moved EE/CE code differences for `app/assets/javascripts/gl_dropdown.js` into CE"
+merge_request: 28711
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/abstract-auto-merge.yml b/changelogs/unreleased/abstract-auto-merge.yml
new file mode 100644
index 00000000000..d3069a3e500
--- /dev/null
+++ b/changelogs/unreleased/abstract-auto-merge.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor and abstract Auto Merge Processes
+merge_request: 28595
+author:
+type: other
diff --git a/changelogs/unreleased/ac-63020-typeerror-nil-can-t-be-coerced-into-integer.yml b/changelogs/unreleased/ac-63020-typeerror-nil-can-t-be-coerced-into-integer.yml
new file mode 100644
index 00000000000..51ac2358fba
--- /dev/null
+++ b/changelogs/unreleased/ac-63020-typeerror-nil-can-t-be-coerced-into-integer.yml
@@ -0,0 +1,5 @@
+---
+title: Fix nil coercion updating storage size on project statistics
+merge_request: 29425
+author:
+type: fixed
diff --git a/changelogs/unreleased/ac-graphql-stats.yml b/changelogs/unreleased/ac-graphql-stats.yml
new file mode 100644
index 00000000000..8837dce4d89
--- /dev/null
+++ b/changelogs/unreleased/ac-graphql-stats.yml
@@ -0,0 +1,5 @@
+---
+title: Add Namespace and ProjectStatistics to GraphQL API
+merge_request: 28277
+author:
+type: added
diff --git a/changelogs/unreleased/ac-graphql-wikisize.yml b/changelogs/unreleased/ac-graphql-wikisize.yml
new file mode 100644
index 00000000000..be9c347ec21
--- /dev/null
+++ b/changelogs/unreleased/ac-graphql-wikisize.yml
@@ -0,0 +1,5 @@
+---
+title: Expose wiki_size on GraphQL API
+merge_request: 29123
+author:
+type: added
diff --git a/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml b/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml
new file mode 100644
index 00000000000..bd005206d4e
--- /dev/null
+++ b/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml
@@ -0,0 +1,5 @@
+---
+title: Forbid NULL in project_statistics.packages_size
+merge_request: 28400
+author:
+type: other
diff --git a/changelogs/unreleased/add-constraint-for-milestone-dates.yml b/changelogs/unreleased/add-constraint-for-milestone-dates.yml
new file mode 100644
index 00000000000..485149cf62e
--- /dev/null
+++ b/changelogs/unreleased/add-constraint-for-milestone-dates.yml
@@ -0,0 +1,5 @@
+---
+title: Limit milestone dates to before year 9999
+merge_request: 28742
+author: Luke Picciau
+type: fixed
diff --git a/changelogs/unreleased/add-lfs-blob-ids-to-tree-type.yml b/changelogs/unreleased/add-lfs-blob-ids-to-tree-type.yml
new file mode 100644
index 00000000000..14a5ef1cef3
--- /dev/null
+++ b/changelogs/unreleased/add-lfs-blob-ids-to-tree-type.yml
@@ -0,0 +1,5 @@
+---
+title: Add LFS oid to GraphQL blob type
+merge_request: 28666
+author:
+type: added
diff --git a/changelogs/unreleased/add-wiki-size-to-statistics.yml b/changelogs/unreleased/add-wiki-size-to-statistics.yml
new file mode 100644
index 00000000000..85b6d7a1774
--- /dev/null
+++ b/changelogs/unreleased/add-wiki-size-to-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Add wiki size to project statistics
+merge_request: 25321
+author: Peter Marko
+type: added
diff --git a/changelogs/unreleased/allow-emoji-in-references.yml b/changelogs/unreleased/allow-emoji-in-references.yml
new file mode 100644
index 00000000000..3116b346c00
--- /dev/null
+++ b/changelogs/unreleased/allow-emoji-in-references.yml
@@ -0,0 +1,5 @@
+---
+title: Allow references to labels and milestones to contain emoji
+merge_request: 29284
+author:
+type: changed
diff --git a/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml b/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml
new file mode 100644
index 00000000000..d60dd65be8a
--- /dev/null
+++ b/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml
@@ -0,0 +1,5 @@
+---
+title: Always show "Pipelines must succeed" checkbox
+merge_request: 28651
+author:
+type: fixed
diff --git a/changelogs/unreleased/api_make_protected_boolean_type.yml b/changelogs/unreleased/api_make_protected_boolean_type.yml
new file mode 100644
index 00000000000..765edea6427
--- /dev/null
+++ b/changelogs/unreleased/api_make_protected_boolean_type.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: change protected attribute type to Boolean'
+merge_request: 28766
+author:
+type: other
diff --git a/changelogs/unreleased/api_masked_variables.yml b/changelogs/unreleased/api_masked_variables.yml
new file mode 100644
index 00000000000..3605339cb91
--- /dev/null
+++ b/changelogs/unreleased/api_masked_variables.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Allow to get and set "masked" attribute for variables'
+merge_request: 28381
+author: Mathieu Parent
+type: added
diff --git a/changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml b/changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml
new file mode 100644
index 00000000000..9ba55719bdf
--- /dev/null
+++ b/changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml
@@ -0,0 +1,5 @@
+---
+title: Bumps Kubernetes in Auto DevOps to 1.11.10
+merge_request: 28525
+author:
+type: other
diff --git a/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml b/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml
new file mode 100644
index 00000000000..2b5a3592775
--- /dev/null
+++ b/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of jobs controller
+merge_request: 28093
+author:
+type: performance
diff --git a/changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml b/changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml
new file mode 100644
index 00000000000..ecfbc97a8c5
--- /dev/null
+++ b/changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml
@@ -0,0 +1,5 @@
+---
+title: Bump Helm version in Auto-DevOps.gitlab-ci.yml to 2.14.0
+merge_request: 28527
+author:
+type: other
diff --git a/changelogs/unreleased/bvl-graphql-multiplex.yml b/changelogs/unreleased/bvl-graphql-multiplex.yml
new file mode 100644
index 00000000000..56d39e447a5
--- /dev/null
+++ b/changelogs/unreleased/bvl-graphql-multiplex.yml
@@ -0,0 +1,5 @@
+---
+title: Support multiplex GraphQL queries
+merge_request: 28273
+author:
+type: added
diff --git a/changelogs/unreleased/bvl-use-global-ids-graphql.yml b/changelogs/unreleased/bvl-use-global-ids-graphql.yml
new file mode 100644
index 00000000000..34cb65e6001
--- /dev/null
+++ b/changelogs/unreleased/bvl-use-global-ids-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Use global IDs when exposing GraphQL resources
+merge_request: 29080
+author:
+type: added
diff --git a/changelogs/unreleased/cancel-auto-merge-when-branch-is-changed.yml b/changelogs/unreleased/cancel-auto-merge-when-branch-is-changed.yml
new file mode 100644
index 00000000000..c3c6e4322a2
--- /dev/null
+++ b/changelogs/unreleased/cancel-auto-merge-when-branch-is-changed.yml
@@ -0,0 +1,5 @@
+---
+title: Cancel Auto Merge when target branch is changed
+merge_request: 29416
+author:
+type: fixed
diff --git a/changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml b/changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml
new file mode 100644
index 00000000000..d38046ebcbf
--- /dev/null
+++ b/changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml
@@ -0,0 +1,5 @@
+---
+title: Cancel auto merge when merge request is closed
+merge_request: 28782
+author:
+type: fixed
diff --git a/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml b/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml
new file mode 100644
index 00000000000..a795e33b00d
--- /dev/null
+++ b/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid setting Gitlab::Session on sessionless requests and Git HTTP
+merge_request: 29146
+author:
+type: fixed
diff --git a/changelogs/unreleased/chore-remove-circuit-breaker-api.yml b/changelogs/unreleased/chore-remove-circuit-breaker-api.yml
new file mode 100644
index 00000000000..f9532be04c8
--- /dev/null
+++ b/changelogs/unreleased/chore-remove-circuit-breaker-api.yml
@@ -0,0 +1,5 @@
+---
+title: Remove the circuit breaker API
+merge_request: 28669
+author:
+type: removed
diff --git a/changelogs/unreleased/ci-variable-conjunction.yml b/changelogs/unreleased/ci-variable-conjunction.yml
new file mode 100644
index 00000000000..839c4285f3a
--- /dev/null
+++ b/changelogs/unreleased/ci-variable-conjunction.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for && and || to CI Pipeline Expressions. Change CI variable expression matching for Lexeme::Pattern to eagerly return tokens.
+merge_request: 27925
+author: Martin Manelli
+type: added
diff --git a/changelogs/unreleased/copy-button-in-modals.yml b/changelogs/unreleased/copy-button-in-modals.yml
new file mode 100644
index 00000000000..bc18eb9ab26
--- /dev/null
+++ b/changelogs/unreleased/copy-button-in-modals.yml
@@ -0,0 +1,5 @@
+---
+title: Add a New Copy Button That Works in Modals
+merge_request: 28676
+author:
+type: added
diff --git a/changelogs/unreleased/dm-http-hostname-override.yml b/changelogs/unreleased/dm-http-hostname-override.yml
new file mode 100644
index 00000000000..f84f36a0010
--- /dev/null
+++ b/changelogs/unreleased/dm-http-hostname-override.yml
@@ -0,0 +1,5 @@
+---
+title: Protect Gitlab::HTTP against DNS rebinding attack
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/docs-add-chatops-request-doc.yml b/changelogs/unreleased/docs-add-chatops-request-doc.yml
new file mode 100644
index 00000000000..85ba86a73af
--- /dev/null
+++ b/changelogs/unreleased/docs-add-chatops-request-doc.yml
@@ -0,0 +1,5 @@
+---
+title: Add section to dev docs on accessing chatops
+merge_request: 28623
+author:
+type: other
diff --git a/changelogs/unreleased/ee-11040-added-conditional-rendering.yml b/changelogs/unreleased/ee-11040-added-conditional-rendering.yml
new file mode 100644
index 00000000000..7b06e43830f
--- /dev/null
+++ b/changelogs/unreleased/ee-11040-added-conditional-rendering.yml
@@ -0,0 +1,5 @@
+---
+title: "Added conditional rendering to `app/views/search/_form.html.haml` for CE/EE code base consistency"
+merge_request: 28883
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/fe-fix-gl-dropdown-scrolling-to-top.yml b/changelogs/unreleased/fe-fix-gl-dropdown-scrolling-to-top.yml
new file mode 100644
index 00000000000..4125b4241e6
--- /dev/null
+++ b/changelogs/unreleased/fe-fix-gl-dropdown-scrolling-to-top.yml
@@ -0,0 +1,5 @@
+---
+title: Fix scrolling to top on assignee change
+merge_request: 29500
+author:
+type: fixed
diff --git a/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml b/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml
new file mode 100644
index 00000000000..443fff92f55
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml
@@ -0,0 +1,5 @@
+---
+title: Use to 'gitlabktl' build serverless applications
+merge_request: 29258
+author:
+type: added
diff --git a/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml b/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml
new file mode 100644
index 00000000000..46fa90ccda0
--- /dev/null
+++ b/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml
@@ -0,0 +1,5 @@
+---
+title: Allow lowercase prefix for Youtrack issue ids
+merge_request: 29057
+author: Matthias Baur
+type: fixed
diff --git a/changelogs/unreleased/fix-autodevops-postgres-versioning.yml b/changelogs/unreleased/fix-autodevops-postgres-versioning.yml
new file mode 100644
index 00000000000..8ddc70f6314
--- /dev/null
+++ b/changelogs/unreleased/fix-autodevops-postgres-versioning.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed ignored postgres version that occurs after the first autodevops deploy when specifying custom $POSTGRES_VERSION
+merge_request: 28735
+author: Brandon Dimcheff
+type: fixed
diff --git a/changelogs/unreleased/fix-format-date-safari-ff.yml b/changelogs/unreleased/fix-format-date-safari-ff.yml
new file mode 100644
index 00000000000..e71ea2867f3
--- /dev/null
+++ b/changelogs/unreleased/fix-format-date-safari-ff.yml
@@ -0,0 +1,5 @@
+---
+title: Throw an error when formatDate's input is invalid
+merge_request: 28713
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml b/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml
new file mode 100644
index 00000000000..88656b7ef4c
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml
@@ -0,0 +1,5 @@
+---
+title: Fix serverless apps deployments by bumping 'tm' version
+merge_request: 29254
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml b/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml
new file mode 100644
index 00000000000..f51ec273a57
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml
@@ -0,0 +1,5 @@
+---
+title: Remove build policies from serverless app template
+merge_request: 29253
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-import-member-access.yml b/changelogs/unreleased/fix-import-member-access.yml
new file mode 100644
index 00000000000..5dc4ae738f8
--- /dev/null
+++ b/changelogs/unreleased/fix-import-member-access.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issue importing members with owner access
+merge_request: 28636
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-import-param-ordering.yml b/changelogs/unreleased/fix-import-param-ordering.yml
new file mode 100644
index 00000000000..47e32cda977
--- /dev/null
+++ b/changelogs/unreleased/fix-import-param-ordering.yml
@@ -0,0 +1,5 @@
+---
+title: Fix order dependency with user params during imports
+merge_request: 28719
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-issue-mr-badge.yml b/changelogs/unreleased/fix-issue-mr-badge.yml
new file mode 100644
index 00000000000..e777f52f173
--- /dev/null
+++ b/changelogs/unreleased/fix-issue-mr-badge.yml
@@ -0,0 +1,5 @@
+---
+title: Use grid and correct border radius for status badge
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml b/changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml
new file mode 100644
index 00000000000..1041943f9c4
--- /dev/null
+++ b/changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml
@@ -0,0 +1,5 @@
+---
+title: Fix milestone references containing &, <, or >
+merge_request: 28667
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-pipeline-schedule-owner-is-nil.yml b/changelogs/unreleased/fix-pipeline-schedule-owner-is-nil.yml
new file mode 100644
index 00000000000..5c8644d2860
--- /dev/null
+++ b/changelogs/unreleased/fix-pipeline-schedule-owner-is-nil.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pipeline schedules when owner is nil
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-search-dropdown-blur-close.yml b/changelogs/unreleased/fix-search-dropdown-blur-close.yml
new file mode 100644
index 00000000000..1ac9dc674fe
--- /dev/null
+++ b/changelogs/unreleased/fix-search-dropdown-blur-close.yml
@@ -0,0 +1,5 @@
+---
+title: Fix search dropdown not closing on blur if empty
+merge_request: 28730
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-time-window-default.yml b/changelogs/unreleased/fix-time-window-default.yml
new file mode 100644
index 00000000000..147f82eb6c9
--- /dev/null
+++ b/changelogs/unreleased/fix-time-window-default.yml
@@ -0,0 +1,5 @@
+---
+title: Use the selected time window for metrics dashboard
+merge_request: 29152
+author:
+type: fixed
diff --git a/changelogs/unreleased/gitaly-version-v1.43.0.yml b/changelogs/unreleased/gitaly-version-v1.43.0.yml
new file mode 100644
index 00000000000..67acd2725e1
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.43.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v1.43.0
+merge_request: 28867
+author:
+type: changed
diff --git a/changelogs/unreleased/gt-externalize-profiles-preferences.yml b/changelogs/unreleased/gt-externalize-profiles-preferences.yml
new file mode 100644
index 00000000000..1a72e92a241
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-profiles-preferences.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize profiles preferences
+merge_request: 28470
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml b/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml
new file mode 100644
index 00000000000..35515c9d639
--- /dev/null
+++ b/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml
@@ -0,0 +1,5 @@
+---
+title: Open visibility help link in a new tab
+merge_request: 28603
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/i18n-active_sessions-in-user-profile.yml b/changelogs/unreleased/i18n-active_sessions-in-user-profile.yml
new file mode 100644
index 00000000000..fe6eb3a2bf7
--- /dev/null
+++ b/changelogs/unreleased/i18n-active_sessions-in-user-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings of active sessions page in user profile
+merge_request: 28590
+author: antony liu
+type: other
diff --git a/changelogs/unreleased/i18n-chat-of-user-profile.yml b/changelogs/unreleased/i18n-chat-of-user-profile.yml
new file mode 100644
index 00000000000..663b4ffc1a1
--- /dev/null
+++ b/changelogs/unreleased/i18n-chat-of-user-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings of chat page in user profile
+merge_request: 28632
+author:
+type: other
diff --git a/changelogs/unreleased/i18n-email-of-user-profile.yml b/changelogs/unreleased/i18n-email-of-user-profile.yml
new file mode 100644
index 00000000000..6cb718843d5
--- /dev/null
+++ b/changelogs/unreleased/i18n-email-of-user-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings of email page in user profile
+merge_request: 28587
+author: antony liu
+type: other
diff --git a/changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml b/changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml
new file mode 100644
index 00000000000..4dc45b35976
--- /dev/null
+++ b/changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings of PGP Keys and SSH Keys page in user profile
+merge_request: 28653
+author: Antony Liu
+type: other
diff --git a/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml b/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml
new file mode 100644
index 00000000000..eae2d5f9b2a
--- /dev/null
+++ b/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml
@@ -0,0 +1,5 @@
+---
+title: Allow removal of empty lines via suggestions
+merge_request: 28703
+author:
+type: fixed
diff --git a/changelogs/unreleased/ignore-artifact-attirbutes-in-project-import-export.yml b/changelogs/unreleased/ignore-artifact-attirbutes-in-project-import-export.yml
new file mode 100644
index 00000000000..536aae03f59
--- /dev/null
+++ b/changelogs/unreleased/ignore-artifact-attirbutes-in-project-import-export.yml
@@ -0,0 +1,5 @@
+---
+title: Ignore legacy artifact columns in Project Import/Export
+merge_request: 29427
+author:
+type: fixed
diff --git a/changelogs/unreleased/improve-email-text-part.yml b/changelogs/unreleased/improve-email-text-part.yml
new file mode 100644
index 00000000000..ce506cb1507
--- /dev/null
+++ b/changelogs/unreleased/improve-email-text-part.yml
@@ -0,0 +1,5 @@
+---
+title: Improve new user email markup unconsistency between text and html parts
+merge_request: 29111
+author: Haunui Saint-sevin
+type: fixed
diff --git a/changelogs/unreleased/increase-move-issue-dropdown-height.yml b/changelogs/unreleased/increase-move-issue-dropdown-height.yml
new file mode 100644
index 00000000000..bb67e9341b2
--- /dev/null
+++ b/changelogs/unreleased/increase-move-issue-dropdown-height.yml
@@ -0,0 +1,5 @@
+---
+title: Increase height of move issue dropdown
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/issue_49897.yml b/changelogs/unreleased/issue_49897.yml
new file mode 100644
index 00000000000..b630b5143c6
--- /dev/null
+++ b/changelogs/unreleased/issue_49897.yml
@@ -0,0 +1,5 @@
+---
+title: Delete unauthorized Todos when project is made private
+merge_request: 28560
+author:
+type: fixed
diff --git a/changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml b/changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml
new file mode 100644
index 00000000000..dc4edbc058f
--- /dev/null
+++ b/changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate the monitoring dashboard store to vuex
+merge_request: 28555
+author:
+type: other
diff --git a/changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml b/changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml
new file mode 100644
index 00000000000..d0a09385d4c
--- /dev/null
+++ b/changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid 500 when rendering users ATOM data
+merge_request: 25408
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-avoid-encoding-errors-on-merge-to-ref-service.yml b/changelogs/unreleased/osw-avoid-encoding-errors-on-merge-to-ref-service.yml
new file mode 100644
index 00000000000..176c8bf36f7
--- /dev/null
+++ b/changelogs/unreleased/osw-avoid-encoding-errors-on-merge-to-ref-service.yml
@@ -0,0 +1,5 @@
+---
+title: Handle encoding errors for MergeToRefService
+merge_request: 29440
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-fix-post-dep-migration-with-timeout.yml b/changelogs/unreleased/osw-fix-post-dep-migration-with-timeout.yml
new file mode 100644
index 00000000000..6b37bb11ad5
--- /dev/null
+++ b/changelogs/unreleased/osw-fix-post-dep-migration-with-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid DB timeouts when scheduling migrations
+merge_request: 29437
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml b/changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml
new file mode 100644
index 00000000000..6b5f97f24b3
--- /dev/null
+++ b/changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml
@@ -0,0 +1,5 @@
+---
+title: Reset merge status from mergeable MRs
+merge_request: 28843
+author:
+type: other
diff --git a/changelogs/unreleased/patch-65.yml b/changelogs/unreleased/patch-65.yml
new file mode 100644
index 00000000000..9ce628a6541
--- /dev/null
+++ b/changelogs/unreleased/patch-65.yml
@@ -0,0 +1,5 @@
+---
+title: Show tooltip on truncated commit title
+merge_request: 28865
+author: Timofey Trofimov
+type: other
diff --git a/changelogs/unreleased/pb-update-gitaly-1-45-0.yml b/changelogs/unreleased/pb-update-gitaly-1-45-0.yml
new file mode 100644
index 00000000000..eaad7a8378b
--- /dev/null
+++ b/changelogs/unreleased/pb-update-gitaly-1-45-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GITALY_SERVER_VERSION to 1.45.0
+merge_request: 29109
+author:
+type: changed
diff --git a/changelogs/unreleased/pipelines-email-default-branch-filter.yml b/changelogs/unreleased/pipelines-email-default-branch-filter.yml
new file mode 100644
index 00000000000..4c2a54af0bf
--- /dev/null
+++ b/changelogs/unreleased/pipelines-email-default-branch-filter.yml
@@ -0,0 +1,5 @@
+---
+title: Add notify_only_default_branch option to PipelinesEmailService
+merge_request: 28271
+author: Peter Marko
+type: added
diff --git a/changelogs/unreleased/referenced-labels.yml b/changelogs/unreleased/referenced-labels.yml
new file mode 100644
index 00000000000..c39ef4c2478
--- /dev/null
+++ b/changelogs/unreleased/referenced-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Add referenced-commands in no overflow list
+merge_request: 28858
+author:
+type: fixed
diff --git a/changelogs/unreleased/relative-urls-for-system-notes.yml b/changelogs/unreleased/relative-urls-for-system-notes.yml
new file mode 100644
index 00000000000..ec834e9f277
--- /dev/null
+++ b/changelogs/unreleased/relative-urls-for-system-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Change links in system notes to use relative paths
+merge_request: 28588
+author: Luke Picciau
+type: fixed
diff --git a/changelogs/unreleased/remove-legacy-artifacts-related-code.yml b/changelogs/unreleased/remove-legacy-artifacts-related-code.yml
new file mode 100644
index 00000000000..acde65af2d4
--- /dev/null
+++ b/changelogs/unreleased/remove-legacy-artifacts-related-code.yml
@@ -0,0 +1,5 @@
+---
+title: Remove legacy artifact related code
+merge_request: 26475
+author:
+type: other
diff --git a/changelogs/unreleased/remove-mr-diff-header-height.yml b/changelogs/unreleased/remove-mr-diff-header-height.yml
new file mode 100644
index 00000000000..c06c7281c58
--- /dev/null
+++ b/changelogs/unreleased/remove-mr-diff-header-height.yml
@@ -0,0 +1,5 @@
+---
+title: Remove fixed height from MR diff headers
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml b/changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml
new file mode 100644
index 00000000000..d9ad5af256a
--- /dev/null
+++ b/changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent XSS injection in note imports
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-60039.yml b/changelogs/unreleased/security-60039.yml
new file mode 100644
index 00000000000..5edbf32ec97
--- /dev/null
+++ b/changelogs/unreleased/security-60039.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent invalid branch for merge request
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml b/changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml
new file mode 100644
index 00000000000..5b79258af54
--- /dev/null
+++ b/changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml
@@ -0,0 +1,5 @@
+---
+title: Filter relative links in wiki for XSS
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml b/changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml
new file mode 100644
index 00000000000..adfd8e1298f
--- /dev/null
+++ b/changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml
@@ -0,0 +1,5 @@
+---
+title: Fix confidential issue label disclosure on milestone view
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-project-existence-disclosure-master.yml b/changelogs/unreleased/security-fix-project-existence-disclosure-master.yml
new file mode 100644
index 00000000000..084439c71d9
--- /dev/null
+++ b/changelogs/unreleased/security-fix-project-existence-disclosure-master.yml
@@ -0,0 +1,5 @@
+---
+title: Fix url redaction for issue links
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix_milestones_search_api_leak.yml b/changelogs/unreleased/security-fix_milestones_search_api_leak.yml
new file mode 100644
index 00000000000..5691550b602
--- /dev/null
+++ b/changelogs/unreleased/security-fix_milestones_search_api_leak.yml
@@ -0,0 +1,5 @@
+---
+title: 'Resolve: Milestones leaked via search API'
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml b/changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml
new file mode 100644
index 00000000000..df636ec37fb
--- /dev/null
+++ b/changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml
@@ -0,0 +1,5 @@
+---
+title: Add extra fields for handling basic auth on import by url page
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml b/changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml
new file mode 100644
index 00000000000..02773fa1d7c
--- /dev/null
+++ b/changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent bypass of restriction disabling web password sign in
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-unsubscribing-from-issue.yml b/changelogs/unreleased/security-unsubscribing-from-issue.yml
new file mode 100644
index 00000000000..3a33a457c69
--- /dev/null
+++ b/changelogs/unreleased/security-unsubscribing-from-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Hide confidential issue title on unsubscribe for anonymous users
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml b/changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml
new file mode 100644
index 00000000000..04eb035b157
--- /dev/null
+++ b/changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml
@@ -0,0 +1,5 @@
+---
+title: Make pipeline schedule worker resilient
+merge_request: 28407
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-add-backtrace-to-sql-queries.yml b/changelogs/unreleased/sh-add-backtrace-to-sql-queries.yml
new file mode 100644
index 00000000000..d4ca027d1b9
--- /dev/null
+++ b/changelogs/unreleased/sh-add-backtrace-to-sql-queries.yml
@@ -0,0 +1,5 @@
+---
+title: Add backtraces to Peek performance bar for SQL calls
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml b/changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml
new file mode 100644
index 00000000000..b089e6e4f37
--- /dev/null
+++ b/changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml
@@ -0,0 +1,5 @@
+---
+title: Add a column header to admin/jobs page
+merge_request: 28837
+author:
+type: other
diff --git a/changelogs/unreleased/sh-default-visibility-fix.yml b/changelogs/unreleased/sh-default-visibility-fix.yml
new file mode 100644
index 00000000000..8308f310150
--- /dev/null
+++ b/changelogs/unreleased/sh-default-visibility-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Set project default visibility to max allowed
+merge_request: 28754
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-fogbugz-import.yml b/changelogs/unreleased/sh-fix-fogbugz-import.yml
new file mode 100644
index 00000000000..1ac730fca24
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-fogbugz-import.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Fogbugz Importer not working
+merge_request: 29383
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-55869.yml b/changelogs/unreleased/sh-fix-issue-55869.yml
new file mode 100644
index 00000000000..7935cffc13b
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-55869.yml
@@ -0,0 +1,5 @@
+---
+title: Fix alignment of resend button in members page
+merge_request: 28202
+author:
+type: other
diff --git a/changelogs/unreleased/sh-fix-utf-8-encoding-resolve-conflicts.yml b/changelogs/unreleased/sh-fix-utf-8-encoding-resolve-conflicts.yml
new file mode 100644
index 00000000000..31039099788
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-utf-8-encoding-resolve-conflicts.yml
@@ -0,0 +1,5 @@
+---
+title: Fix UTF-8 conversion issues when resolving conflicts
+merge_request: 29453
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-project-import-visibility-error.yml b/changelogs/unreleased/sh-project-import-visibility-error.yml
deleted file mode 100644
index eb7e001c6aa..00000000000
--- a/changelogs/unreleased/sh-project-import-visibility-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix invalid visibility string comparison in project import
-merge_request: 28612
-author:
-type: fixed
diff --git a/changelogs/unreleased/t1.yml b/changelogs/unreleased/t1.yml
new file mode 100644
index 00000000000..6740375485d
--- /dev/null
+++ b/changelogs/unreleased/t1.yml
@@ -0,0 +1,5 @@
+---
+title: Set up git client in Jupyter installtion
+merge_request: 28783
+author: Amit Rathi
+type: added
diff --git a/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml b/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml
new file mode 100644
index 00000000000..3391fcc9537
--- /dev/null
+++ b/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml
@@ -0,0 +1,5 @@
+---
+title: Allow masking if 8 or more characters in base64.
+merge_request: 29143
+author: thomas-nilsson-irfu
+type: changed
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml
new file mode 100644
index 00000000000..dd71aa7d461
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.5.1
+merge_request: 28720
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml
new file mode 100644
index 00000000000..9ca6d18c2a8
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.5.2
+merge_request: 29050
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-shell-9-3-0.yml b/changelogs/unreleased/update-gitlab-shell-9-3-0.yml
new file mode 100644
index 00000000000..781ff31c7d8
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-shell-9-3-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update to GitLab Shell v9.3.0
+merge_request: 29283
+author:
+type: other
diff --git a/changelogs/unreleased/update-pages.yml b/changelogs/unreleased/update-pages.yml
new file mode 100644
index 00000000000..97a20b6b8fa
--- /dev/null
+++ b/changelogs/unreleased/update-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Pages to v1.6.0
+merge_request: 29048
+author:
+type: other
diff --git a/changelogs/unreleased/update-psd-doc.yml b/changelogs/unreleased/update-psd-doc.yml
new file mode 100644
index 00000000000..38c8d1c0d68
--- /dev/null
+++ b/changelogs/unreleased/update-psd-doc.yml
@@ -0,0 +1,5 @@
+---
+title: Update project security dashboard documentation
+merge_request: 28681
+author:
+type: changed
diff --git a/changelogs/unreleased/update-smileys-new.yml b/changelogs/unreleased/update-smileys-new.yml
new file mode 100644
index 00000000000..0e3f606c81b
--- /dev/null
+++ b/changelogs/unreleased/update-smileys-new.yml
@@ -0,0 +1,5 @@
+---
+title: Update new smiley icons, find n replace old names with new ones
+merge_request: 28338
+author: Jarek Ostrowski
+type: changed
diff --git a/changelogs/unreleased/variables-boolean-type.yml b/changelogs/unreleased/variables-boolean-type.yml
new file mode 100644
index 00000000000..7776657f60a
--- /dev/null
+++ b/changelogs/unreleased/variables-boolean-type.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: change masked attribute type to Boolean'
+merge_request: 28758
+author:
+type: other
diff --git a/changelogs/unreleased/weimeng-email-routing.yml b/changelogs/unreleased/weimeng-email-routing.yml
new file mode 100644
index 00000000000..6536433bd03
--- /dev/null
+++ b/changelogs/unreleased/weimeng-email-routing.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to define notification email addresses for groups you belong to.
+merge_request: 25299
+author:
+type: added
diff --git a/changelogs/unreleased/zj-remove-delta-island-feature-flag.yml b/changelogs/unreleased/zj-remove-delta-island-feature-flag.yml
new file mode 100644
index 00000000000..e752e01b701
--- /dev/null
+++ b/changelogs/unreleased/zj-remove-delta-island-feature-flag.yml
@@ -0,0 +1,3 @@
+merge_request: 28871
+title: Improve clone performance by using delta islands
+type: performance
diff --git a/config/database.yml.example b/config/database.yml.example
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/config/database.yml.example
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 494ddd72556..ac9b02b08d5 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -47,4 +47,7 @@ Rails.application.configure do
config.assets.quiet = true
config.allow_concurrency = defined?(::Puma)
+
+ # BetterErrors live shell (REPL) on every stack frame
+ BetterErrors::Middleware.allow_ip!("127.0.0.1/0")
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 23377b43f78..c83f569d885 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -752,6 +752,8 @@ production: &base
monitoring:
# Time between sampling of unicorn socket metrics, in seconds
# unicorn_sampler_interval: 10
+ # Time between sampling of Puma metrics, in seconds
+ # puma_sampler_interval: 5
# IP whitelist to access monitoring endpoints
ip_whitelist:
- 127.0.0.0/8
diff --git a/config/initializers/01_secret_token.rb b/config/initializers/01_secret_token.rb
index e24b5cbd510..9225a99a584 100644
--- a/config/initializers/01_secret_token.rb
+++ b/config/initializers/01_secret_token.rb
@@ -39,8 +39,7 @@ def create_tokens
secret_key_base: file_secret_key || generate_new_secure_token,
otp_key_base: env_secret_key || file_secret_key || generate_new_secure_token,
db_key_base: generate_new_secure_token,
- openid_connect_signing_key: generate_new_rsa_private_key,
- lets_encrypt_private_key: generate_lets_encrypt_private_key
+ openid_connect_signing_key: generate_new_rsa_private_key
}
missing_secrets = set_missing_keys(defaults)
@@ -61,10 +60,6 @@ def generate_new_rsa_private_key
OpenSSL::PKey::RSA.new(2048).to_pem
end
-def generate_lets_encrypt_private_key
- OpenSSL::PKey::RSA.new(4096).to_pem
-end
-
def warn_missing_secret(secret)
warn "Missing Rails.application.secrets.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml."
end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index d56bd7654af..0c8d94ccaed 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -491,6 +491,7 @@ Settings.webpack.dev_server['port'] ||= 3808
Settings['monitoring'] ||= Settingslogic.new({})
Settings.monitoring['ip_whitelist'] ||= ['127.0.0.1/8']
Settings.monitoring['unicorn_sampler_interval'] ||= 10
+Settings.monitoring['puma_sampler_interval'] ||= 5
Settings.monitoring['ruby_sampler_interval'] ||= 60
Settings.monitoring['sidekiq_exporter'] ||= Settingslogic.new({})
Settings.monitoring.sidekiq_exporter['enabled'] ||= false
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 8052880cc3d..4da683014d4 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -19,22 +19,22 @@ Gitlab::Application.configure do |config|
config.middleware.insert(1, Gitlab::Metrics::RequestsRackMiddleware)
end
-Sidekiq.configure_server do |config|
- config.on(:startup) do
- Gitlab::Metrics::SidekiqMetricsExporter.instance.start
- end
-end
-
if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
Gitlab::Cluster::LifecycleEvents.on_worker_start do
defined?(::Prometheus::Client.reinitialize_on_pid_change) && Prometheus::Client.reinitialize_on_pid_change
- unless Sidekiq.server?
+ if defined?(::Unicorn)
Gitlab::Metrics::Samplers::UnicornSampler.initialize_instance(Settings.monitoring.unicorn_sampler_interval).start
end
Gitlab::Metrics::Samplers::RubySampler.initialize_instance(Settings.monitoring.ruby_sampler_interval).start
end
+
+ if defined?(::Puma)
+ Gitlab::Cluster::LifecycleEvents.on_master_start do
+ Gitlab::Metrics::Samplers::PumaSampler.initialize_instance(Settings.monitoring.puma_sampler_interval).start
+ end
+ end
end
Gitlab::Cluster::LifecycleEvents.on_master_restart do
diff --git a/config/initializers/hipchat_client_patch.rb b/config/initializers/hipchat_client_patch.rb
index 1879ecb15fb..51bd48af320 100644
--- a/config/initializers/hipchat_client_patch.rb
+++ b/config/initializers/hipchat_client_patch.rb
@@ -2,14 +2,14 @@
# This monkey patches the HTTParty used in https://github.com/hipchat/hipchat-rb.
module HipChat
class Client
- connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ connection_adapter ::Gitlab::HTTPConnectionAdapter
end
class Room
- connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ connection_adapter ::Gitlab::HTTPConnectionAdapter
end
class User
- connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ connection_adapter ::Gitlab::HTTPConnectionAdapter
end
end
diff --git a/config/initializers/http_hostname_override.rb b/config/initializers/http_hostname_override.rb
new file mode 100644
index 00000000000..58dd380326f
--- /dev/null
+++ b/config/initializers/http_hostname_override.rb
@@ -0,0 +1,49 @@
+# This override allows passing `@hostname_override` to the SNI protocol,
+# which is used to lookup the correct SSL certificate in the
+# request handshake process.
+#
+# Given we've forced the HTTP request to be sent to the resolved
+# IP address in a few scenarios (e.g.: `Gitlab::HTTP` through
+# `Gitlab::UrlBlocker.validate!`), we need to provide the _original_
+# hostname via SNI in order to have a clean connection setup.
+#
+# This is ultimately needed in order to avoid DNS rebinding attacks
+# through HTTP requests.
+#
+class OpenSSL::SSL::SSLContext
+ attr_accessor :hostname_override
+end
+
+class OpenSSL::SSL::SSLSocket
+ module HostnameOverride
+ # rubocop: disable Gitlab/ModuleWithInstanceVariables
+ def hostname=(hostname)
+ super(@context.hostname_override || hostname)
+ end
+
+ def post_connection_check(hostname)
+ super(@context.hostname_override || hostname)
+ end
+ # rubocop: enable Gitlab/ModuleWithInstanceVariables
+ end
+
+ prepend HostnameOverride
+end
+
+class Net::HTTP
+ attr_accessor :hostname_override
+ SSL_IVNAMES << :@hostname_override
+ SSL_ATTRIBUTES << :hostname_override
+
+ module HostnameOverride
+ def addr_port
+ return super unless hostname_override
+
+ addr = hostname_override
+ default_port = use_ssl? ? Net::HTTP.https_default_port : Net::HTTP.http_default_port
+ default_port == port ? addr : "#{addr}:#{port}"
+ end
+ end
+
+ prepend HostnameOverride
+end
diff --git a/config/initializers/rack_attack_logging.rb b/config/initializers/rack_attack_logging.rb
index 8bb9ea29c33..2a3fdc8de5f 100644
--- a/config/initializers/rack_attack_logging.rb
+++ b/config/initializers/rack_attack_logging.rb
@@ -1,7 +1,15 @@
+# frozen_string_literal: true
+#
# Adds logging for all Rack Attack blocks and throttling events.
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
if [:throttle, :blacklist].include? req.env['rack.attack.match_type']
- Rails.logger.info("Rack_Attack: #{req.env['rack.attack.match_type']} #{req.ip} #{req.request_method} #{req.fullpath}")
+ Gitlab::AuthLogger.error(
+ message: 'Rack_Attack',
+ env: req.env['rack.attack.match_type'],
+ ip: req.ip,
+ request_method: req.request_method,
+ fullpath: req.fullpath
+ )
end
end
diff --git a/config/initializers/rack_timeout.rb b/config/initializers/rack_timeout.rb
new file mode 100644
index 00000000000..5c4f2dd708c
--- /dev/null
+++ b/config/initializers/rack_timeout.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# Unicorn terminates any request which runs longer than 60 seconds.
+# Puma doesn't have any timeout mechanism for terminating long-running
+# requests, to make sure that server is not paralyzed by long-running
+# or stuck queries, we add a request timeout which terminates the
+# request after 60 seconds. This may be dangerous in some situations
+# (https://github.com/heroku/rack-timeout/blob/master/doc/exceptions.md)
+# and it's used only as the last resort. In such case this termination is
+# logged and we should fix the potential timeout issue in the code itself.
+
+if defined?(::Puma) && !Rails.env.test?
+ require 'rack/timeout/base'
+
+ Gitlab::Application.configure do |config|
+ config.middleware.insert_before(Rack::Runtime, Rack::Timeout,
+ service_timeout: 60,
+ wait_timeout: 90)
+ end
+
+ observer = Gitlab::RackTimeoutObserver.new
+ Rack::Timeout.register_state_change_observer(:gitlab_rack_timeout, &observer.callback)
+end
diff --git a/config/karma.config.js b/config/karma.config.js
index b2fc3a32816..2a5bf3581e0 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -9,11 +9,22 @@ const IS_EE = require('./helpers/is_ee_env');
const ROOT_PATH = path.resolve(__dirname, '..');
const SPECS_PATH = /^(?:\.[\\\/])?(ee[\\\/])?spec[\\\/]javascripts[\\\/]/;
-function fatalError(message) {
+function exitError(message) {
console.error(chalk.red(`\nError: ${message}\n`));
process.exit(1);
}
+function exitWarn(message) {
+ console.error(chalk.yellow(`\nWarn: ${message}\n`));
+ process.exit(0);
+}
+
+function exit(message, isError = true) {
+ const fn = isError ? exitError : exitWarn;
+
+ fn(message);
+}
+
// disable problematic options
webpackConfig.entry = undefined;
webpackConfig.mode = 'development';
@@ -31,7 +42,8 @@ webpackConfig.plugins.push(
}),
);
-const specFilters = argumentsParser
+const options = argumentsParser
+ .option('--no-fail-on-empty-test-suite')
.option(
'-f, --filter-spec [filter]',
'Filter run spec files by path. Multiple filters are like a logical OR.',
@@ -41,7 +53,9 @@ const specFilters = argumentsParser
},
[],
)
- .parse(process.argv).filterSpec;
+ .parse(process.argv);
+
+const specFilters = options.filterSpec;
const createContext = (specFiles, regex, suffix) => {
const newContext = specFiles.reduce((context, file) => {
@@ -73,11 +87,13 @@ if (specFilters.length) {
filteredSpecFiles = [...new Set(filteredSpecFiles)];
if (filteredSpecFiles.length < 1) {
- fatalError('Your filter did not match any test files.');
+ const isError = options.failOnEmptyTestSuite;
+
+ exit('Your filter did not match any test files.', isError);
}
if (!filteredSpecFiles.every(file => SPECS_PATH.test(file))) {
- fatalError('Test files must be located within /spec/javascripts.');
+ exitError('Test files must be located within /spec/javascripts.');
}
const CE_FILES = filteredSpecFiles.filter(file => !file.startsWith('ee'));
diff --git a/config/puma.example.development.rb b/config/puma.example.development.rb
index 490c940077a..9df24bf74e3 100644
--- a/config/puma.example.development.rb
+++ b/config/puma.example.development.rb
@@ -42,7 +42,6 @@ bind 'unix:///home/git/gitlab.socket'
workers 2
require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
-require_relative "/home/git/gitlab/lib/gitlab/cluster/puma_worker_killer_initializer"
on_restart do
# Signal application hooks that we're about to restart
diff --git a/config/routes.rb b/config/routes.rb
index f5957f43655..cb90a0134c4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -75,6 +75,8 @@ Rails.application.routes.draw do
resources :issues, module: :boards, only: [:index, :update]
end
+ get 'acme-challenge/' => 'acme_challenges#show'
+
# UserCallouts
resources :user_callouts, only: [:create]
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index bc19219a0b8..ae79beb1dba 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -83,7 +83,7 @@ namespace :admin do
resources(:projects,
path: '/',
constraints: { id: Gitlab::PathRegex.project_route_regex },
- only: [:show]) do
+ only: [:show, :destroy]) do
member do
put :transfer
diff --git a/config/routes/import.rb b/config/routes/import.rb
index 24013eb2c88..9fe2688de1e 100644
--- a/config/routes/import.rb
+++ b/config/routes/import.rb
@@ -67,4 +67,6 @@ namespace :import do
get :jobs
post :upload
end
+
+ resource :phabricator, only: [:create, :new], controller: :phabricator
end
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index c1cac3905f1..0e213b0b989 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -17,7 +17,11 @@ resource :profile, only: [:show, :update] do
delete :unlink
end
end
- resource :notifications, only: [:show, :update]
+
+ resource :notifications, only: [:show, :update] do
+ resources :groups, only: :update
+ end
+
resource :password, only: [:new, :create, :edit, :update] do
member do
put :reset
diff --git a/config/routes/project.rb b/config/routes/project.rb
index fdc2a3c0086..a1e769f6ca3 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -26,24 +26,150 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
module: :projects,
as: :project) do
- resources :autocomplete_sources, only: [] do
- collection do
- get 'members'
- get 'issues'
- get 'merge_requests'
- get 'labels'
- get 'milestones'
- get 'commands'
- get 'snippets'
+ # Begin of the /-/ scope.
+ # Use this scope for all new project routes.
+ scope '-' do
+ get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
+
+ resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
+ collection do
+ resources :artifacts, only: [] do
+ collection do
+ get :latest_succeeded,
+ path: '*ref_name_and_path',
+ format: false
+ end
+ end
+ end
+
+ member do
+ get :status
+ post :cancel
+ post :unschedule
+ post :retry
+ post :play
+ post :erase
+ get :trace, defaults: { format: 'json' }
+ get :raw
+ get :terminal
+ get '/terminal.ws/authorize', to: 'jobs#terminal_websocket_authorize', constraints: { format: nil }
+ end
+
+ resource :artifacts, only: [] do
+ get :download
+ get :browse, path: 'browse(/*path)', format: false
+ get :file, path: 'file/*path', format: false
+ get :raw, path: 'raw/*path', format: false
+ post :keep
+ end
+ end
+
+ namespace :ci do
+ resource :lint, only: [:show, :create]
+ end
+
+ namespace :settings do
+ get :members, to: redirect("%{namespace_id}/%{project_id}/project_members")
+
+ resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
+ post :reset_cache
+ put :reset_registration_token
+ end
+
+ resource :operations, only: [:show, :update]
+ resource :integrations, only: [:show]
+
+ resource :repository, only: [:show], controller: :repository do
+ post :create_deploy_token, path: 'deploy_token/create'
+ post :cleanup
+ end
+ end
+
+ resources :autocomplete_sources, only: [] do
+ collection do
+ get 'members'
+ get 'issues'
+ get 'merge_requests'
+ get 'labels'
+ get 'milestones'
+ get 'commands'
+ get 'snippets'
+ end
+ end
+
+ resources :project_members, except: [:show, :new, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+]+} }, concerns: :access_requestable do
+ collection do
+ delete :leave
+
+ # Used for import team
+ # from another project
+ get :import
+ post :apply_import
+ end
+
+ member do
+ post :resend_invite
+ end
+ end
+
+ resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create, :edit, :update] do
+ member do
+ put :enable
+ put :disable
+ end
end
+
+ resources :deploy_tokens, constraints: { id: /\d+/ }, only: [] do
+ member do
+ put :revoke
+ end
+ end
+
+ resources :milestones, constraints: { id: /\d+/ } do
+ member do
+ post :promote
+ put :sort_issues
+ put :sort_merge_requests
+ get :merge_requests
+ get :participants
+ get :labels
+ end
+ end
+
+ resources :labels, except: [:show], constraints: { id: /\d+/ } do
+ collection do
+ post :generate
+ post :set_priorities
+ end
+
+ member do
+ post :promote
+ post :toggle_subscription
+ delete :remove_priority
+ end
+ end
+
+ resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
+ member do
+ put :test
+ end
+ end
+
+ resources :boards, only: [:index, :show], constraints: { id: /\d+/ }
+ resources :releases, only: [:index]
+ resources :forks, only: [:index, :new, :create]
+ resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
+
+ resource :import, only: [:new, :create, :show]
+ resource :avatar, only: [:show, :destroy]
end
+ # End of the /-/ scope.
#
# Templates
#
get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: %r{[^/]+} }
- resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
member do
get :branches
@@ -70,12 +196,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :services, constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
- member do
- put :test
- end
- end
-
resource :mattermost, only: [:new, :create]
namespace :prometheus do
@@ -84,28 +204,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create, :edit, :update] do
- member do
- put :enable
- put :disable
- end
- end
-
- resources :deploy_tokens, constraints: { id: /\d+/ }, only: [] do
- member do
- put :revoke
- end
- end
-
- resources :releases, only: [:index]
- resources :forks, only: [:index, :new, :create]
- resource :import, only: [:new, :create, :show]
-
resources :merge_requests, concerns: :awardable, except: [:new, :create], constraints: { id: /\d+/ } do
member do
get :commit_change_content
post :merge
- post :cancel_merge_when_pipeline_succeeds
+ post :cancel_auto_merge
get :pipeline_status
get :ci_environments_status
post :toggle_subscription
@@ -227,7 +330,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :metrics_dashboard
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
- get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#proxy'
+ get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#proxy', as: :prometheus_api
end
collection do
@@ -267,47 +370,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :functions, only: [:index]
end
- scope '-' do
- get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
-
- resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
- collection do
- resources :artifacts, only: [] do
- collection do
- get :latest_succeeded,
- path: '*ref_name_and_path',
- format: false
- end
- end
- end
-
- member do
- get :status
- post :cancel
- post :unschedule
- post :retry
- post :play
- post :erase
- get :trace, defaults: { format: 'json' }
- get :raw
- get :terminal
- get '/terminal.ws/authorize', to: 'jobs#terminal_websocket_authorize', constraints: { format: nil }
- end
-
- resource :artifacts, only: [] do
- get :download
- get :browse, path: 'browse(/*path)', format: false
- get :file, path: 'file/*path', format: false
- get :raw, path: 'raw/*path', format: false
- post :keep
- end
- end
-
- namespace :ci do
- resource :lint, only: [:show, :create]
- end
- end
-
draw :legacy_builds
resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do
@@ -337,31 +399,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :milestones, constraints: { id: /\d+/ } do
- member do
- post :promote
- put :sort_issues
- put :sort_merge_requests
- get :merge_requests
- get :participants
- get :labels
- end
- end
-
- resources :labels, except: [:show], constraints: { id: /\d+/ } do
- collection do
- post :generate
- post :set_priorities
- end
-
- member do
- post :promote
- post :toggle_subscription
- delete :remove_priority
- end
- end
-
get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics }
+
resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
@@ -373,29 +412,13 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post :create_merge_request
get :discussions, format: :json
end
+
collection do
post :bulk_update
post :import_csv
end
end
- resources :project_members, except: [:show, :new, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+]+} }, concerns: :access_requestable do
- collection do
- delete :leave
-
- # Used for import team
- # from another project
- get :import
- post :apply_import
- end
-
- member do
- post :resend_invite
- end
- end
-
- resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
-
resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
member do
delete :delete_attachment
@@ -406,8 +429,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
- resources :boards, only: [:index, :show], constraints: { id: /\d+/ }
-
resources :todos, only: [:create]
resources :uploads, only: [:create] do
@@ -443,25 +464,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- scope '-' do
- namespace :settings do
- get :members, to: redirect("%{namespace_id}/%{project_id}/project_members")
-
- resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do
- post :reset_cache
- put :reset_registration_token
- end
-
- resource :operations, only: [:show, :update]
- resource :integrations, only: [:show]
-
- resource :repository, only: [:show], controller: :repository do
- post :create_deploy_token, path: 'deploy_token/create'
- post :cleanup
- end
- end
- end
-
resources :error_tracking, only: [:index], controller: :error_tracking do
collection do
post :list_projects
@@ -507,7 +509,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
constraints: { project_id: Gitlab::PathRegex.project_route_regex },
module: :projects,
as: :project) do
- Gitlab::Routing.redirect_legacy_paths(self, :settings, :branches, :tags, :network, :graphs)
+ Gitlab::Routing.redirect_legacy_paths(self, :settings, :branches, :tags,
+ :network, :graphs, :autocomplete_sources,
+ :project_members, :deploy_keys, :deploy_tokens,
+ :labels, :milestones, :services, :boards, :releases,
+ :forks, :group_links, :import, :avatar)
end
end
end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 8bc2426ec4c..fd9ce4d3374 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -30,6 +30,7 @@
- [pipeline_default, 3]
- [pipeline_cache, 3]
- [deployment, 3]
+ - [auto_merge, 3]
- [pipeline_hooks, 2]
- [gitlab_shell, 2]
- [email_receiver, 2]
@@ -91,3 +92,4 @@
- [chat_notification, 2]
- [migrate_external_diffs, 1]
- [update_project_statistics, 1]
+ - [phabricator_import_import_tasks, 1]
diff --git a/config/webpack.config.review_toolbar.js b/config/webpack.config.review_toolbar.js
new file mode 100644
index 00000000000..baaba7ed387
--- /dev/null
+++ b/config/webpack.config.review_toolbar.js
@@ -0,0 +1,58 @@
+const path = require('path');
+const CompressionPlugin = require('compression-webpack-plugin');
+
+const ROOT_PATH = path.resolve(__dirname, '..');
+const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
+const NO_SOURCEMAPS = process.env.NO_SOURCEMAPS;
+const IS_PRODUCTION = process.env.NODE_ENV === 'production';
+
+const devtool = IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map';
+
+const alias = {
+ vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
+ spec: path.join(ROOT_PATH, 'spec/javascripts'),
+};
+
+module.exports = {
+ mode: IS_PRODUCTION ? 'production' : 'development',
+
+ context: path.join(ROOT_PATH, 'app/assets/javascripts'),
+
+ name: 'visual_review_toolbar',
+
+ entry: './visual_review_toolbar',
+
+ output: {
+ path: path.join(ROOT_PATH, 'public/assets/webpack'),
+ filename: 'visual_review_toolbar.js',
+ library: 'VisualReviewToolbar',
+ libraryTarget: 'var',
+ },
+
+ resolve: {
+ alias,
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ loader: 'babel-loader',
+ options: {
+ cacheDirectory: path.join(CACHE_PATH, 'babel-loader'),
+ },
+ },
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader'],
+ },
+ ],
+ },
+
+ plugins: [
+ // compression can require a lot of compute time and is disabled in CI
+ new CompressionPlugin(),
+ ].filter(Boolean),
+
+ devtool: NO_SOURCEMAPS ? false : devtool,
+};
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index 048c539bcf9..bdb4343b1d6 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -163,10 +163,10 @@ def lint_commit(commit)
end
if emoji_checker.includes_emoji?(commit.message)
- fail_commit(
+ warn_commit(
commit,
'Avoid the use of Markdown Emoji such as `:+1:`. ' \
- 'These add no value to the commit message, ' \
+ 'These add limited value to the commit message, ' \
'and are displayed as plain text outside of GitLab'
)
diff --git a/danger/plugins/helper.rb b/danger/plugins/helper.rb
index 581c0720083..2d7a933e801 100644
--- a/danger/plugins/helper.rb
+++ b/danger/plugins/helper.rb
@@ -1,8 +1,5 @@
# frozen_string_literal: true
-require 'net/http'
-require 'yaml'
-
require_relative '../../lib/gitlab/danger/helper'
module Danger
diff --git a/danger/plugins/roulette.rb b/danger/plugins/roulette.rb
new file mode 100644
index 00000000000..7c62cff0c92
--- /dev/null
+++ b/danger/plugins/roulette.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require_relative '../../lib/gitlab/danger/roulette'
+
+module Danger
+ class Roulette < Plugin
+ # Put the helper code somewhere it can be tested
+ include Gitlab::Danger::Roulette
+ end
+end
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
index 62e5526c02b..a0f1447e76a 100644
--- a/danger/roulette/Dangerfile
+++ b/danger/roulette/Dangerfile
@@ -32,53 +32,26 @@ for them.
MARKDOWN
def spin_for_category(team, project, category, branch_name)
- rng = Random.new(Digest::MD5.hexdigest(branch_name).to_i(16))
-
- reviewers = team.select { |member| member.reviewer?(project, category) }
- traintainers = team.select { |member| member.traintainer?(project, category) }
- maintainers = team.select { |member| member.maintainer?(project, category) }
+ random = roulette.new_random(branch_name)
+ labels = gitlab.mr_labels
+
+ reviewers, traintainers, maintainers =
+ %i[reviewer? traintainer? maintainer?].map do |kind|
+ team.select do |member|
+ member.public_send(kind, project, category, labels) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
# TODO: take CODEOWNERS into account?
# https://gitlab.com/gitlab-org/gitlab-ce/issues/57653
# Make traintainers have triple the chance to be picked as a reviewer
- reviewer = spin_for_person(reviewers + traintainers + traintainers, random: rng)
- maintainer = spin_for_person(maintainers, random: rng)
+ reviewer = roulette.spin_for_person(reviewers + traintainers + traintainers, random: random)
+ maintainer = roulette.spin_for_person(maintainers, random: random)
"| #{helper.label_for_category(category)} | #{reviewer&.markdown_name} | #{maintainer&.markdown_name} |"
end
-# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
-# selection will change on next spin
-def spin_for_person(people, random:)
- person = nil
- people = people.dup
-
- people.size.times do
- person = people.sample(random: random)
-
- break person unless out_of_office?(person)
-
- people -= [person]
- end
-
- person
-end
-
-def out_of_office?(person)
- username = CGI.escape(person.username)
- api_endpoint = "https://gitlab.com/api/v4/users/#{username}/status"
- response = HTTParty.get(api_endpoint) # rubocop:disable Gitlab/HTTParty
-
- if response.code == 200
- response["message"]&.match(/OOO/i)
- else
- false # this is no worse than not checking for OOO
- end
-rescue
- false
-end
-
def build_list(items)
list = items.map { |filename| "* `#{filename}`" }.join("\n")
@@ -101,14 +74,12 @@ categories = changes.keys - [:unknown]
# disable the review roulette for such MRs.
if changes.any? && !gitlab.mr_labels.include?('single codebase') && !gitlab.mr_labels.include?('CSS cleanup')
# Strip leading and trailing CE/EE markers
- canonical_branch_name = gitlab
- .mr_json['source_branch']
- .gsub(/^[ce]e-/, '')
- .gsub(/-[ce]e$/, '')
+ canonical_branch_name =
+ roulette.canonical_branch_name(gitlab.mr_json['source_branch'])
team =
begin
- helper.project_team
+ roulette.project_team(helper.project_name)
rescue => err
warn("Reviewer roulette failed to load team data: #{err.message}")
[]
diff --git a/danger/single_codebase/Dangerfile b/danger/single_codebase/Dangerfile
index a5938cd6783..d1f538bec7f 100644
--- a/danger/single_codebase/Dangerfile
+++ b/danger/single_codebase/Dangerfile
@@ -1,29 +1,36 @@
+def new_teammates(usernames)
+ usernames.map { |u| ::Gitlab::Danger::Teammate.new('username' => u) }
+end
+
def mention_single_codebase_approvers
- frontend_maintainers = %w(@filipa @iamphill)
- backend_maintainers = %w(@rspeicher @rymai @yorickpeterse @godfat)
+ canonical_branch_name =
+ roulette.canonical_branch_name(gitlab.mr_json['source_branch'])
+
+ random = roulette.new_random(canonical_branch_name)
+
+ frontend_maintainers = new_teammates(%w[filipa iamphill])
+ backend_maintainers = new_teammates(%w[rspeicher rymai yorickpeterse godfat])
rows = []
- users = []
if gitlab.mr_labels.include?('frontend')
- frontend_maintainer = frontend_maintainers.sample
+ frontend_maintainer =
+ roulette.spin_for_person(frontend_maintainers, random: random)
- rows << "| ~frontend | `#{frontend_maintainer}`"
- users << frontend_maintainer
+ rows << "| ~frontend | #{frontend_maintainer.markdown_name}"
end
if gitlab.mr_labels.include?('backend')
- backend_maintainer = backend_maintainers.sample
+ backend_maintainer =
+ roulette.spin_for_person(backend_maintainers, random: random)
- rows << "| ~backend | `#{backend_maintainer}`"
- users << backend_maintainer
+ rows << "| ~backend | #{backend_maintainer.markdown_name}"
end
if rows.empty?
backup_maintainer = backend_maintainers.sample
- rows << "| ~frontend / ~backend | `#{backup_maintainer}`"
- users << backup_maintainer
+ rows << "| ~frontend / ~backend | #{backup_maintainer.markdown_name}"
end
markdown(<<~MARKDOWN.strip)
diff --git a/db/migrate/20180702134423_generate_missing_routes.rb b/db/migrate/20180702134423_generate_missing_routes.rb
index a440bc3179c..dd1106c9e6a 100644
--- a/db/migrate/20180702134423_generate_missing_routes.rb
+++ b/db/migrate/20180702134423_generate_missing_routes.rb
@@ -98,6 +98,7 @@ class GenerateMissingRoutes < ActiveRecord::Migration[4.2]
class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
+ self.inheritance_column = :_type_disabled
include EachBatch
include GenerateMissingRoutes::Routable
diff --git a/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
new file mode 100644
index 00000000000..2f3069032a1
--- /dev/null
+++ b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddNotificationEmailToNotificationSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :notification_settings, :notification_email, :string
+ end
+end
diff --git a/db/migrate/20190429082448_create_pages_domain_acme_orders.rb b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
new file mode 100644
index 00000000000..af811e83518
--- /dev/null
+++ b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreatePagesDomainAcmeOrders < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :pages_domain_acme_orders do |t|
+ t.references :pages_domain, null: false, index: true, foreign_key: { on_delete: :cascade }, type: :integer
+
+ t.datetime_with_timezone :expires_at, null: false
+ t.timestamps_with_timezone null: false
+
+ t.string :url, null: false
+
+ t.string :challenge_token, null: false, index: true
+ t.text :challenge_file_content, null: false
+
+ t.text :encrypted_private_key, null: false
+ t.text :encrypted_private_key_iv, null: false
+ end
+ end
+end
diff --git a/db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb b/db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb
new file mode 100644
index 00000000000..e1d3cca48d6
--- /dev/null
+++ b/db/migrate/20190516151857_add_lets_encrypt_private_key_to_application_settings.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLetsEncryptPrivateKeyToApplicationSettings < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :encrypted_lets_encrypt_private_key, :text
+ add_column :application_settings, :encrypted_lets_encrypt_private_key_iv, :text
+ end
+end
diff --git a/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb b/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb
new file mode 100644
index 00000000000..eba154df496
--- /dev/null
+++ b/db/migrate/20190516155724_change_packages_size_defaults_in_project_statistics.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class ChangePackagesSizeDefaultsInProjectStatistics < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ change_column_default :project_statistics, :packages_size, 0
+
+ update_column_in_batches(:project_statistics, :packages_size, 0) do |table, query|
+ query.where(table[:packages_size].eq(nil))
+ end
+
+ change_column_null :project_statistics, :packages_size, false
+ end
+
+ def down
+ change_column_null :project_statistics, :packages_size, true
+ change_column_default :project_statistics, :packages_size, nil
+ end
+end
diff --git a/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb b/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb
new file mode 100644
index 00000000000..86fe09d7573
--- /dev/null
+++ b/db/migrate/20190523112344_limit_milestone_date_years_to_4_digits.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class LimitMilestoneDateYearsTo4Digits < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # When a migration requires downtime you **must** uncomment the following
+ # constant and define a short and easy to understand explanation as to why the
+ # migration requires downtime.
+ # DOWNTIME_REASON = ''
+
+ # When using the methods "add_concurrent_index", "remove_concurrent_index" or
+ # "add_column_with_default" you must disable the use of transactions
+ # as these methods can not run in an existing transaction.
+ # When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
+ # that either of them is the _only_ method called in the migration,
+ # any other changes should go in a separate migration.
+ # This ensures that upon failure _only_ the index creation or removing fails
+ # and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ Milestone.where("start_date > '9999-12-31'").update_all(
+ "start_date = '9999-12-31'"
+ )
+ Milestone.where("due_date > '9999-12-31'").update_all(
+ "due_date = '9999-12-31'"
+ )
+ end
+end
diff --git a/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb
new file mode 100644
index 00000000000..ae93a76575a
--- /dev/null
+++ b/db/migrate/20190524062810_generate_lets_encrypt_private_key.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class GenerateLetsEncryptPrivateKey < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # we now generate this key on the fly, but since this migration was merged to master, we don't remove it
+ def up
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb b/db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb
new file mode 100644
index 00000000000..18544dcb6d3
--- /dev/null
+++ b/db/migrate/20190524071727_add_ssl_valid_period_to_pages_domain.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddSslValidPeriodToPagesDomain < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :pages_domains, :certificate_valid_not_before, :datetime_with_timezone
+ add_column :pages_domains, :certificate_valid_not_after, :datetime_with_timezone
+ end
+end
diff --git a/db/migrate/20190527194830_add_wiki_size_to_statistics.rb b/db/migrate/20190527194830_add_wiki_size_to_statistics.rb
new file mode 100644
index 00000000000..d4f16cdec18
--- /dev/null
+++ b/db/migrate/20190527194830_add_wiki_size_to_statistics.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddWikiSizeToStatistics < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :project_statistics, :wiki_size, :bigint
+ end
+end
diff --git a/db/migrate/20190529142545_add_dns_rebinding_protection_enabled_to_application_settings.rb b/db/migrate/20190529142545_add_dns_rebinding_protection_enabled_to_application_settings.rb
new file mode 100644
index 00000000000..8835dc8b7ba
--- /dev/null
+++ b/db/migrate/20190529142545_add_dns_rebinding_protection_enabled_to_application_settings.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddDnsRebindingProtectionEnabledToApplicationSettings < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:application_settings, :dns_rebinding_protection_enabled,
+ :boolean,
+ default: true,
+ allow_null: false)
+ end
+
+ def down
+ remove_column(:application_settings, :dns_rebinding_protection_enabled)
+ end
+end
diff --git a/db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb b/db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb
new file mode 100644
index 00000000000..8abea05def4
--- /dev/null
+++ b/db/migrate/20190530042141_add_default_git_depth_to_ci_cd_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddDefaultGitDepthToCiCdSettings < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :project_ci_cd_settings, :default_git_depth, :integer
+ end
+end
diff --git a/db/migrate/20190530154715_add_index_to_merge_requests_state_and_merge_status.rb b/db/migrate/20190530154715_add_index_to_merge_requests_state_and_merge_status.rb
new file mode 100644
index 00000000000..e669f81ca35
--- /dev/null
+++ b/db/migrate/20190530154715_add_index_to_merge_requests_state_and_merge_status.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIndexToMergeRequestsStateAndMergeStatus < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_requests, [:state, :merge_status],
+ where: "state = 'opened' AND merge_status = 'can_be_merged'"
+ end
+
+ def down
+ remove_concurrent_index :merge_requests, [:state, :merge_status]
+ end
+end
diff --git a/db/migrate/20190611161641_add_target_project_id_to_merge_trains.rb b/db/migrate/20190611161641_add_target_project_id_to_merge_trains.rb
new file mode 100644
index 00000000000..c200208e4c3
--- /dev/null
+++ b/db/migrate/20190611161641_add_target_project_id_to_merge_trains.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class AddTargetProjectIdToMergeTrains < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ # rubocop: disable Rails/NotNullColumn
+ add_reference :merge_trains, :target_project, null: false, index: true, foreign_key: { on_delete: :cascade, to_table: :projects }, type: :integer
+ add_column :merge_trains, :target_branch, :text, null: false
+ # rubocop: enable Rails/NotNullColumn
+ end
+end
diff --git a/db/post_migrate/20190522143720_drop_project_auto_devops_domain.rb b/db/post_migrate/20190522143720_drop_project_auto_devops_domain.rb
new file mode 100644
index 00000000000..36278d83927
--- /dev/null
+++ b/db/post_migrate/20190522143720_drop_project_auto_devops_domain.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class DropProjectAutoDevopsDomain < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ remove_column :project_auto_devops, :domain, :string
+ end
+end
diff --git a/db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb b/db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb
new file mode 100644
index 00000000000..1d8510e4514
--- /dev/null
+++ b/db/post_migrate/20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ScheduleFillValidTimeForPagesDomainCertificates < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ MIGRATION = 'FillValidTimeForPagesDomainCertificate'
+ BATCH_SIZE = 500
+ BATCH_TIME = 5.minutes
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class PagesDomain < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'pages_domains'
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ PagesDomain.where.not(certificate: [nil, '']),
+ MIGRATION,
+ BATCH_TIME,
+ batch_size: BATCH_SIZE)
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb b/db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb
new file mode 100644
index 00000000000..04cf5906b61
--- /dev/null
+++ b/db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class ScheduleCalculateWikiSizes < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ MIGRATION = 'CalculateWikiSizes'
+ BATCH_SIZE = 100000
+ BATCH_TIME = 5.minutes
+
+ class ProjectStatistics < ActiveRecord::Base
+ self.table_name = 'project_statistics'
+
+ scope :without_wiki_size, -> { where(wiki_size: nil) }
+
+ include ::EachBatch
+ end
+
+ disable_ddl_transaction!
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ ::ScheduleCalculateWikiSizes::ProjectStatistics.without_wiki_size,
+ MIGRATION,
+ BATCH_TIME,
+ batch_size: BATCH_SIZE)
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20190528180441_enqueue_reset_merge_status.rb b/db/post_migrate/20190528180441_enqueue_reset_merge_status.rb
new file mode 100644
index 00000000000..a3d2f497806
--- /dev/null
+++ b/db/post_migrate/20190528180441_enqueue_reset_merge_status.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class EnqueueResetMergeStatus < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10_000
+ MIGRATION = 'ResetMergeStatus'
+ DELAY_INTERVAL = 5.minutes.to_i
+
+ disable_ddl_transaction!
+
+ def up
+ say 'Scheduling `ResetMergeStatus` jobs'
+
+ # We currently have more than ~5_000_000 merge request records on GitLab.com.
+ # This means it'll schedule ~500 jobs (10k MRs each) with a 5 minutes gap,
+ # so this should take ~41 hours for all background migrations to complete.
+ # ((5_000_000 / 10_000) * 5) / 60 => 41.6666..
+ queue_background_migration_jobs_by_range_at_intervals(MergeRequest, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 412b5313b69..1755730bb72 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: 20190516011213) do
+ActiveRecord::Schema.define(version: 20190611161641) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -191,6 +191,9 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.boolean "lets_encrypt_terms_of_service_accepted", default: false, null: false
t.integer "elasticsearch_shards", default: 5, null: false
t.integer "elasticsearch_replicas", default: 1, null: false
+ t.text "encrypted_lets_encrypt_private_key"
+ t.text "encrypted_lets_encrypt_private_key_iv"
+ t.boolean "dns_rebinding_protection_enabled", default: true, null: false
t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree
end
@@ -1359,6 +1362,7 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.index ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)", using: :btree
t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree
+ t.index ["state", "merge_status"], name: "index_merge_requests_on_state_and_merge_status", where: "(((state)::text = 'opened'::text) AND ((merge_status)::text = 'can_be_merged'::text))", using: :btree
t.index ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)", using: :btree
@@ -1383,8 +1387,11 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.integer "pipeline_id"
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
+ t.integer "target_project_id", null: false
+ t.text "target_branch", null: false
t.index ["merge_request_id"], name: "index_merge_trains_on_merge_request_id", unique: true, using: :btree
t.index ["pipeline_id"], name: "index_merge_trains_on_pipeline_id", using: :btree
+ t.index ["target_project_id"], name: "index_merge_trains_on_target_project_id", using: :btree
t.index ["user_id"], name: "index_merge_trains_on_user_id", using: :btree
end
@@ -1514,6 +1521,7 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.boolean "success_pipeline"
t.boolean "push_to_merge_request"
t.boolean "issue_due"
+ t.string "notification_email"
t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true, using: :btree
t.index ["user_id"], name: "index_notification_settings_on_user_id", using: :btree
@@ -1566,6 +1574,20 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.index ["access_grant_id"], name: "index_oauth_openid_requests_on_access_grant_id", using: :btree
end
+ create_table "pages_domain_acme_orders", force: :cascade do |t|
+ t.integer "pages_domain_id", null: false
+ t.datetime_with_timezone "expires_at", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.string "url", null: false
+ t.string "challenge_token", null: false
+ t.text "challenge_file_content", null: false
+ t.text "encrypted_private_key", null: false
+ t.text "encrypted_private_key_iv", null: false
+ t.index ["challenge_token"], name: "index_pages_domain_acme_orders_on_challenge_token", using: :btree
+ t.index ["pages_domain_id"], name: "index_pages_domain_acme_orders_on_pages_domain_id", using: :btree
+ end
+
create_table "pages_domains", id: :serial, force: :cascade do |t|
t.integer "project_id"
t.text "certificate"
@@ -1578,6 +1600,8 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.datetime_with_timezone "enabled_until"
t.datetime_with_timezone "remove_at"
t.boolean "auto_ssl_enabled", default: false, null: false
+ t.datetime_with_timezone "certificate_valid_not_before"
+ t.datetime_with_timezone "certificate_valid_not_after"
t.index ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree
t.index ["project_id", "enabled_until"], name: "index_pages_domains_on_project_id_and_enabled_until", using: :btree
t.index ["project_id"], name: "index_pages_domains_on_project_id", using: :btree
@@ -1630,7 +1654,6 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.boolean "enabled"
- t.string "domain"
t.integer "deploy_strategy", default: 0, null: false
t.index ["project_id"], name: "index_project_auto_devops_on_project_id", unique: true, using: :btree
end
@@ -1640,6 +1663,7 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.boolean "group_runners_enabled", default: true, null: false
t.boolean "merge_pipelines_enabled"
t.boolean "merge_trains_enabled", default: false, null: false
+ t.integer "default_git_depth"
t.index ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true, using: :btree
end
@@ -1742,7 +1766,8 @@ ActiveRecord::Schema.define(version: 20190516011213) do
t.bigint "repository_size", default: 0, null: false
t.bigint "lfs_objects_size", default: 0, null: false
t.bigint "build_artifacts_size", default: 0, null: false
- t.bigint "packages_size"
+ t.bigint "packages_size", default: 0, null: false
+ t.bigint "wiki_size"
t.index ["namespace_id"], name: "index_project_statistics_on_namespace_id", using: :btree
t.index ["project_id"], name: "index_project_statistics_on_project_id", unique: true, using: :btree
end
@@ -2548,6 +2573,7 @@ ActiveRecord::Schema.define(version: 20190516011213) do
add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade
add_foreign_key "merge_trains", "ci_pipelines", column: "pipeline_id", on_delete: :nullify
add_foreign_key "merge_trains", "merge_requests", on_delete: :cascade
+ add_foreign_key "merge_trains", "projects", column: "target_project_id", on_delete: :cascade
add_foreign_key "merge_trains", "users", on_delete: :cascade
add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade
add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade
@@ -2555,6 +2581,7 @@ ActiveRecord::Schema.define(version: 20190516011213) do
add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade
add_foreign_key "notification_settings", "users", name: "fk_0c95e91db7", on_delete: :cascade
add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
+ add_foreign_key "pages_domain_acme_orders", "pages_domains", on_delete: :cascade
add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade
add_foreign_key "personal_access_tokens", "users"
add_foreign_key "pool_repositories", "projects", column: "source_project_id", on_delete: :nullify
diff --git a/doc/administration/auth/google_secure_ldap.md b/doc/administration/auth/google_secure_ldap.md
index 65a51fc4aa0..760af0cfd1a 100644
--- a/doc/administration/auth/google_secure_ldap.md
+++ b/doc/administration/auth/google_secure_ldap.md
@@ -27,7 +27,7 @@ The steps below cover:
'Entire domain (GitLab)' or 'Selected organizational units' for both 'Verify user
credentials' and 'Read user information'. Select 'Add LDAP Client'
- TIP: **Tip:** If you plan to use GitLab [LDAP Group Sync](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)
+ TIP: **Tip:** If you plan to use GitLab [LDAP Group Sync](ldap-ee.md#group-sync)
, turn on 'Read group information'.
![Add LDAP Client Step 2](img/google_secure_ldap_add_step_2.png)
diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md
index e55f7dbb4df..00422ec347c 100644
--- a/doc/administration/auth/oidc.md
+++ b/doc/administration/auth/oidc.md
@@ -31,6 +31,7 @@ The OpenID Connect will provide you with a client details and secret for you to
{ 'name' => 'openid_connect',
'label' => '<your_oidc_label>',
'args' => {
+ "name' => 'openid_connect',
'scope' => ['openid','profile'],
'response_type' => 'code',
'issuer' => '<your_oidc_url>',
@@ -53,6 +54,7 @@ The OpenID Connect will provide you with a client details and secret for you to
- { name: 'openid_connect',
label: '<your_oidc_label>',
args: {
+ name: 'openid_connect',
scope: ['openid','profile'],
response_type: 'code',
issuer: '<your_oidc_url>',
@@ -103,3 +105,59 @@ On the sign in page, there should now be an OpenID Connect icon below the regula
Click the icon to begin the authentication process. The OpenID Connect provider will ask the user to
sign in and authorize the GitLab application (if confirmation required by the client). If everything goes well, the user
will be redirected to GitLab and will be signed in.
+
+## Example configurations
+
+The following configurations illustrate how to set up OpenID with
+different providers with Omnibus GitLab.
+
+### Google
+
+See the [Google
+documentation](https://developers.google.com/identity/protocols/OpenIDConnect)
+for more details:
+
+```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ 'name' => 'openid_connect',
+ 'label' => 'Google OpenID',
+ 'args' => {
+ 'name' => 'openid_connect',
+ 'scope' => ['openid', 'profile', 'email'],
+ 'response_type' => 'code',
+ 'issuer' => 'https://accounts.google.com',
+ 'client_auth_method' => 'query',
+ 'discovery' => true,
+ 'uid_field' => 'preferred_username',
+ 'client_options' => {
+ 'identifier' => '<YOUR PROJECT CLIENT ID>',
+ 'secret' => '<YOUR PROJECT CLIENT SECRET>',
+ 'redirect_uri' => 'https://example.com/users/auth/openid_connect/callback',
+ }
+ }
+ }
+```
+
+### Troubleshooting
+
+If you're having trouble, here are some tips:
+
+1. Ensure `discovery` is set to `true`. Setting it to `false` requires
+specifying all the URLs and keys required to make OpenID work.
+
+1. Check your system clock to ensure the time is synchronized properly.
+
+1. As mentioned in [the
+documentation](https://github.com/m0n9oose/omniauth_openid_connect),
+make sure `issuer` corresponds to the base URL of the Discovery URL. For
+example, `https://accounts.google.com` is used for the URL
+`https://accounts.google.com/.well-known/openid-configuration`.
+
+1. The OpenID Connect client uses HTTP Basic Authentication to send the
+OAuth2 access token. For example, if you are seeing 401 errors upon
+retrieving the `userinfo` endpoint, you may want to check your OpenID
+Web server configuration. For example, for
+[oauth2-server-php](https://github.com/bshaffer/oauth2-server-php), you
+may need to [add a configuration parameter to
+Apache](https://github.com/bshaffer/oauth2-server-php/issues/926#issuecomment-387502778).
diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md
index 9c13ff772b3..246addb6dc9 100644
--- a/doc/administration/compliance.md
+++ b/doc/administration/compliance.md
@@ -12,7 +12,7 @@ GitLab’s [security features](../security/README.md) may also help you meet rel
|**[Email all users of a project, group, or entire server](../user/admin_area/settings/terms.md)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Starter+||
|**[Omnibus package supports log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-forwarding)**<br>Forward your logs to a central system.|Starter+||
|**[Lock project membership to group](../user/group/index.md#member-lock-starter)**<br>Group owners can prevent new members from being added to projects within a group.|Starter+|✓|
-|**[LDAP group sync](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Starter+||
-|**[LDAP group sync filters](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition Premium gives more flexibility to synchronize with LDAP based on filters, meaning you can leverage LDAP attributes to map GitLab permissions.|Premium+||
+|**[LDAP group sync](auth/ldap-ee.md#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Starter+||
+|**[LDAP group sync filters](auth/ldap-ee.md#group-sync)**<br>GitLab Enterprise Edition Premium gives more flexibility to synchronize with LDAP based on filters, meaning you can leverage LDAP attributes to map GitLab permissions.|Premium+||
|**[Audit logs](audit_events.md)**<br>To maintain the integrity of your code, GitLab Enterprise Edition Premium gives admins the ability to view any modifications made within the GitLab server in an advanced audit log system, so you can control, analyze and track every change.|Premium+||
|**[Auditor users](auditor_users.md)**<br>Auditor users are users who are given read-only access to all projects, groups, and other resources on the GitLab instance.|Premium+||
diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md
index 288cb1bf0bb..113514e1ee8 100644
--- a/doc/administration/custom_hooks.md
+++ b/doc/administration/custom_hooks.md
@@ -5,11 +5,11 @@ Custom Git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
Please explore [webhooks] and [CI] as an option if you do not
have filesystem access. For a user configurable Git hook interface, see
-[Push Rules](https://docs.gitlab.com/ee/push_rules/push_rules.html),
+[Push Rules](../push_rules/push_rules.md),
available in GitLab Enterprise Edition.
NOTE: **Note:**
-Custom Git hooks won't be replicated to secondary nodes if you use [GitLab Geo][gitlab-geo]
+Custom Git hooks won't be replicated to secondary nodes if you use [GitLab Geo](geo/replication/index.md)
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
@@ -123,6 +123,5 @@ exit 1
[CI]: ../ci/README.md
[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks
[webhooks]: ../user/project/integrations/webhooks.md
-[gitlab-geo]: https://docs.gitlab.com/ee/administration/geo/replication/index.html
[5073]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5073
[93]: https://gitlab.com/gitlab-org/gitlab-shell/merge_requests/93
diff --git a/doc/administration/geo/disaster_recovery/background_verification.md b/doc/administration/geo/disaster_recovery/background_verification.md
index c7299b6e196..d4c8c2d3624 100644
--- a/doc/administration/geo/disaster_recovery/background_verification.md
+++ b/doc/administration/geo/disaster_recovery/background_verification.md
@@ -29,12 +29,7 @@ the node more time before scheduling a planned failover.
Run the following commands in a Rails console on the **primary** node:
```sh
-# Omnibus GitLab
gitlab-rails console
-
-# Installation from source
-cd /home/git/gitlab
-sudo -u git -H bin/rails console RAILS_ENV=production
```
To check if automatic background verification is enabled:
@@ -102,12 +97,7 @@ disable if you need. Run the following commands in a Rails console on the
**primary** node:
```sh
-# Omnibus GitLab
gitlab-rails console
-
-# Installation from source
-cd /home/git/gitlab
-sudo -u git -H bin/rails console RAILS_ENV=production
```
To disable automatic background re-verification:
@@ -131,31 +121,15 @@ to be resynced without the backoff period:
For repositories:
-- Omnibus Installation
-
- ```sh
- sudo gitlab-rake geo:verification:repository:reset
- ```
-
-- Source Installation
-
- ```sh
- sudo -u git -H bundle exec rake geo:verification:repository:reset RAILS_ENV=production
- ```
+```sh
+sudo gitlab-rake geo:verification:repository:reset
+```
For wikis:
-- Omnibus Installation
-
- ```sh
- sudo gitlab-rake geo:verification:wiki:reset
- ```
-
-- Source Installation
-
- ```sh
- sudo -u git -H bundle exec rake geo:verification:wiki:reset RAILS_ENV=production
- ```
+```sh
+sudo gitlab-rake geo:verification:wiki:reset
+```
## Reconcile differences with checksum mismatches
@@ -169,7 +143,7 @@ If the **primary** and **secondary** nodes have a checksum verification mismatch
1. On the project admin page get the **Gitaly storage name**, and **Gitaly relative path**:
![Project admin page](img/checksum-differences-admin-project-page.png)
-1. Navigate to the project's repository directory on both **primary** and **secondary** nodes. For an installation from source, the path is usually `/home/git/repositories`. For Omnibus installs, the path is usually `/var/opt/gitlab/git-data/repositories`. Note that if `git_data_dirs` is customized, check the directory layout on your server to be sure.
+1. Navigate to the project's repository directory on both **primary** and **secondary** nodes (the path is usually `/var/opt/gitlab/git-data/repositories`). Note that if `git_data_dirs` is customized, check the directory layout on your server to be sure.
```sh
cd /var/opt/gitlab/git-data/repositories
diff --git a/doc/administration/geo/disaster_recovery/planned_failover.md b/doc/administration/geo/disaster_recovery/planned_failover.md
index 88ab12d910a..b8071b5993f 100644
--- a/doc/administration/geo/disaster_recovery/planned_failover.md
+++ b/doc/administration/geo/disaster_recovery/planned_failover.md
@@ -113,7 +113,7 @@ If any objects are failing to replicate, this should be investigated before
scheduling the maintenance window. Following a planned failover, anything that
failed to replicate will be **lost**.
-You can use the [Geo status API](https://docs.gitlab.com/ee/api/geo_nodes.html#retrieve-project-sync-or-verification-failures-that-occurred-on-the-current-node) to review failed objects and
+You can use the [Geo status API](../../../api/geo_nodes.md#retrieve-project-sync-or-verification-failures-that-occurred-on-the-current-node) to review failed objects and
the reasons for failure.
A common cause of replication failures is the data being missing on the
diff --git a/doc/administration/geo/replication/configuration.md b/doc/administration/geo/replication/configuration.md
index 57735b21cda..3d4f69d3abe 100644
--- a/doc/administration/geo/replication/configuration.md
+++ b/doc/administration/geo/replication/configuration.md
@@ -1,9 +1,4 @@
-# Geo configuration (GitLab Omnibus) **[PREMIUM ONLY]**
-
-NOTE: **Note:**
-This is the documentation for the Omnibus GitLab packages. For installations
-from source, follow the [**Geo nodes configuration for installations
-from source**][configuration-source] guide.
+# Geo configuration **[PREMIUM ONLY]**
## Configuring a new **secondary** node
@@ -303,7 +298,6 @@ See the [updating the Geo nodes document](updating_the_geo_nodes.md).
See the [troubleshooting document](troubleshooting.md).
-[configuration-source]: configuration_source.md
[setup-geo-omnibus]: index.md#using-omnibus-gitlab
[Hashed Storage]: ../../repository_storage_types.md
[Disaster Recovery]: ../disaster_recovery/index.md
diff --git a/doc/administration/geo/replication/configuration_source.md b/doc/administration/geo/replication/configuration_source.md
deleted file mode 100644
index 10dd9405b4b..00000000000
--- a/doc/administration/geo/replication/configuration_source.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# Geo configuration (source) **[PREMIUM ONLY]**
-
-NOTE: **Note:**
-This documentation applies to GitLab source installations. In GitLab 11.5, this documentation was deprecated and will be removed in a future release.
-Please consider [migrating to GitLab Omnibus install](https://docs.gitlab.com/omnibus/update/convert_to_omnibus.html). For installations
-using the Omnibus GitLab packages, follow the
-[**Omnibus Geo nodes configuration**][configuration] guide.
-
-## Configuring a new **secondary** node
-
-NOTE: **Note:**
-This is the final step in setting up a **secondary** node. Stages of the setup
-process must be completed in the documented order. Before attempting the steps
-in this stage, [complete all prior stages](index.md#using-gitlab-installed-from-source-deprecated).
-
-The basic steps of configuring a **secondary** node are to:
-
-- Replicate required configurations between the **primary** and **secondary** nodes.
-- Configure a tracking database on each **secondary** node.
-- Start GitLab on the **secondary** node.
-
-You are encouraged to first read through all the steps before executing them
-in your testing/production environment.
-
-NOTE: **Note:**
-**Do not** set up any custom authentication on **secondary** nodes, this will be handled by the **primary** node.
-
-NOTE: **Note:**
-**Do not** add anything in the **secondary** node's admin area (**Admin Area > Geo**). This is handled solely by the **primary** node.
-
-### Step 1. Manually replicate secret GitLab values
-
-GitLab stores a number of secret values in the `/home/git/gitlab/config/secrets.yml`
-file which *must* match between the **primary** and **secondary** nodes. Until there is
-a means of automatically replicating these between nodes (see [gitlab-org/gitlab-ee#3789]), they must
-be manually replicated to **secondary** nodes.
-
-1. SSH into the **primary** node, and execute the command below:
-
- ```sh
- sudo cat /home/git/gitlab/config/secrets.yml
- ```
-
- This will display the secrets that need to be replicated, in YAML format.
-
-1. SSH into the **secondary** node and login as the `git` user:
-
- ```sh
- sudo -i -u git
- ```
-
-1. Make a backup of any existing secrets:
-
- ```sh
- mv /home/git/gitlab/config/secrets.yml /home/git/gitlab/config/secrets.yml.`date +%F`
- ```
-
-1. Copy `/home/git/gitlab/config/secrets.yml` from the **primary** node to the **secondary** node, or
- copy-and-paste the file contents between nodes:
-
- ```sh
- sudo editor /home/git/gitlab/config/secrets.yml
-
- # paste the output of the `cat` command you ran on the primary
- # save and exit
- ```
-
-1. Ensure the file permissions are correct:
-
- ```sh
- chown git:git /home/git/gitlab/config/secrets.yml
- chmod 0600 /home/git/gitlab/config/secrets.yml
- ```
-
-1. Restart GitLab
-
- ```sh
- service gitlab restart
- ```
-
-Once restarted, the **secondary** node will automatically start replicating missing data
-from the **primary** node in a process known as backfill. Meanwhile, the **primary** node
-will start to notify the **secondary** node of any changes, so that the **secondary** node can
-act on those notifications immediately.
-
-Make sure the **secondary** node is running and accessible. You can login to
-the **secondary** node with the same credentials as used for the **primary** node.
-
-### Step 2. Manually replicate the **primary** node's SSH host keys
-
-Read [Manually replicate the **primary** node's SSH host keys](configuration.md#step-2-manually-replicate-the-primary-nodes-ssh-host-keys)
-
-### Step 3. Add the **secondary** GitLab node
-
-1. Navigate to the **primary** node's **Admin Area > Geo**
- (`/admin/geo/nodes`) in your browser.
-1. Add the **secondary** node by providing its full URL. **Do NOT** check the
- **This is a primary node** checkbox.
-1. Optionally, choose which namespaces should be replicated by the
- **secondary** node. Leave blank to replicate all. Read more in
- [selective synchronization](#selective-synchronization).
-1. Click the **Add node** button.
-1. SSH into your GitLab **secondary** server and restart the services:
-
- ```sh
- service gitlab restart
- ```
-
- Check if there are any common issue with your Geo setup by running:
-
- ```sh
- bundle exec rake gitlab:geo:check
- ```
-
-1. SSH into your GitLab **primary** server and login as root to verify the
- **secondary** node is reachable or there are any common issue with your Geo setup:
-
- ```sh
- bundle exec rake gitlab:geo:check
- ```
-
-Once reconfigured, the **secondary** node will automatically start
-replicating missing data from the **primary** node in a process known as backfill.
-Meanwhile, the **primary** node will start to notify the **secondary** node of any changes, so
-that the **secondary** node can act on those notifications immediately.
-
-Make sure the **secondary** node is running and accessible.
-You can log in to the **secondary** node with the same credentials as used for the **primary** node.
-
-### Step 4. Enabling Hashed Storage
-
-Read [Enabling Hashed Storage](configuration.md#step-4-enabling-hashed-storage).
-
-### Step 5. (Optional) Configuring the secondary to trust the primary
-
-You can safely skip this step if your **primary** node uses a CA-issued HTTPS certificate.
-
-If your **primary** node is using a self-signed certificate for *HTTPS* support, you will
-need to add that certificate to the **secondary** node's trust store. Retrieve the
-certificate from the **primary** node and follow your distribution's instructions for
-adding it to the **secondary** node's trust store. In Debian/Ubuntu, you would follow these steps:
-
-```sh
-sudo -i
-cp <primary_node_certification_file> /usr/local/share/ca-certificates
-update-ca-certificates
-```
-
-### Step 6. Enable Git access over HTTP/HTTPS
-
-Geo synchronizes repositories over HTTP/HTTPS, and therefore requires this clone
-method to be enabled. Navigate to **Admin Area > Settings**
-(`/admin/application_settings`) on the **primary** node, and set
-`Enabled Git access protocols` to `Both SSH and HTTP(S)` or `Only HTTP(S)`.
-
-### Step 7. Verify proper functioning of the secondary node
-
-Read [Verify proper functioning of the secondary node][configuration-verify-node].
-
-## Selective synchronization
-
-Read [Selective synchronization][configuration-selective-replication].
-
-## Troubleshooting
-
-Read the [troubleshooting document][troubleshooting].
-
-[gitlab-org/gitlab-ee#3789]: https://gitlab.com/gitlab-org/gitlab-ee/issues/3789
-[configuration]: configuration.md
-[configuration-selective-replication]: configuration.md#selective-synchronization
-[configuration-verify-node]: configuration.md#step-7-verify-proper-functioning-of-the-secondary-node
-[troubleshooting]: troubleshooting.md
diff --git a/doc/administration/geo/replication/database.md b/doc/administration/geo/replication/database.md
index e57583a3bf9..1e5a56c3f4e 100644
--- a/doc/administration/geo/replication/database.md
+++ b/doc/administration/geo/replication/database.md
@@ -1,9 +1,7 @@
-# Geo database replication (GitLab Omnibus) **[PREMIUM ONLY]**
+# Geo database replication **[PREMIUM ONLY]**
NOTE: **Note:**
-This is the documentation for the Omnibus GitLab packages. For installations
-from source, follow the
-[Geo database replication (source)](database_source.md) guide.
+The following steps are for Omnibus installs only. Using Geo with source-based installs was **deprecated** in GitLab 11.5.
NOTE: **Note:**
If your GitLab installation uses external (not managed by Omnibus) PostgreSQL
@@ -102,10 +100,15 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
else.
If you are using an external database not managed by Omnibus GitLab, you need
- to create the replicator user and define a password to it manually.
- For information on how to create a replication user, refer to the
- [appropriate step](database_source.md#step-1-configure-the-primary-server)
- in [Geo database replication (source)](database_source.md).
+ to create the replicator user and define a password to it manually:
+
+ ```sql
+ --- Create a new user 'replicator'
+ CREATE USER gitlab_replicator;
+
+ --- Set/change a password and grants replication privilege
+ ALTER USER gitlab_replicator WITH REPLICATION ENCRYPTED PASSWORD '<replication_password>';
+ ```
1. Configure PostgreSQL to listen on network interfaces:
@@ -340,7 +343,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
##
## Secondary address
- ## - replace '<secondary_node_ip>' with the public or VPC address of your Geo secondary node
+ ## - replace '<secondary_node_ip>' with the public or VPC address of your Geo secondary node
##
postgresql['listen_address'] = '<secondary_node_ip>'
postgresql['md5_auth_cidr_addresses'] = ['<secondary_node_ip>/32']
@@ -383,8 +386,7 @@ the database on the **primary** node, replicates the database, and creates the
needed files for streaming replication.
The directories used are the defaults that are set up in Omnibus. If you have
-changed any defaults or are using a source installation, configure it as you
-see fit replacing the directories and paths.
+changed any defaults, configure it as you see fit replacing the directories and paths.
CAUTION: **Warning:**
Make sure to run this on the **secondary** server as it removes all PostgreSQL's
@@ -443,8 +445,7 @@ The replication process is now complete.
PostgreSQL connections. We recommend using PGBouncer if you use GitLab in a
high-availability configuration with a cluster of nodes supporting a Geo
**primary** node and another cluster of nodes supporting a Geo **secondary** node. For more
-information, see the [Omnibus HA](https://docs.gitlab.com/ee/administration/high_availability/database.html#configure-using-omnibus-for-high-availability)
-documentation.
+information, see [High Availability with GitLab Omnibus](../../high_availability/database.md#high-availability-with-gitlab-omnibus-premium-only).
For a Geo **secondary** node to work properly with PGBouncer in front of the database,
it will need a separate read-only user to make [PostgreSQL FDW queries][FDW]
diff --git a/doc/administration/geo/replication/database_source.md b/doc/administration/geo/replication/database_source.md
deleted file mode 100644
index 67cf8b6535f..00000000000
--- a/doc/administration/geo/replication/database_source.md
+++ /dev/null
@@ -1,439 +0,0 @@
-# Geo database replication (source) **[PREMIUM ONLY]**
-
-NOTE: **Note:**
-This documentation applies to GitLab source installations. In GitLab 11.5, this documentation was deprecated and will be removed in a future release.
-Please consider [migrating to GitLab Omnibus install](https://docs.gitlab.com/omnibus/update/convert_to_omnibus.html). For installations
-using the Omnibus GitLab packages, follow the
-[**database replication for Omnibus GitLab**][database] guide.
-
-NOTE: **Note:**
-The stages of the setup process must be completed in the documented order.
-Before attempting the steps in this stage, [complete all prior stages](index.md#using-gitlab-installed-from-source-deprecated).
-
-This document describes the minimal steps you have to take in order to
-replicate your **primary** GitLab database to a **secondary** node's database. You may
-have to change some values according to your database setup, how big it is, etc.
-
-You are encouraged to first read through all the steps before executing them
-in your testing/production environment.
-
-## PostgreSQL replication
-
-The GitLab **primary** node where the write operations happen will connect to
-**primary** database server, and the **secondary** ones which are read-only will
-connect to **secondary** database servers (which are read-only too).
-
-NOTE: **Note:**
-In many databases' documentation, you will see "**primary**" being referenced as "master"
-and "**secondary**" as either "slave" or "standby" server (read-only).
-
-We recommend using [PostgreSQL replication slots][replication-slots-article]
-to ensure the **primary** node retains all the data necessary for the secondaries to
-recover. See below for more details.
-
-The following guide assumes that:
-
-- You are using PostgreSQL 9.6 or later which includes the
- [`pg_basebackup` tool][pgback] and improved [Foreign Data Wrapper][FDW] support.
-- You have a **primary** node already set up (the GitLab server you are
- replicating from), running PostgreSQL 9.6 or later, and
- you have a new **secondary** server set up with the same versions of the OS,
- PostgreSQL, and GitLab on all nodes.
-- The IP of the **primary** server for our examples is `198.51.100.1`, whereas the
- **secondary** node's IP is `198.51.100.2`. Note that the **primary** and **secondary** servers
- **must** be able to communicate over these addresses. These IP addresses can either
- be public or private.
-
-CAUTION: **Warning:**
-Geo works with streaming replication. Logical replication is not supported at this time.
-There is an [issue where support is being discussed](https://gitlab.com/gitlab-org/gitlab-ee/issues/7420).
-
-### Step 1. Configure the **primary** server
-
-1. SSH into your GitLab **primary** server and login as root:
-
- ```sh
- sudo -i
- ```
-
-1. Add this node as the Geo **primary** by running:
-
- ```sh
- bundle exec rake geo:set_primary_node
- ```
-
-1. Create a [replication user] named `gitlab_replicator`:
-
- ```sql
- --- Create a new user 'replicator'
- CREATE USER gitlab_replicator;
-
- --- Set/change a password and grants replication privilege
- ALTER USER gitlab_replicator WITH REPLICATION ENCRYPTED PASSWORD '<replication_password>';
- ```
-
-1. Make sure your the `gitlab` database user has a password defined:
-
- ```sh
- sudo \
- -u postgres psql \
- -d template1 \
- -c "ALTER USER gitlab WITH ENCRYPTED PASSWORD '<database_password>';"
- ```
-
-1. Edit the content of `database.yml` in `production:` and add the password like the example below:
-
- ```yaml
- #
- # PRODUCTION
- #
- production:
- adapter: postgresql
- encoding: unicode
- database: gitlabhq_production
- pool: 10
- username: gitlab
- password: <database_password>
- host: /var/opt/gitlab/geo-postgresql
- ```
-
-1. Set up TLS support for the PostgreSQL **primary** server:
-
- CAUTION: **Warning**:
- Only skip this step if you **know** that PostgreSQL traffic
- between the **primary** and **secondary** nodes will be secured through some other
- means, e.g., a known-safe physical network path or a site-to-site VPN that
- you have configured.
-
- If you are replicating your database across the open Internet, it is
- **essential** that the connection is TLS-secured. Correctly configured, this
- provides protection against both passive eavesdroppers and active
- "man-in-the-middle" attackers.
-
- To generate a self-signed certificate and key, run this command:
-
- ```sh
- openssl req \
- -nodes \
- -batch \
- -x509 \
- -newkey rsa:4096 \
- -keyout server.key \
- -out server.crt \
- -days 3650
- ```
-
- This will create two files - `server.key` and `server.crt` - that you can
- use for authentication.
-
- Copy them to the correct location for your PostgreSQL installation:
-
- ```sh
- # Copying a self-signed certificate and key
- install -o postgres -g postgres -m 0400 -T server.crt ~postgres/9.x/main/data/server.crt
- install -o postgres -g postgres -m 0400 -T server.key ~postgres/9.x/main/data/server.key
- ```
-
- Add this configuration to `postgresql.conf`, removing any existing
- configuration for `ssl_cert_file` or `ssl_key_file`:
-
- ```
- ssl = on
- ssl_cert_file='server.crt'
- ssl_key_file='server.key'
- ```
-
-1. Edit `postgresql.conf` to configure the **primary** server for streaming replication
- (for Debian/Ubuntu that would be `/etc/postgresql/9.x/main/postgresql.conf`):
-
- ```
- listen_address = '<primary_node_ip>'
- wal_level = hot_standby
- max_wal_senders = 5
- min_wal_size = 80MB
- max_wal_size = 1GB
- max_replicaton_slots = 1 # Number of Geo secondary nodes
- wal_keep_segments = 10
- hot_standby = on
- ```
-
- NOTE: **Note**:
- Be sure to set `max_replication_slots` to the number of Geo **secondary**
- nodes that you may potentially have (at least 1).
-
- For security reasons, PostgreSQL by default only listens on the local
- interface (e.g. 127.0.0.1). However, Geo needs to communicate
- between the **primary** and **secondary** nodes over a common network, such as a
- corporate LAN or the public Internet. For this reason, we need to
- configure PostgreSQL to listen on more interfaces.
-
- The `listen_address` option opens PostgreSQL up to external connections
- with the interface corresponding to the given IP. See [the PostgreSQL
- documentation][pg-docs-runtime-conn] for more details.
-
- You may also want to edit the `wal_keep_segments` and `max_wal_senders` to
- match your database replication requirements. Consult the
- [PostgreSQL - Replication documentation][pg-docs-runtime-replication] for more information.
-
-1. Set the access control on the **primary** node to allow TCP connections using the
- server's public IP and set the connection from the **secondary** node to require a
- password. Edit `pg_hba.conf` (for Debian/Ubuntu that would be
- `/etc/postgresql/9.x/main/pg_hba.conf`):
-
- ```sh
- host all all <primary_node_ip>/32 md5
- host replication gitlab_replicator <secondary_node_ip>/32 md5
- ```
-
- If you want to add another secondary, add one more row like the replication
- one and change the IP address:
-
- ```sh
- host all all <primary_node_ip>/32 md5
- host replication gitlab_replicator <secondary_node_ip>/32 md5
- host replication gitlab_replicator <another_secondary_node_ip>/32 md5
- ```
-
-1. Restart PostgreSQL for the changes to take effect.
-
-1. Choose a database-friendly name to use for your secondary to use as the
- replication slot name. For example, if your domain is
- `secondary.geo.example.com`, you may use `secondary_example` as the slot
- name.
-
-1. Create the replication slot on the **primary** node:
-
- ```sh
- $ sudo -u postgres psql -c "SELECT * FROM pg_create_physical_replication_slot('secondary_example');"
- slot_name | xlog_position
- ------------------+---------------
- secondary_example |
- (1 row)
- ```
-
-1. Now that the PostgreSQL server is set up to accept remote connections, run
- `netstat -plnt` to make sure that PostgreSQL is listening to the server's
- public IP.
-
-### Step 2. Configure the secondary server
-
-Follow the first steps in ["configure the secondary server"][database-replication] and note that since you are installing from source, the username and
-group listed as `gitlab-psql` in those steps should be replaced by `postgres`
-instead. After completing the "Test that the `gitlab-psql` user can connect to
-the **primary** node's database" step, continue here:
-
-1. Edit `postgresql.conf` to configure the secondary for streaming replication
- (for Debian/Ubuntu that would be `/etc/postgresql/9.*/main/postgresql.conf`):
-
- ```sh
- wal_level = hot_standby
- max_wal_senders = 5
- checkpoint_segments = 10
- wal_keep_segments = 10
- hot_standby = on
- ```
-
-1. Restart PostgreSQL for the changes to take effect.
-
-#### Enable tracking database on the secondary server
-
-Geo secondary nodes use a tracking database to keep track of replication status
-and recover automatically from some replication issues. Follow the steps below to create
-the tracking database.
-
-1. On the secondary node, run the following command to create `database_geo.yml` with the
- information of your secondary PostgreSQL instance:
-
- ```sh
- sudo cp /home/git/gitlab/config/database_geo.yml.postgresql /home/git/gitlab/config/database_geo.yml
- ```
-
-1. Edit the content of `database_geo.yml` in `production:` as in the example below:
-
- ```yaml
- #
- # PRODUCTION
- #
- production:
- adapter: postgresql
- encoding: unicode
- database: gitlabhq_geo_production
- pool: 10
- username: gitlab_geo
- # password:
- host: /var/opt/gitlab/geo-postgresql
- ```
-
-1. Create the database `gitlabhq_geo_production` on the PostgreSQL instance of the **secondary** node.
-
-1. Set up the Geo tracking database:
-
- ```sh
- bundle exec rake geo:db:migrate
- ```
-
-1. Configure the [PostgreSQL FDW][FDW] connection and credentials:
-
- Save the script below in a file, ex. `/tmp/geo_fdw.sh` and modify the connection
- params to match your environment. Execute it to set up the FDW connection.
-
- ```sh
- #!/bin/bash
-
- # Secondary Database connection params:
- DB_HOST="/var/opt/gitlab/postgresql" # change to the public IP or VPC private IP if its an external server
- DB_NAME="gitlabhq_production"
- DB_USER="gitlab"
- DB_PORT="5432"
-
- # Tracking Database connection params:
- GEO_DB_HOST="/var/opt/gitlab/geo-postgresql" # change to the public IP or VPC private IP if its an external server
- GEO_DB_NAME="gitlabhq_geo_production"
- GEO_DB_USER="gitlab_geo"
- GEO_DB_PORT="5432"
-
- query_exec () {
- gitlab-psql -h $GEO_DB_HOST -d $GEO_DB_NAME -p $GEO_DB_PORT -c "${1}"
- }
-
- query_exec "CREATE EXTENSION postgres_fdw;"
- query_exec "CREATE SERVER gitlab_secondary FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '${DB_HOST}', dbname '${DB_NAME}', port '${DB_PORT}');"
- query_exec "CREATE USER MAPPING FOR ${GEO_DB_USER} SERVER gitlab_secondary OPTIONS (user '${DB_USER}');"
- query_exec "CREATE SCHEMA gitlab_secondary;"
- query_exec "GRANT USAGE ON FOREIGN SERVER gitlab_secondary TO ${GEO_DB_USER};"
- ```
-
- And edit the content of `database_geo.yml` and to add `fdw: true` to
- the `production:` block.
-
-### Step 3. Initiate the replication process
-
-Below we provide a script that connects the database on the **secondary** node to
-the database on the **primary** node, replicates the database, and creates the
-needed files for streaming replication.
-
-The directories used are the defaults for Debian/Ubuntu. If you have changed
-any defaults, configure it as you see fit replacing the directories and paths.
-
-CAUTION: **Warning:**
-Make sure to run this on the **secondary** server as it removes all PostgreSQL's
-data before running `pg_basebackup`.
-
-1. SSH into your GitLab **secondary** server and login as root:
-
- ```sh
- sudo -i
- ```
-
-1. Save the snippet below in a file, let's say `/tmp/replica.sh`. Modify the
- embedded paths if necessary:
-
- ```
- #!/bin/bash
-
- PORT="5432"
- USER="gitlab_replicator"
- echo ---------------------------------------------------------------
- echo WARNING: Make sure this script is run from the secondary server
- echo ---------------------------------------------------------------
- echo
- echo Enter the IP or FQDN of the primary PostgreSQL server
- read HOST
- echo Enter the password for $USER@$HOST
- read -s PASSWORD
- echo Enter the required sslmode
- read SSLMODE
-
- echo Stopping PostgreSQL and all GitLab services
- sudo service gitlab stop
- sudo service postgresql stop
-
- echo Backing up postgresql.conf
- sudo -u postgres mv /var/opt/gitlab/postgresql/data/postgresql.conf /var/opt/gitlab/postgresql/
-
- echo Cleaning up old cluster directory
- sudo -u postgres rm -rf /var/opt/gitlab/postgresql/data
-
- echo Starting base backup as the replicator user
- echo Enter the password for $USER@$HOST
- sudo -u postgres /opt/gitlab/embedded/bin/pg_basebackup -h $HOST -D /var/opt/gitlab/postgresql/data -U gitlab_replicator -v -x -P
-
- echo Writing recovery.conf file
- sudo -u postgres bash -c "cat > /var/opt/gitlab/postgresql/data/recovery.conf <<- _EOF1_
- standby_mode = 'on'
- primary_conninfo = 'host=$HOST port=$PORT user=$USER password=$PASSWORD sslmode=$SSLMODE'
- _EOF1_
- "
-
- echo Restoring postgresql.conf
- sudo -u postgres mv /var/opt/gitlab/postgresql/postgresql.conf /var/opt/gitlab/postgresql/data/
-
- echo Starting PostgreSQL
- sudo service postgresql start
- ```
-
-1. Run it with:
-
- ```sh
- bash /tmp/replica.sh
- ```
-
- When prompted, enter the IP/FQDN of the **primary** node, and the password you set up
- for the `gitlab_replicator` user in the first step.
-
- You should use `verify-ca` for the `sslmode`. You can use `disable` if you
- are happy to skip PostgreSQL TLS authentication altogether (e.g., you know
- the network path is secure, or you are using a site-to-site VPN). This is
- **not** safe over the public Internet!
-
- You can read more details about each `sslmode` in the
- [PostgreSQL documentation][pg-docs-ssl];
- the instructions above are carefully written to ensure protection against
- both passive eavesdroppers and active "man-in-the-middle" attackers.
-
-The replication process is now over.
-
-## PGBouncer support (optional)
-
-1. First, enter the PostgreSQL console as an admin user.
-
-1. Then create the read-only user:
-
- ```sql
- -- NOTE: Use the password defined earlier
- CREATE USER gitlab_geo_fdw WITH password '<your_password_here>';
- GRANT CONNECT ON DATABASE gitlabhq_production to gitlab_geo_fdw;
- GRANT USAGE ON SCHEMA public TO gitlab_geo_fdw;
- GRANT SELECT ON ALL TABLES IN SCHEMA public TO gitlab_geo_fdw;
- GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO gitlab_geo_fdw;
-
- -- Tables created by "gitlab" should be made read-only for "gitlab_geo_fdw"
- -- automatically.
- ALTER DEFAULT PRIVILEGES FOR USER gitlab IN SCHEMA public GRANT SELECT ON TABLES TO gitlab_geo_fdw;
- ALTER DEFAULT PRIVILEGES FOR USER gitlab IN SCHEMA public GRANT SELECT ON SEQUENCES TO gitlab_geo_fdw;
- ```
-
-1. Enter the PostgreSQL console on the **secondary** tracking database and change the user mapping to this new user:
-
- ```
- ALTER USER MAPPING FOR gitlab_geo SERVER gitlab_secondary OPTIONS (SET user 'gitlab_geo_fdw')
- ```
-
-## MySQL replication
-
-MySQL replication is not supported for Geo.
-
-## Troubleshooting
-
-Read the [troubleshooting document](troubleshooting.md).
-
-[replication-slots-article]: https://medium.com/@tk512/replication-slots-in-postgresql-b4b03d277c75
-[pgback]: http://www.postgresql.org/docs/9.6/static/app-pgbasebackup.html
-[replication user]:https://wiki.postgresql.org/wiki/Streaming_Replication
-[FDW]: https://www.postgresql.org/docs/9.6/static/postgres-fdw.html
-[database]: database.md
-[add-geo-node]: configuration.md#step-3-add-the-secondary-gitlab-node
-[database-replication]: database.md#step-2-configure-the-secondary-server
-[pg-docs-ssl]: https://www.postgresql.org/docs/9.6/static/libpq-ssl.html#LIBPQ-SSL-PROTECTION
-[pg-docs-runtime-conn]: https://www.postgresql.org/docs/9.6/static/runtime-config-connection.html
-[pg-docs-runtime-replication]: https://www.postgresql.org/docs/9.6/static/runtime-config-replication.html
diff --git a/doc/administration/geo/replication/external_database.md b/doc/administration/geo/replication/external_database.md
index dae5ed911b0..177ca68613e 100644
--- a/doc/administration/geo/replication/external_database.md
+++ b/doc/administration/geo/replication/external_database.md
@@ -129,7 +129,7 @@ To configure the connection to the external read-replica database and enable Log
database to keep track of replication status and automatically recover from
potential replication issues. Omnibus automatically configures a tracking database
when `roles ['geo_secondary_role']` is set. For high availability,
-refer to [Geo High Availability](https://docs.gitlab.com/ee/administration/high_availability).
+refer to [Geo High Availability](../../high_availability/README.md).
If you want to run this database external to Omnibus, please follow the instructions below.
The tracking database requires an [FDW](https://www.postgresql.org/docs/9.6/static/postgres-fdw.html)
diff --git a/doc/administration/geo/replication/high_availability.md b/doc/administration/geo/replication/high_availability.md
index 715a83a9ff3..921a3ef1c7a 100644
--- a/doc/administration/geo/replication/high_availability.md
+++ b/doc/administration/geo/replication/high_availability.md
@@ -6,7 +6,7 @@ described, it is possible to adapt these instructions to your needs.
## Architecture overview
-![Geo HA Diagram](https://docs.gitlab.com/ee/administration/img/high_availability/geo-ha-diagram.png)
+![Geo HA Diagram](../../high_availability/img/geo-ha-diagram.png)
_[diagram source - gitlab employees only][diagram-source]_
@@ -68,7 +68,7 @@ NOTE: **Note:** PostgreSQL and Redis should have already been disabled on the
application servers, and connections from the application servers to those
services on the backend servers configured, during normal GitLab HA set up. See
high availability configuration documentation for
-[PostgreSQL](https://docs.gitlab.com/ee/administration/high_availability/database.html#configuring-the-application-nodes)
+[PostgreSQL](../../high_availability/database.md#configuring-the-application-nodes)
and [Redis](../../high_availability/redis.md#example-configuration-for-the-gitlab-application).
The **primary** database will require modification later, as part of
diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md
index 6abea2cf271..54377f7ae4e 100644
--- a/doc/administration/geo/replication/index.md
+++ b/doc/administration/geo/replication/index.md
@@ -179,37 +179,20 @@ The steps below should be followed in the order they appear. **Make sure the Git
If you installed GitLab using the Omnibus packages (highly recommended):
1. [Install GitLab Enterprise Edition](https://about.gitlab.com/installation/) on the server that will serve as the **secondary** node. Do not create an account or log in to the new **secondary** node.
-1. [Upload the GitLab License](https://docs.gitlab.com/ee/user/admin_area/license.html) on the **primary** node to unlock Geo. The license must be for [GitLab Premium](https://about.gitlab.com/pricing/) or higher.
+1. [Upload the GitLab License](../../../user/admin_area/license.md) on the **primary** node to unlock Geo. The license must be for [GitLab Premium](https://about.gitlab.com/pricing/) or higher.
1. [Set up the database replication](database.md) (`primary (read-write) <-> secondary (read-only)` topology).
1. [Configure fast lookup of authorized SSH keys in the database](../../operations/fast_ssh_key_lookup.md). This step is required and needs to be done on **both** the **primary** and **secondary** nodes.
1. [Configure GitLab](configuration.md) to set the **primary** and **secondary** nodes.
1. Optional: [Configure a secondary LDAP server](../../auth/ldap.md) for the **secondary** node. See [notes on LDAP](#ldap).
1. [Follow the "Using a Geo Server" guide](using_a_geo_server.md).
-### Using GitLab installed from source (Deprecated)
-
-NOTE: **Note:**
-In GitLab 11.5, support for using Geo in GitLab source installations was deprecated and will be removed in a future release. Please consider [migrating to GitLab Omnibus install](https://docs.gitlab.com/omnibus/update/convert_to_omnibus.html).
-
-If you installed GitLab from source:
-
-1. [Install GitLab Enterprise Edition](../../../install/installation.md) on the server that will serve as the **secondary** node. Do not create an account or log in to the new **secondary** node.
-1. [Upload the GitLab License](https://docs.gitlab.com/ee/user/admin_area/license.html) on the **primary** node to unlock Geo. The license must be for [GitLab Premium](https://about.gitlab.com/pricing/) or higher.
-1. [Set up the database replication](database_source.md) (`primary (read-write) <-> secondary (read-only)` topology).
-1. [Configure fast lookup of authorized SSH keys in the database](../../operations/fast_ssh_key_lookup.md). Do this step for **both** **primary** and **secondary** nodes.
-1. [Configure GitLab](configuration_source.md) to set the **primary** and **secondary** nodes.
-1. [Follow the "Using a Geo Server" guide](using_a_geo_server.md).
-
## Post-installation documentation
After installing GitLab on the **secondary** nodes and performing the initial configuration, see the following documentation for post-installation information.
### Configuring Geo
-For information on configuring Geo, see:
-
-- [Geo configuration (GitLab Omnibus)](configuration.md).
-- [Geo configuration (source)](configuration_source.md). Configuring Geo in GitLab source installations was **deprecated** in GitLab 11.5.
+For information on configuring Geo, see [Geo configuration](configuration.md).
### Updating Geo
@@ -278,7 +261,7 @@ Take special note that these examples of GitLab features are both:
Examples include:
-- [Elasticsearch integration](https://docs.gitlab.com/ee/integration/elasticsearch.html).
+- [Elasticsearch integration](../../../integration/elasticsearch.md).
- [Container Registry](../../container_registry.md). [Object Storage](object_storage.md) can mitigate this.
- [GitLab Pages](../../pages/index.md).
- [Mattermost integration](https://docs.gitlab.com/omnibus/gitlab-mattermost/).
diff --git a/doc/administration/geo/replication/security_review.md b/doc/administration/geo/replication/security_review.md
index 46d3e68ab63..cd54e2dc8c4 100644
--- a/doc/administration/geo/replication/security_review.md
+++ b/doc/administration/geo/replication/security_review.md
@@ -120,9 +120,7 @@ questions from [owasp.org](https://www.owasp.org).
### What details regarding required OS components and lock‐down needs have been defined?
-- The recommended installation method (Omnibus) packages most components itself.
- A from-source installation method exists. Both are documented at
- <https://docs.gitlab.com/ee/administration/geo/replication/index.html>
+- The supported installation method (Omnibus) packages most components itself.
- There are significant dependencies on the system-installed OpenSSH daemon (Geo
requires users to set up custom authentication methods) and the omnibus or
system-provided PostgreSQL daemon (it must be configured to listen on TCP,
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index 9c95720487d..c5bdd36ba70 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -23,6 +23,8 @@ to help identify if something is wrong:
![Geo health check](img/geo_node_healthcheck.png)
+For information on how to resolve common errors reported from the UI, see [common errors](#common-errors).
+
If the UI is not working, or you are unable to log in, you can run the Geo
health check manually to get this information as well as a few more details.
This rake task can be run on an app node in the **primary** or **secondary**
@@ -40,7 +42,8 @@ Checking Geo ...
GitLab Geo is available ... yes
GitLab Geo is enabled ... yes
GitLab Geo secondary database is correctly configured ... yes
-Using database streaming replication? ... yes
+Database replication enabled? ... yes
+Database replication working? ... yes
GitLab Geo tracking database is configured to use Foreign Data Wrapper? ... yes
GitLab Geo tracking database Foreign Data Wrapper schema is up-to-date? ... yes
GitLab Geo HTTP(S) connectivity ...
@@ -68,22 +71,22 @@ Example output:
```
http://secondary.example.com/
-----------------------------------------------------
- GitLab Version: 11.8.1-ee
+ GitLab Version: 11.10.4-ee
Geo Role: Secondary
Health Status: Healthy
- Repositories: 190/190 (100%)
- Verified Repositories: 190/190 (100%)
- Wikis: 190/190 (100%)
- Verified Wikis: 190/190 (100%)
- LFS Objects: 35/35 (100%)
- Attachments: 528/528 (100%)
- CI job artifacts: 477/477 (100%)
- Repositories Checked: 0/190 (0%)
+ Repositories: 289/289 (100%)
+ Verified Repositories: 289/289 (100%)
+ Wikis: 289/289 (100%)
+ Verified Wikis: 289/289 (100%)
+ LFS Objects: 8/8 (100%)
+ Attachments: 5/5 (100%)
+ CI job artifacts: 0/0 (0%)
+ Repositories Checked: 0/289 (0%)
Sync Settings: Full
Database replication lag: 0 seconds
- Last event ID seen from primary: 2158 (about 2 minute ago)
- Last event ID processed by cursor: 2158 (about 2 minute ago)
- Last status report was: 4 minutes ago
+ Last event ID seen from primary: 10215 (about 2 minutes ago)
+ Last event ID processed by cursor: 10215 (about 2 minutes ago)
+ Last status report was: 2 minutes ago
```
## Is Postgres replication working?
@@ -307,7 +310,7 @@ same host on different ports. That is, 5432 and 5431 respectively.
### Checking configuration
NOTE: **Note:**
-The following steps are for Omnibus installs only. Using Geo with source-based installs [is deprecated](index.md#using-gitlab-installed-from-source-deprecated).
+The following steps are for Omnibus installs only. Using Geo with source-based installs was **deprecated** in GitLab 11.5.
To check the configuration:
@@ -455,3 +458,57 @@ reload of the FDW schema. To manually reload the FDW schema:
[database-start-replication]: database.md#step-3-initiate-the-replication-process
[database-pg-replication]: database.md#postgresql-replication
+
+## Common errors
+
+This section documents common errors reported in the admin UI and how to fix them.
+
+### Geo database configuration file is missing
+
+GitLab cannot find or doesn't have permission to access the `database_geo.yml` configuration file.
+
+In an Omnibus GitLab installation, the file should be in `/var/opt/gitlab/gitlab-rails/etc`.
+If it doesn't exist or inadvertent changes have been made to it, run `sudo gitlab-ctl reconfigure` to restore it to its correct state.
+
+
+If this path is mounted on a remote volume, please check your volume configuration and that it has correct permissions.
+
+### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node.
+
+This error refers to a problem with the database replica on a **secondary** node,
+which Geo expects to have access to. It usually means, either:
+
+- An unsupported replication method was used (for example, logical replication).
+- The instructions to setup a [Geo database replication](database.md) were not followed correctly.
+
+A common source of confusion with **secondary** nodes is that it requires two separate
+PostgreSQL instances:
+
+- A read-only replica of the **primary** node.
+- A regular, writable instance that holds replication metadata. That is, the Geo tracking database.
+
+### Geo node does not appear to be replicating the database from the primary node.
+
+The most common problems that prevent the database from replicating correctly are:
+
+- **Secondary** nodes cannot reach the **primary** node. Check credentials, firewall rules, etc.
+- SSL certificate problems. Make sure you copied `/etc/gitlab/gitlab-secrets.json` from the **primary** node.
+- Database storage disk is full.
+- Database replication slot is misconfigured.
+- Database is not using a replication slot or another alternative and cannot catch-up because WAL files were purged.
+
+Make sure you follow the [Geo database replication](database.md) instructions for supported configuration.
+
+### Geo database version (...) does not match latest migration (...)
+
+If you are using GitLab Omnibus installation, something might have failed during upgrade. You can:
+
+- Run `sudo gitlab-ctl reconfigure`.
+- Manually trigger the database migration by running: `sudo gitlab-rake geo:db:migrate` as root on the **secondary** node.
+
+### Geo database is not configured to use Foreign Data Wrapper
+
+This error means the Geo Tracking Database doesn't have the FDW server and credentials
+configured.
+
+See [How do I fix a "Foreign Data Wrapper (FDW) is not configured" error?](#how-do-i-fix-a-foreign-data-wrapper-fdw-is-not-configured-error).
diff --git a/doc/administration/geo/replication/tuning.md b/doc/administration/geo/replication/tuning.md
index b9921b2e69f..1943f2230df 100644
--- a/doc/administration/geo/replication/tuning.md
+++ b/doc/administration/geo/replication/tuning.md
@@ -13,5 +13,5 @@ However, this may not lead to more downloads in parallel unless the number of
available Sidekiq threads is also increased. For example, if repository sync
capacity is increased from 25 to 50, you may also want to increase the number
of Sidekiq threads from 25 to 50. See the
-[Sidekiq concurrency documentation](https://docs.gitlab.com/ee/administration/operations/extra_sidekiq_processes.html#number-of-threads)
+[Sidekiq concurrency documentation](../../operations/extra_sidekiq_processes.md#number-of-threads)
for more details.
diff --git a/doc/administration/geo/replication/updating_the_geo_nodes.md b/doc/administration/geo/replication/updating_the_geo_nodes.md
index 66728366e24..933a75c47d8 100644
--- a/doc/administration/geo/replication/updating_the_geo_nodes.md
+++ b/doc/administration/geo/replication/updating_the_geo_nodes.md
@@ -337,6 +337,53 @@ is prepended with the relevant node for better clarity:
1. **[secondary]** Create the `replica.sh` script as described in the
[database configuration document][database-source-replication].
+ 1. 1. **[secondary]** Save the snippet below in a file, let's say `/tmp/replica.sh`. Modify the
+ embedded paths if necessary:
+
+ ```
+ #!/bin/bash
+
+ PORT="5432"
+ USER="gitlab_replicator"
+ echo ---------------------------------------------------------------
+ echo WARNING: Make sure this script is run from the secondary server
+ echo ---------------------------------------------------------------
+ echo
+ echo Enter the IP or FQDN of the primary PostgreSQL server
+ read HOST
+ echo Enter the password for $USER@$HOST
+ read -s PASSWORD
+ echo Enter the required sslmode
+ read SSLMODE
+
+ echo Stopping PostgreSQL and all GitLab services
+ sudo service gitlab stop
+ sudo service postgresql stop
+
+ echo Backing up postgresql.conf
+ sudo -u postgres mv /var/opt/gitlab/postgresql/data/postgresql.conf /var/opt/gitlab/postgresql/
+
+ echo Cleaning up old cluster directory
+ sudo -u postgres rm -rf /var/opt/gitlab/postgresql/data
+
+ echo Starting base backup as the replicator user
+ echo Enter the password for $USER@$HOST
+ sudo -u postgres /opt/gitlab/embedded/bin/pg_basebackup -h $HOST -D /var/opt/gitlab/postgresql/data -U gitlab_replicator -v -x -P
+
+ echo Writing recovery.conf file
+ sudo -u postgres bash -c "cat > /var/opt/gitlab/postgresql/data/recovery.conf <<- _EOF1_
+ standby_mode = 'on'
+ primary_conninfo = 'host=$HOST port=$PORT user=$USER password=$PASSWORD sslmode=$SSLMODE'
+ _EOF1_
+ "
+
+ echo Restoring postgresql.conf
+ sudo -u postgres mv /var/opt/gitlab/postgresql/postgresql.conf /var/opt/gitlab/postgresql/data/
+
+ echo Starting PostgreSQL
+ sudo service postgresql start
+ ```
+
1. **[secondary]** Run the recovery script using the credentials from the
previous step:
@@ -396,8 +443,6 @@ and it is required since 10.0.
[update]: ../../../update/README.md
[database]: database.md
-[database-replication]: database.md#step-3-initiate-the-replication-process
-[database-source-replication]: database_source.md#step-3-initiate-the-replication-process
[Hashed Storage]: ../../repository_storage_types.md
[hashed-migration]: ../../raketasks/storage.md
[ssh-fast-lookup]: ../../operations/fast_ssh_key_lookup.md
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index f1cedb85455..53a85dfad6c 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -53,6 +53,10 @@ But since 11.8 the indexer uses Gitaly for data access as well. NFS can still
be leveraged for redudancy on block level of the Git data. But only has to
be mounted on the Gitaly server.
+NOTE: **Note:** While Gitaly can be used as a replacement for NFS, we do not recommend
+using EFS as it may impact GitLab's performance. Please review the [relevant documentation](../high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs)
+for more details.
+
### Network architecture
- gitlab-rails shards repositories into "repository storages"
@@ -73,18 +77,29 @@ be mounted on the Gitaly server.
- Gitaly servers must not be exposed to the public internet
Gitaly network traffic is unencrypted by default, but supports
-[TLS](#tls-support). Authentication is done through a static token. For
-security in depth, its recommended to use a firewall to restrict access
-to your Gitaly server.
+[TLS](#tls-support). Authentication is done through a static token.
+
+NOTE: **Note:** Gitaly network traffic is unencrypted so we recommend a firewall to
+restrict access to your Gitaly server.
Below we describe how to configure a Gitaly server at address
`gitaly.internal:8075` with secret token `abc123secret`. We assume
your GitLab installation has two repository storages, `default` and
`storage1`.
+### Installation
+
+First install Gitaly using either Omnibus or from source.
+
+Omnibus: [Download/install](https://about.gitlab.com/installation) the Omnibus GitLab
+package you want using **steps 1 and 2** from the GitLab downloads page but
+**_do not_** provide the `EXTERNAL_URL=` value.
+
+Source: [Install Gitaly](../../install/installation.md#install-gitaly)
+
### Client side token configuration
-Start by configuring a token on the client side.
+Configure a token on the client side.
Omnibus installations:
@@ -110,7 +125,7 @@ changes to be picked up.
Next, on the Gitaly server, we need to configure storage paths, enable
the network listener and configure the token.
-Note: if you want to reduce the risk of downtime when you enable
+NOTE: **Note:** if you want to reduce the risk of downtime when you enable
authentication you can temporarily disable enforcement, see [the
documentation on configuring Gitaly
authentication](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/configuration/README.md#authentication)
@@ -122,12 +137,17 @@ the Gitaly server. The easiest way to accomplish this is to copy `/etc/gitlab/gi
from an existing GitLab server to the Gitaly server. Without this shared secret,
Git operations in GitLab will result in an API error.
-> **NOTE:** In most or all cases the storage paths below end in `/repositories` which is
+NOTE: **Note:** In most or all cases the storage paths below end in `/repositories` which is
different than `path` in `git_data_dirs` of Omnibus installations. Check the
directory layout on your Gitaly server to be sure.
Omnibus installations:
+<!--
+updates to following example must also be made at
+https://gitlab.com/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+-->
+
```ruby
# /etc/gitlab/gitlab.rb
@@ -147,6 +167,7 @@ gitlab_rails['auto_migrate'] = false
# Configure the gitlab-shell API callback URL. Without this, `git push` will
# fail. This can be your 'front door' GitLab URL or an internal load
# balancer.
+# Don't forget to copy `/etc/gitlab/gitlab-secrets.json` from web server to Gitaly server.
gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
# Make Gitaly accept connections on all network interfaces. You must use
@@ -199,6 +220,9 @@ network, firewall, or name resolution problem preventing your GitLab
server from reaching the Gitaly server then all Gitaly requests will
fail.
+Additionally, you need to
+[disable Rugged if previously manually enabled](../high_availability/nfs.md#improving-nfs-performance-with-gitlab).
+
We assume that your Gitaly server can be reached at
`gitaly.internal:8075` from your GitLab server, and that Gitaly can read and
write to `/mnt/gitlab/default` and `/mnt/gitlab/storage1` respectively.
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index 24db1c28778..d9c80b1ec59 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -26,8 +26,7 @@ experience with GitLab.com and Enterprise Edition on-premises customers.
For a detailed insight into how GitLab scales and configures GitLab.com, you can
watch [this 1 hour Q&A](https://www.youtube.com/watch?v=uCU8jdYzpac)
-with [John Northrup](https://gitlab.com/northrup), one of our infrastructure
-engineers, and live questions coming in from some of our customers.
+with [John Northrup](https://gitlab.com/northrup), and live questions coming in from some of our customers.
## GitLab Components
@@ -64,8 +63,8 @@ larger one.
- 1 PostgreSQL node
- 1 Redis node
-- 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq)
- 1 NFS/Gitaly storage server
+- 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq)
#### Installation Instructions
@@ -73,10 +72,10 @@ Complete the following installation steps in order. A link at the end of each
section will bring you back to the Scalable Architecture Examples section so
you can continue with the next step.
-1. [PostgreSQL](./database.md#postgresql-in-a-scaled-environment)
-1. [Redis](./redis.md#redis-in-a-scaled-environment)
-1. [Gitaly](./gitaly.md) (recommended) or [NFS](./nfs.md)
-1. [GitLab application nodes](./gitlab.md)
+1. [PostgreSQL](database.md#postgresql-in-a-scaled-environment)
+1. [Redis](redis.md#redis-in-a-scaled-environment)
+1. [Gitaly](gitaly.md) (recommended) or [NFS](nfs.md)
+1. [GitLab application nodes](gitlab.md)
### Full Scaling
@@ -88,9 +87,9 @@ in size, indicating that there is contention or not enough resources.
- 1 PostgreSQL node
- 1 Redis node
-- 2 or more GitLab application nodes (Unicorn, Workhorse)
-- 2 or more Sidekiq nodes
- 2 or more NFS/Gitaly storage servers
+- 2 or more Sidekiq nodes
+- 2 or more GitLab application nodes (Unicorn, Workhorse)
## High Availability Architecture Examples
@@ -135,7 +134,7 @@ the contention.
- 2 or more GitLab application nodes (Unicorn, Workhorse, Sidekiq, PGBouncer)
- 1 NFS/Gitaly server
-![Horizontal architecture diagram](https://docs.gitlab.com/ee/administration/img/high_availability/horizontal.png)
+![Horizontal architecture diagram](img/horizontal.png)
### Hybrid
@@ -153,7 +152,7 @@ contention due to certain workloads.
- 1 or more NFS/Gitaly servers
- 1 Monitoring node (Prometheus, Grafana)
-![Hybrid architecture diagram](https://docs.gitlab.com/ee/administration/img/high_availability/hybrid.png)
+![Hybrid architecture diagram](img/hybrid.png)
#### Reference Architecture
@@ -194,7 +193,7 @@ with the added complexity of many more nodes to configure, manage and monitor.
- 2 or more Web nodes (All other web requests)
- 2 or more NFS/Gitaly servers
-![Fully Distributed architecture diagram](https://docs.gitlab.com/ee/administration/img/high_availability/fully-distributed.png)
+![Fully Distributed architecture diagram](img/fully-distributed.png)
The following pages outline the steps necessary to configure each component
separately:
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index 1648b6b848a..3b874e5d312 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -17,16 +17,16 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
## PostgreSQL in a Scaled Environment
-This section is relevant for [Scaled Architecture](./README.md#scalable-architecture-examples)
-environments including [Basic Scaling](./README.md#basic-scaling) and
-[Full Scaling](./README.md#full-scaling).
+This section is relevant for [Scaled Architecture](README.md#scalable-architecture-examples)
+environments including [Basic Scaling](README.md#basic-scaling) and
+[Full Scaling](README.md#full-scaling).
### Provide your own PostgreSQL instance **[CORE ONLY]**
-If you want to use your own deployed PostgreSQL instance(s),
+If you want to use your own deployed PostgreSQL instance(s),
see [Provide your own PostgreSQL instance](#provide-your-own-postgresql-instance-core-only)
-for more details. However, you can use the GitLab Omnibus package to easily
-deploy the bundled PostgreSQL.
+for more details. However, you can use the GitLab Omnibus package to easily
+deploy the bundled PostgreSQL.
### Standalone PostgreSQL using GitLab Omnibus **[CORE ONLY]**
@@ -36,19 +36,19 @@ deploy the bundled PostgreSQL.
- Do not complete any other steps on the download page.
1. Generate a password hash for PostgreSQL. This assumes you will use the default
username of `gitlab` (recommended). The command will request a password
- and confirmation. Use the value that is output by this command in the next
+ and confirmation. Use the value that is output by this command in the next
step as the value of `POSTGRESQL_PASSWORD_HASH`.
```sh
sudo gitlab-ctl pg-password-md5 gitlab
```
-
+
1. Edit `/etc/gitlab/gitlab.rb` and add the contents below, updating placeholder
- values appropriately.
-
+ values appropriately.
+
- `POSTGRESQL_PASSWORD_HASH` - The value output from the previous step
- `APPLICATION_SERVER_IP_BLOCKS` - A space delimited list of IP subnets or IP
- addresses of the GitLab application servers that will connect to the
+ addresses of the GitLab application servers that will connect to the
database. Example: `%w(123.123.123.123/32 123.123.123.234/32)`
```ruby
@@ -65,11 +65,11 @@ deploy the bundled PostgreSQL.
postgresql['listen_address'] = '0.0.0.0'
postgresql['port'] = 5432
- # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
# Replace XXX.XXX.XXX.XXX/YY with Network Address
- # ????
+ # ????
postgresql['trust_auth_cidr_addresses'] = %w(APPLICATION_SERVER_IP_BLOCKS)
# Disable automatic database migrations
@@ -77,42 +77,43 @@ deploy the bundled PostgreSQL.
```
NOTE: **Note:** The role `postgres_role` was introduced with GitLab 10.3
-
+
1. [Reconfigure GitLab] for the changes to take effect.
1. Note the PostgreSQL node's IP address or hostname, port, and
plain text password. These will be necessary when configuring the GitLab
application servers later.
-
+
Advanced configuration options are supported and can be added if
needed.
Continue configuration of other components by going
-[back to Scaled Architectures](./README.md#scalable-architecture-examples)
+[back to Scaled Architectures](README.md#scalable-architecture-examples)
## PostgreSQL with High Availability
-This section is relevant for [High Availability Architecture](./README.md#high-availability-architecture-examples)
-environments including [Horizontal](./README.md#horizontal),
-[Hybrid](./README.md#hybrid), and
-[Fully Distributed](./README.md#fully-distributed).
+This section is relevant for [High Availability Architecture](README.md#high-availability-architecture-examples)
+environments including [Horizontal](README.md#horizontal),
+[Hybrid](README.md#hybrid), and
+[Fully Distributed](README.md#fully-distributed).
### Provide your own PostgreSQL instance **[CORE ONLY]**
If you want to use your own deployed PostgreSQL instance(s),
see [Provide your own PostgreSQL instance](#provide-your-own-postgresql-instance-core-only)
for more details. However, you can use the GitLab Omnibus package to easily
-deploy the bundled PostgreSQL.
+deploy the bundled PostgreSQL.
### High Availability with GitLab Omnibus **[PREMIUM ONLY]**
> Important notes:
+>
> - This document will focus only on configuration supported with [GitLab Premium](https://about.gitlab.com/pricing/), using the Omnibus GitLab package.
> - If you are a Community Edition or Starter user, consider using a cloud hosted solution.
> - This document will not cover installations from source.
>
> - If HA setup is not what you were looking for, see the [database configuration document](http://docs.gitlab.com/omnibus/settings/database.html)
> for the Omnibus GitLab packages.
-
+>
> Please read this document fully before attempting to configure PostgreSQL HA
> for GitLab.
>
@@ -122,9 +123,9 @@ The recommended configuration for a PostgreSQL HA requires:
- A minimum of three database nodes
- Each node will run the following services:
- - `PostgreSQL` - The database itself
- - `repmgrd` - A service to monitor, and handle failover in case of a failure
- - `Consul` agent - Used for service discovery, to alert other nodes when failover occurs
+ - `PostgreSQL` - The database itself
+ - `repmgrd` - A service to monitor, and handle failover in case of a failure
+ - `Consul` agent - Used for service discovery, to alert other nodes when failover occurs
- A minimum of three `Consul` server nodes
- A minimum of one `pgbouncer` service node
@@ -134,7 +135,7 @@ otherwise the networks will become a single point of failure.
#### Architecture
-![PG HA Architecture](pg_ha_architecture.png)
+![PG HA Architecture](img/pg_ha_architecture.png)
Database nodes run two services with PostgreSQL:
@@ -142,7 +143,7 @@ Database nodes run two services with PostgreSQL:
- Selecting a new master for the cluster.
- Promoting the new node to master.
- Instructing remaining servers to follow the new master node.
-
+
On failure, the old master node is automatically evicted from the cluster, and should be rejoined manually once recovered.
- Consul. Monitors the status of each node in the database cluster and tracks its health in a service definition on the consul cluster.
@@ -171,13 +172,10 @@ Similarly, PostgreSQL access is controlled based on the network source.
This is why you will need:
-> IP address of each nodes network interface
-> - This can be set to `0.0.0.0` to listen on all interfaces. It cannot
-> be set to the loopack address `127.0.0.1`
->
-> Network Address
-> - This can be in subnet (i.e. `192.168.0.0/255.255.255.0`) or CIDR (i.e.
-> `192.168.0.0/24`) form.
+- IP address of each nodes network interface. This can be set to `0.0.0.0` to
+ listen on all interfaces. It cannot be set to the loopack address `127.0.0.1`.
+- Network Address. This can be in subnet (i.e. `192.168.0.0/255.255.255.0`)
+ or CIDR (i.e. `192.168.0.0/24`) form.
##### User information
@@ -199,7 +197,7 @@ When using default setup, minimum configuration requires:
sudo gitlab-ctl pg-password-md5 CONSUL_USERNAME
```
-- `CONSUL_SERVER_NODES`. The IP addresses or DNS records of the Consul server nodes.
+- `CONSUL_SERVER_NODES`. The IP addresses or DNS records of the Consul server nodes.
Few notes on the service itself:
@@ -220,8 +218,7 @@ the number of database nodes in the cluster.
This is used to prevent replication from using up all of the
available database connections.
-> Note:
-> - In this document we are assuming 3 database nodes, which makes this configuration:
+In this document we are assuming 3 database nodes, which makes this configuration:
```
postgresql['max_wal_senders'] = 4
@@ -277,7 +274,7 @@ be allowed to authenticate with the service.
Few notes on the service itself:
- The service runs under the same system account as the database
- - In the package, this is by default `gitlab-psql`
+ - In the package, this is by default `gitlab-psql`
- The service will have a superuser database user account generated for it
- This defaults to `gitlab_repmgr`
@@ -327,7 +324,7 @@ On each Consul node perform the following:
Before moving on, make sure Consul is configured correctly. Run the following
command to verify all server nodes are communicating:
-```
+```sh
/opt/gitlab/embedded/bin/consul members
```
@@ -401,14 +398,15 @@ check the [Troubleshooting section](#troubleshooting) before proceeding.
repmgr['master_on_initialization'] = false
```
-1. [Reconfigure GitLab] for te changes to take effect.
+1. [Reconfigure GitLab] for the changes to take effect.
> Please note:
+>
> - If you want your database to listen on a specific interface, change the config:
-> `postgresql['listen_address'] = '0.0.0.0'`
+> `postgresql['listen_address'] = '0.0.0.0'`.
> - If your Pgbouncer service runs under a different user account,
> you also need to specify: `postgresql['pgbouncer_user'] = PGBOUNCER_USERNAME` in
-> your configuration
+> your configuration.
##### Database nodes post-configuration
@@ -449,7 +447,6 @@ Select one node as a primary node.
is not an IP address, it will need to be a resolvable name (via DNS or
`/etc/hosts`)
-
###### Secondary nodes
1. Set up the repmgr standby:
@@ -500,7 +497,7 @@ Before moving on, make sure the databases are configured correctly. Run the
following command on the **primary** node to verify that replication is working
properly:
-```
+```sh
gitlab-ctl repmgr cluster show
```
@@ -518,7 +515,7 @@ If the 'Role' column for any node says "FAILED", check the
Also, check that the check master command works successfully on each node:
-```
+```sh
su - gitlab-consul
gitlab-ctl repmgr-check-master || echo 'This node is a standby repmgr node'
```
@@ -649,7 +646,7 @@ in the Troubleshooting section before proceeding.
##### Ensure GitLab is running
At this point, your GitLab instance should be up and running. Verify you are
-able to login, and create issues and merge requests. If you have troubles check
+able to login, and create issues and merge requests. If you have troubles check
the [Troubleshooting section](#troubleshooting).
#### Example configuration
@@ -665,13 +662,13 @@ can connect to each freely other on those addresses.
Here is a list and description of each machine and the assigned IP:
-* `10.6.0.11`: Consul 1
-* `10.6.0.12`: Consul 2
-* `10.6.0.13`: Consul 3
-* `10.6.0.21`: PostgreSQL master
-* `10.6.0.22`: PostgreSQL secondary
-* `10.6.0.23`: PostgreSQL secondary
-* `10.6.0.31`: GitLab application
+- `10.6.0.11`: Consul 1
+- `10.6.0.12`: Consul 2
+- `10.6.0.13`: Consul 3
+- `10.6.0.21`: PostgreSQL master
+- `10.6.0.22`: PostgreSQL secondary
+- `10.6.0.23`: PostgreSQL secondary
+- `10.6.0.31`: GitLab application
All passwords are set to `toomanysecrets`, please do not use this password or derived hashes.
@@ -735,7 +732,7 @@ consul['configuration'] = {
On secondary nodes, edit `/etc/gitlab/gitlab.rb` and add all the configuration
added to primary node, noted above. In addition, append the following
-configuration
+configuration:
```
# HA setting to specify if a node should attempt to be master on initialization
@@ -839,10 +836,10 @@ In this example we start with all servers on the same 10.6.0.0/16 private networ
Here is a list and description of each machine and the assigned IP:
-* `10.6.0.21`: PostgreSQL master
-* `10.6.0.22`: PostgreSQL secondary
-* `10.6.0.23`: PostgreSQL secondary
-* `10.6.0.31`: GitLab application
+- `10.6.0.21`: PostgreSQL master
+- `10.6.0.22`: PostgreSQL secondary
+- `10.6.0.23`: PostgreSQL secondary
+- `10.6.0.31`: GitLab application
All passwords are set to `toomanysecrets`, please do not use this password or derived hashes.
@@ -853,6 +850,7 @@ Please note that after the initial configuration, if a failover occurs, the Post
##### Example minimal configuration for database servers
##### Primary node
+
On primary database node edit `/etc/gitlab/gitlab.rb`:
```ruby
@@ -1047,7 +1045,6 @@ For example:
repmgr['trust_auth_cidr_addresses'] = %w(192.168.1.44/32 db2.example.com)
```
-
##### MD5 Authentication
If you are running on an untrusted network, repmgr can use md5 authentication
@@ -1114,7 +1111,7 @@ steps to fix the problem:
1. Change to the `gitlab-consul` user - `su - gitlab-consul`
1. Try the check command again - `gitlab-ctl repmgr-check-master`.
-Now there should not be errors. If errors still occur then there is another problem.
+Now there should not be errors. If errors still occur then there is another problem.
#### PGBouncer error `ERROR: pgbouncer cannot connect to server`
@@ -1157,8 +1154,6 @@ If you're running into an issue with a component not outlined here, be sure to c
**Note**: We recommend that you follow the instructions here for a full [PostgreSQL cluster](#high-availability-with-gitlab-omnibus-premium-only).
If you are reading this section due to an old bookmark, you can find that old documentation [in the repository](https://gitlab.com/gitlab-org/gitlab-ce/blob/v10.1.4/doc/administration/high_availability/database.md#configure-using-omnibus).
----
-
Read more on high-availability configuration:
1. [Configure Redis](redis.md)
diff --git a/doc/administration/high_availability/gitaly.md b/doc/administration/high_availability/gitaly.md
index d44744f2af8..1d8e6c999cb 100644
--- a/doc/administration/high_availability/gitaly.md
+++ b/doc/administration/high_availability/gitaly.md
@@ -7,84 +7,15 @@ should consider using Gitaly on a separate node.
See the [Gitaly HA Epic](https://gitlab.com/groups/gitlab-org/-/epics/289) to
track plans and progress toward high availability support.
-This document is relevant for [Scaled Architecture](./README.md#scalable-architecture-examples)
-environments and [High Availability Architecture](./README.md#high-availability-architecture-examples).
+This document is relevant for [Scaled Architecture](README.md#scalable-architecture-examples)
+environments and [High Availability Architecture](README.md#high-availability-architecture-examples).
## Running Gitaly on its own server
-Starting with GitLab 11.4, Gitaly is a replacement for NFS except
-when the [Elastic Search indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer)
-is used.
-
-NOTE: **Note:** While Gitaly can be used as a replacement for NFS, we do not recommend using EFS as it may impact GitLab's performance. Please review the [relevant documentation](nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
-
-NOTE: **Note:** Gitaly network traffic is unencrypted so we recommend a firewall to
-restrict access to your Gitaly server.
-
-The steps below are the minimum necessary to configure a Gitaly server with
-Omnibus:
-
-1. SSH into the Gitaly server.
-1. [Download/install](https://about.gitlab.com/installation) the Omnibus GitLab
- package you want using **steps 1 and 2** from the GitLab downloads page.
- - Do not complete any other steps on the download page.
-
-1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
-
- Gitaly must trigger some callbacks to GitLab via GitLab Shell. As a result,
- the GitLab Shell secret must be the same between the other GitLab servers and
- the Gitaly server. The easiest way to accomplish this is to copy `/etc/gitlab/gitlab-secrets.json`
- from an existing GitLab server to the Gitaly server. Without this shared secret,
- Git operations in GitLab will result in an API error.
-
- > **NOTE:** In most or all cases the storage paths below end in `repositories` which is
- different than `path` in `git_data_dirs` of Omnibus installations. Check the
- directory layout on your Gitaly server to be sure.
-
- ```ruby
- # Enable Gitaly
- gitaly['enable'] = true
-
- ## Disable all other services
- sidekiq['enable'] = false
- gitlab_workhorse['enable'] = false
- unicorn['enable'] = false
- postgresql['enable'] = false
- nginx['enable'] = false
- prometheus['enable'] = false
- alertmanager['enable'] = false
- pgbouncer_exporter['enable'] = false
- redis_exporter['enable'] = false
- gitlab_monitor['enable'] = false
-
- # Prevent database connections during 'gitlab-ctl reconfigure'
- gitlab_rails['rake_cache_clear'] = false
- gitlab_rails['auto_migrate'] = false
-
- # Configure the gitlab-shell API callback URL. Without this, `git push` will
- # fail. This can be your 'front door' GitLab URL or an internal load
- # balancer.
- gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
-
- # Make Gitaly accept connections on all network interfaces. You must use
- # firewalls to restrict access to this address/port.
- gitaly['listen_addr'] = "0.0.0.0:8075"
- gitaly['auth_token'] = 'abc123secret'
-
- gitaly['storage'] = [
- { 'name' => 'default', 'path' => '/mnt/gitlab/default/repositories' },
- { 'name' => 'storage1', 'path' => '/mnt/gitlab/storage1/repositories' },
- ]
-
- # To use tls for gitaly you need to add
- gitaly['tls_listen_addr'] = "0.0.0.0:9999"
- gitaly['certificate_path'] = "path/to/cert.pem"
- gitaly['key_path'] = "path/to/key.pem"
- ```
-
-Again, reconfigure (Omnibus) or restart (source).
+See [Running Gitaly on its own server](../gitaly/index.md#running-gitaly-on-its-own-server)
+in Gitaly documentation.
Continue configuration of other components by going back to:
-- [Scaled Architectures](./README.md#scalable-architecture-examples)
-- [High Availability Architectures](./README.md#high-availability-architecture-examples)
+- [Scaled Architectures](README.md#scalable-architecture-examples)
+- [High Availability Architectures](README.md#high-availability-architecture-examples)
diff --git a/doc/administration/high_availability/img/fully-distributed.png b/doc/administration/high_availability/img/fully-distributed.png
new file mode 100644
index 00000000000..ad23207134e
--- /dev/null
+++ b/doc/administration/high_availability/img/fully-distributed.png
Binary files differ
diff --git a/doc/administration/high_availability/img/geo-ha-diagram.png b/doc/administration/high_availability/img/geo-ha-diagram.png
new file mode 100644
index 00000000000..da5d612827c
--- /dev/null
+++ b/doc/administration/high_availability/img/geo-ha-diagram.png
Binary files differ
diff --git a/doc/administration/high_availability/img/horizontal.png b/doc/administration/high_availability/img/horizontal.png
new file mode 100644
index 00000000000..c3bd489d96f
--- /dev/null
+++ b/doc/administration/high_availability/img/horizontal.png
Binary files differ
diff --git a/doc/administration/high_availability/img/hybrid.png b/doc/administration/high_availability/img/hybrid.png
new file mode 100644
index 00000000000..7d4a56bf0ea
--- /dev/null
+++ b/doc/administration/high_availability/img/hybrid.png
Binary files differ
diff --git a/doc/administration/high_availability/pg_ha_architecture.png b/doc/administration/high_availability/img/pg_ha_architecture.png
index ef870f652ae..ef870f652ae 100644
--- a/doc/administration/high_availability/pg_ha_architecture.png
+++ b/doc/administration/high_availability/img/pg_ha_architecture.png
Binary files differ
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index caadec3ac4e..3013ce63bd5 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -37,15 +37,18 @@ options:
circumstances it could lead to data loss if a failure occurs before data has
synced.
+Due to the complexities of running Omnibus with LDAP and the complexities of
+maintaining ID mapping without LDAP, in most cases you should enable numeric UIDs
+and GIDs (which is off by default in some cases) for simplified permission
+management between systems:
+
+ - [NetApp instructions](https://library.netapp.com/ecmdocs/ECMP1401220/html/GUID-24367A9F-E17B-4725-ADC1-02D86F56F78E.html)
+ - For non-NetApp devices, disable NFSv4 `idmapping` by performing opposite of [enable NFSv4 idmapper](https://wiki.archlinux.org/index.php/NFS#Enabling_NFSv4_idmapping)
+
### Improving NFS performance with GitLab
-NOTE: **Note:**
-This is only available starting in certain versions of GitLab:
- * 11.5.11
- * 11.6.11
- * 11.7.12
- * 11.8.8
- * 11.9.0 and up (e.g. 11.10, 11.11, etc.)
+NOTE: **Note:** This is only available starting in certain versions of GitLab: 11.5.11,
+11.6.11, 11.7.12, 11.8.8, 11.9.0 and up (e.g. 11.10, 11.11, etc.)
If you are using NFS to share Git data, we recommend that you enable a
number of feature flags that will allow GitLab application processes to
@@ -61,7 +64,8 @@ To do this, run the Rake task:
sudo gitlab-rake gitlab:features:enable_rugged
```
-If you need to undo this setting for some reason, run:
+If you need to undo this setting for some reason such as switching to [Gitaly without NFS](gitaly.md)
+(recommended), run:
```sh
sudo gitlab-rake gitlab:features:disable_rugged
@@ -107,6 +111,11 @@ stored on a local volume.
For more details on another person's experience with EFS, see
[Amazon's Elastic File System: Burst Credits](https://rawkode.com/2017/04/16/amazons-elastic-file-system-burst-credits/)
+## Avoid using CephFS and GlusterFS
+
+GitLab strongly recommends against using CephFS and GlusterFS.
+These distributed file systems are not well-suited for GitLab's input/output access patterns because git uses many small files and access times and file locking times to propagate will make git activity very slow.
+
## Avoid using PostgreSQL with NFS
GitLab strongly recommends against running your PostgreSQL database
@@ -122,7 +131,7 @@ Additionally, this configuration is specifically warned against in the
>to the NFS server can cause data corruption problems.
For supported database architecture, please see our documentation on
-[Configuring a Database for GitLab HA](https://docs.gitlab.com/ee/administration/high_availability/database.html).
+[Configuring a Database for GitLab HA](database.md).
## NFS Client mount options
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index 46ad3ecd9bb..1aaa709fc8f 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -16,9 +16,9 @@ These will be necessary when configuring the GitLab application servers later.
## Redis in a Scaled Environment
-This section is relevant for [Scaled Architecture](./README.md#scalable-architecture-examples)
-environments including [Basic Scaling](./README.md#basic-scaling) and
-[Full Scaling](./README.md#full-scaling).
+This section is relevant for [Scaled Architecture](README.md#scalable-architecture-examples)
+environments including [Basic Scaling](README.md#basic-scaling) and
+[Full Scaling](README.md#full-scaling).
### Provide your own Redis instance **[CORE ONLY]**
@@ -34,7 +34,7 @@ In this configuration Redis is not highly available, and represents a single
point of failure. However, in a scaled environment the objective is to allow
the environment to handle more users or to increase throughput. Redis itself
is generally stable and can handle many requests so it is an acceptable
-trade off to have only a single instance. See [Scaling and High Availability](./README.md)
+trade off to have only a single instance. See [Scaling and High Availability](README.md)
for an overview of GitLab scaling and high availability options.
The steps below are the minimum necessary to configure a Redis server with
@@ -79,14 +79,14 @@ Advanced configuration options are supported and can be added if
needed.
Continue configuration of other components by going
-[back to Scaled Architectures](./README.md#scalable-architecture-examples)
+[back to Scaled Architectures](README.md#scalable-architecture-examples)
## Redis with High Availability
-This section is relevant for [High Availability Architecture](./README.md#high-availability-architecture-examples)
-environments including [Horizontal](./README.md#horizontal),
-[Hybrid](./README.md#hybrid), and
-[Fully Distributed](./README.md#fully-distributed).
+This section is relevant for [High Availability Architecture](README.md#high-availability-architecture-examples)
+environments including [Horizontal](README.md#horizontal),
+[Hybrid](README.md#hybrid), and
+[Fully Distributed](README.md#fully-distributed).
### Provide your own Redis instance **[CORE ONLY]**
@@ -863,7 +863,7 @@ You can check if everything is correct by connecting to each server using
`redis-cli` application, and sending the `info replication` command as below.
```
-/opt/gitlab/embedded/bin/redis-cli -a <redis-password> info replication
+/opt/gitlab/embedded/bin/redis-cli -h <redis-host-or-ip> -a '<redis-password>' info replication
```
When connected to a `master` redis, you will see the number of connected
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 9ac310af248..8271c579f5b 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -10,7 +10,7 @@ GitLab has several features based on receiving incoming emails:
- [New merge request by email](../user/project/merge_requests/index.md#create-new-merge-requests-by-email):
allow GitLab users to create a new merge request by sending an email to a
user-specific email address.
-- [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html): provide e-mail support to
+- [Service Desk](../user/project/service_desk.md): provide e-mail support to
your customers through GitLab. **[PREMIUM]**
## Requirements
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 797a7242bd0..06d900b152d 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -20,7 +20,7 @@ GitLab Community Edition installations only have access to Core features.
GitLab.com is administered by GitLab, Inc., therefore, only GitLab team members have
access to its admin configurations. If you're a GitLab.com user, please check the
-[user documentation](../user/index.html).
+[user documentation](../user/index.md).
NOTE: **Note:**
Non-administrator users don’t have access to GitLab administration tools and settings.
@@ -36,10 +36,10 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Omnibus support for external MySQL DB](https://docs.gitlab.com/omnibus/settings/database.html#using-a-mysql-database-management-server-enterprise-edition-only): Omnibus package supports configuring an external MySQL database. **[STARTER ONLY]**
- [Omnibus support for log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only) **[STARTER ONLY]**
- [High Availability](high_availability/README.md): Configure multiple servers for scaling or high availability.
- - [High Availability on AWS](../university/high-availability/aws/README.md): Set up GitLab HA on Amazon AWS.
-- [Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html): Replicate your GitLab instance to other geographic locations as a read-only fully operational version. **[PREMIUM ONLY]**
-- [Disaster Recovery](https://docs.gitlab.com/ee/administration/geo/disaster_recovery/index.html): Quickly fail-over to a different site with minimal effort in a disaster situation. **[PREMIUM ONLY]**
-- [Pivotal Tile](https://docs.gitlab.com/ee/install/pivotal/index.html): Deploy GitLab as a pre-configured appliance using Ops Manager (BOSH) for Pivotal Cloud Foundry. **[PREMIUM ONLY]**
+ - [Installing GitLab HA on Amazon Web Services (AWS)](../install/aws/index.md): Set up GitLab High Availability on Amazon AWS.
+- [Geo](geo/replication/index.md): Replicate your GitLab instance to other geographic locations as a read-only fully operational version. **[PREMIUM ONLY]**
+- [Disaster Recovery](geo/disaster_recovery/index.md): Quickly fail-over to a different site with minimal effort in a disaster situation. **[PREMIUM ONLY]**
+- [Pivotal Tile](../install/pivotal/index.md): Deploy GitLab as a pre-configured appliance using Ops Manager (BOSH) for Pivotal Cloud Foundry. **[PREMIUM ONLY]**
- [Add License](../user/admin_area/license.md): Upload a license at install time to unlock features that are in paid tiers of GitLab. **[STARTER ONLY]**
### Configuring GitLab
@@ -61,9 +61,9 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages.
- [Merge request diffs storage](merge_request_diffs.md): Configure merge requests diffs external storage.
- [Broadcast Messages](../user/admin_area/broadcast_messages.md): Send messages to GitLab users through the UI.
-- [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html): Enable Elasticsearch to empower GitLab's Advanced Global Search. Useful when you deal with a huge amount of data. **[STARTER ONLY]**
+- [Elasticsearch](../integration/elasticsearch.md): Enable Elasticsearch to empower GitLab's Advanced Global Search. Useful when you deal with a huge amount of data. **[STARTER ONLY]**
- [External Classification Policy Authorization](../user/admin_area/settings/external_authorization.md) **[PREMIUM ONLY]**
-- [Upload a license](https://docs.gitlab.com/ee/user/admin_area/license.html): Upload a license to unlock features that are in paid tiers of GitLab. **[STARTER ONLY]**
+- [Upload a license](../user/admin_area/license.md): Upload a license to unlock features that are in paid tiers of GitLab. **[STARTER ONLY]**
- [Admin Area](../user/admin_area/index.md): for self-managed instance-wide configuration and maintenance.
#### Customizing GitLab's appearance
@@ -73,7 +73,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Branded login page](../customization/branded_login_page.md): Customize the login page with your own logo, title, and description.
- [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page.
- ["New Project" page](../customization/new_project_page.md): Customize the text to be displayed on the page that opens whenever your users create a new project.
-- [Additional custom email text](https://docs.gitlab.com/ee/user/admin_area/settings/email.html#custom-additional-text-premium-only): Add additional custom text to emails sent from GitLab. **[PREMIUM ONLY]**
+- [Additional custom email text](../user/admin_area/settings/email.md#custom-additional-text-premium-only): Add additional custom text to emails sent from GitLab. **[PREMIUM ONLY]**
### Maintaining GitLab
@@ -108,34 +108,29 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Sign-up restrictions](../user/admin_area/settings/sign_up_restrictions.md): block email addresses of specific domains, or whitelist only specific domains.
- [Access restrictions](../user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols): Define which Git access protocols can be used to talk to GitLab (SSH, HTTP, HTTPS).
- [Authentication and Authorization](auth/README.md): Configure external authentication with LDAP, SAML, CAS and additional providers.
- - [Sync LDAP](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html) **[STARTER ONLY]**
- - [Kerberos authentication](https://docs.gitlab.com/ee/integration/kerberos.html) **[STARTER ONLY]**
+ - [Sync LDAP](auth/ldap-ee.md) **[STARTER ONLY]**
+ - [Kerberos authentication](../integration/kerberos.md) **[STARTER ONLY]**
- See also other [authentication](../topics/authentication/index.md#gitlab-administrators) topics (for example, enforcing 2FA).
-- [Email users](https://docs.gitlab.com/ee/tools/email.html): Email GitLab users from within GitLab. **[STARTER ONLY]**
+- [Email users](../tools/email.md): Email GitLab users from within GitLab. **[STARTER ONLY]**
- [User Cohorts](../user/admin_area/user_cohorts.md): Display the monthly cohorts of new users and their activities over time.
- [Audit logs and events](audit_events.md): View the changes made within the GitLab server for:
- Groups and projects. **[STARTER]**
- Instances. **[PREMIUM ONLY]**
- [Auditor users](auditor_users.md): Users with read-only access to all projects, groups, and other resources on the GitLab instance. **[PREMIUM ONLY]**
- [Incoming email](incoming_email.md): Configure incoming emails to allow
- users to [reply by email], create [issues by email] and
- [merge requests by email], and to enable [Service Desk].
+ users to [reply by email](reply_by_email.md), create [issues by email](../user/project/issues/create_new_issue.md#new-issue-via-email) and
+ [merge requests by email](../user/project/merge_requests/index.md#create-new-merge-requests-by-email), and to enable [Service Desk](../user/project/service_desk.md).
- [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a
basic Postfix mail server with IMAP authentication on Ubuntu for incoming
emails.
- [Abuse reports](../user/admin_area/abuse_reports.md): View and resolve abuse reports from your users.
-[reply by email]: reply_by_email.md
-[issues by email]: ../user/project/issues/create_new_issue.md#new-issue-via-email
-[merge requests by email]: ../user/project/merge_requests/index.md#create-new-merge-requests-by-email
-[Service Desk]: https://docs.gitlab.com/ee/user/project/service_desk.html
-
## Project settings
- [Container Registry](container_registry.md): Configure Container Registry with GitLab.
- [Issue closing pattern](issue_closing_pattern.md): Customize how to close an issue from commit messages.
- [Gitaly](gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service.
-- [Default labels](../user/admin_area/labels.html): Create labels that will be automatically added to every new project.
+- [Default labels](../user/admin_area/labels.md): Create labels that will be automatically added to every new project.
- [Restrict the use of public or internal projects](../public_access/public_access.md#restricting-the-use-of-public-or-internal-projects): Restrict the use of visibility levels for users when they create a project or a snippet.
- [Custom project templates](../user/admin_area/custom_project_templates.md): Configure a set of projects to be used as custom templates when creating a new project. **[PREMIUM ONLY]**
- [Packages](packages.md): Enable GitLab to act as a Maven repository or NPM registry. **[PREMIUM ONLY]**
@@ -145,7 +140,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Repository checks](repository_checks.md): Periodic Git repository checks.
- [Repository storage paths](repository_storage_paths.md): Manage the paths used to store repositories.
- [Repository storage rake tasks](raketasks/storage.md): A collection of rake tasks to list and migrate existing projects and attachments associated with it from Legacy storage to Hashed storage.
-- [Limit repository size](https://docs.gitlab.com/ee/user/admin_area/settings/account_and_limit_settings.html): Set a hard limit for your repositories' size. **[STARTER ONLY]**
+- [Limit repository size](../user/admin_area/settings/account_and_limit_settings.md): Set a hard limit for your repositories' size. **[STARTER ONLY]**
## Continuous Integration settings
@@ -154,7 +149,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Job artifacts](job_artifacts.md): Enable, disable, and configure job artifacts (a set of files and directories which are outputted by a job when it completes successfully).
- [Job traces](job_traces.md): Information about the job traces (logs).
- [Register Shared and specific Runners](../ci/runners/README.md#registering-a-shared-runner): Learn how to register and configure Shared and specific Runners to your own instance.
-- [Shared Runners pipelines quota](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#shared-runners-pipeline-minutes-quota-starter-only): Limit the usage of pipeline minutes for Shared Runners. **[STARTER ONLY]**
+- [Shared Runners pipelines quota](../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota-starter-only): Limit the usage of pipeline minutes for Shared Runners. **[STARTER ONLY]**
- [Enable/disable Auto DevOps](../topics/autodevops/index.md#enablingdisabling-auto-devops): Enable or disable Auto DevOps for your instance.
## Git configuration options
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index b7b820abb40..82e0c14ffc2 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -27,7 +27,7 @@ own PlantUML server is easy in Debian/Ubuntu distributions using Tomcat.
First you need to create a `plantuml.war` file from the source code:
```
-sudo apt-get install graphviz openjdk-7-jdk git-core maven
+sudo apt-get install graphviz openjdk-8-jdk git-core maven
git clone https://github.com/plantuml/plantuml-server.git
cd plantuml-server
mvn package
diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md
index 2596e3fe68b..c34858cd0db 100644
--- a/doc/administration/integration/terminal.md
+++ b/doc/administration/integration/terminal.md
@@ -43,6 +43,11 @@ detail below.
## Enabling and disabling terminal support
+NOTE: **Note:** AWS Elastic Load Balancers (ELBs) do not support web sockets.
+AWS Application Load Balancers (ALBs) must be used if you want web terminals
+to work. See [AWS Elastic Load Balancing Product Comparison](https://aws.amazon.com/elasticloadbalancing/features/#compare)
+for more information.
+
As web terminals use WebSockets, every HTTP/HTTPS reverse proxy in front of
Workhorse needs to be configured to pass the `Connection` and `Upgrade` headers
through to the next one in the chain. If you installed GitLab using Omnibus, or
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index e7792106f81..05e15fc303b 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -7,9 +7,9 @@
> - Starting with GitLab 8.17, builds are renamed to jobs.
> - This is the administration documentation. For the user guide see [pipelines/job_artifacts](../user/project/pipelines/job_artifacts.md).
-Artifacts is a list of files and directories which are attached to a job
-after it completes successfully. This feature is enabled by default in all
-GitLab installations. Keep reading if you want to know how to disable it.
+Artifacts is a list of files and directories which are attached to a job after it
+finishes. This feature is enabled by default in all GitLab installations. Keep reading
+if you want to know how to disable it.
## Disabling job artifacts
@@ -42,8 +42,9 @@ To disable artifacts site-wide, follow the steps below.
## Storing job artifacts
-After a successful job, GitLab Runner uploads an archive containing the job
-artifacts to GitLab.
+GitLab Runner can upload an archive containing the job artifacts to GitLab. By default,
+this is done when the job succeeds, but can also be done on failure, or always, via the
+[`artifacts:when`](../ci/yaml/README.md#artifactswhen) parameter.
### Using local storage
@@ -100,6 +101,9 @@ artifacts, you can use an object storage like AWS S3 instead.
This configuration relies on valid AWS credentials to be configured already.
Use an object storage option like AWS S3 to store job artifacts.
+DANGER: **Danger:**
+If you're enabling S3 in [GitLab HA](high_availability/README.md), you will need to have an [NFS mount set up for CI traces and artifacts](high_availability/nfs.md#a-single-nfs-mount) or enable [live tracing](job_traces.md#new-live-trace-architecture). If these settings are not set, you will risk job traces disappearing or not being saved.
+
### Object Storage Settings
For source installations the following settings are nested under `artifacts:` and then `object_store:`. On omnibus installs they are prefixed by `artifacts_object_store_`.
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 3d40cda491a..c5cfb8d5016 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -129,7 +129,7 @@ It contains information about [integrations](../user/project/integrations/projec
``` 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"}
-{"severity":"INFO","time":"2018-09-06T17:15:16.365Z","service_class":"JiraService","project_id":3,"project_path":"namespace2/project2","message":"Successfully posted","client_url":"http://jira.example.net"}
+{"severity":"INFO","time":"2018-09-06T17:15:16.365Z","service_class":"JiraService","project_id":3,"project_path":"namespace2/project2","message":"Successfully posted","client_url":"http://jira.example.com"}
```
## `kubernetes.log`
@@ -280,6 +280,28 @@ installations from source.
Currently it logs the progress of project imports from the Bitbucket Server
importer. Future importers may use this file.
+## `auth.log`
+
+Introduced in GitLab 12.0. This file lives in `/var/log/gitlab/gitlab-rails/auth.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/auth.log` for
+installations from source.
+
+It logs information whenever [Rack Attack] registers an abusive request.
+
+## `graphql_json.log`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/59587) in GitLab 12.0.
+
+This file lives in `/var/log/gitlab/gitlab-rails/graphql_json.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/graphql_json.log` for
+installations from source.
+
+GraphQL queries are recorded in that file. For example:
+
+```json
+{"query_string":"query IntrospectionQuery{__schema {queryType { name },mutationType { name }}}...(etc)","variables":{"a":1,"b":2},"complexity":181,"depth":1,"duration":7}
+```
+
## Reconfigure Logs
Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab
@@ -298,3 +320,4 @@ Omnibus GitLab packages or in `/home/git/gitlab/log/sidekiq_exporter.log` for
installations from source.
[repocheck]: repository_checks.md
+[Rack Attack]: ../security/rack_attack.md
diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md
index ab43ec2cc4f..187fb2f73a1 100644
--- a/doc/administration/monitoring/performance/grafana_configuration.md
+++ b/doc/administration/monitoring/performance/grafana_configuration.md
@@ -3,7 +3,7 @@
[Grafana](http://grafana.org/) is a tool that allows you to visualize time
series metrics through graphs and dashboards. It supports several backend
data stores, including InfluxDB. GitLab writes performance data to InfluxDB
-and Grafana will allow you to query InfluxDB to display useful graphs.
+and Grafana will allow you to query to display useful graphs.
For the easiest installation and configuration, install Grafana on the same
server as InfluxDB. For larger installations, you may want to split out these
@@ -11,11 +11,13 @@ services.
## Installation
-Grafana supplies package repositories (Yum/Apt) for easy installation.
+[GitLab Omnibus can help you install Grafana (recommended)](https://docs.gitlab.com/omnibus/settings/grafana.html)
+or Grafana supplies package repositories (Yum/Apt) for easy installation.
See [Grafana installation documentation](http://docs.grafana.org/installation/)
for detailed steps.
-> **Note**: Before starting Grafana for the first time, set the admin user
+NOTE: **Note:**
+Before starting Grafana for the first time, set the admin user
and password in `/etc/grafana/grafana.ini`. Otherwise, the default password
will be `admin`.
diff --git a/doc/administration/monitoring/performance/index.md b/doc/administration/monitoring/performance/index.md
index f5f0363ed38..ef71ca1d6c3 100644
--- a/doc/administration/monitoring/performance/index.md
+++ b/doc/administration/monitoring/performance/index.md
@@ -14,7 +14,7 @@ documents in order to understand and properly configure GitLab Performance Monit
- [Performance bar](performance_bar.md)
- [Request profiling](request_profiling.md)
->**Note:**
+NOTE: **Note:**
Omnibus GitLab 8.16 includes Prometheus as an additional tool to collect
metrics. It will eventually replace InfluxDB when their metrics collection is
on par. Read more in the [Prometheus documentation](../prometheus/index.md).
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index c243dd9edbb..84b71ae6f1c 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -43,10 +43,11 @@ The following metrics are available:
| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
| upload_file_does_not_exist | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file |
-| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login |
-| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login |
-| unicorn_active_connections | Gauge | 11.0 | The number of active Unicorn connections (workers) |
-| unicorn_queued_connections | Gauge | 11.0 | The number of queued Unicorn connections |
+| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login |
+| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login |
+| unicorn_active_connections | Gauge | 11.0 | The number of active Unicorn connections (workers) |
+| unicorn_queued_connections | Gauge | 11.0 | The number of queued Unicorn connections |
+| unicorn_workers | Gauge | 12.0 | The number of Unicorn workers |
## Sidekiq Metrics available for Geo **[PREMIUM]**
@@ -86,8 +87,8 @@ the `monitoring.sidekiq_exporter` configuration option in `gitlab.yml`.
| geo_wikis_checksum_mismatch_count | Gauge | 10.7 | Number of wikis that checksum mismatch on secondary | url
| geo_repositories_checked_count | Gauge | 11.1 | Number of repositories that have been checked via `git fsck` | url
| geo_repositories_checked_failed_count | Gauge | 11.1 | Number of repositories that have a failure from `git fsck` | url
-| geo_repositories_retrying_verification_count | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | url
-| geo_wikis_retrying_verification_count | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | url
+| geo_repositories_retrying_verification_count | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | url
+| geo_wikis_retrying_verification_count | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | url
### Ruby metrics
@@ -100,9 +101,32 @@ Some basic Ruby runtime metrics are available:
| ruby_file_descriptors | Gauge | 11.1 | File descriptors per process |
| ruby_memory_bytes | Gauge | 11.1 | Memory usage by process |
| ruby_sampler_duration_seconds_total | Counter | 11.1 | Time spent collecting stats |
+| ruby_process_cpu_seconds_total | Gauge | 12.0 | Total amount of CPU time per process |
+| ruby_process_max_fds | Gauge | 12.0 | Maximum number of open file descriptors per process |
+| ruby_process_resident_memory_bytes | Gauge | 12.0 | Memory usage by process, measured in bytes |
+| ruby_process_start_time_seconds | Gauge | 12.0 | The elapsed time between system boot and the process started, measured in seconds |
[GC.stat]: https://ruby-doc.org/core-2.3.0/GC.html#method-c-stat
+## Puma Metrics **[EXPERIMENTAL]**
+
+When Puma is used instead of Unicorn, following metrics are available:
+
+| Metric | Type | Since | Description |
+|:-------------------------------------------- |:------- |:----- |:----------- |
+| puma_workers | Gauge | 12.0 | Total number of workers |
+| puma_running_workers | Gauge | 12.0 | Number of booted workers |
+| puma_stale_workers | Gauge | 12.0 | Number of old workers |
+| puma_phase | Gauge | 12.0 | Phase number (increased during phased restarts) |
+| puma_running | Gauge | 12.0 | Number of running threads |
+| puma_queued_connections | Gauge | 12.0 | Number of connections in that worker's "todo" set waiting for a worker thread |
+| puma_active_connections | Gauge | 12.0 | Number of threads processing a request |
+| puma_pool_capacity | Gauge | 12.0 | Number of requests the worker is capable of taking right now |
+| puma_max_threads | Gauge | 12.0 | Maximum number of worker threads |
+| puma_idle_threads | Gauge | 12.0 | Number of spawned threads which are not processing a request |
+| rack_state_total | Gauge | 12.0 | Number of requests in a given rack state |
+| puma_killer_terminations_total | Gauge | 12.0 | Number of workers terminated by PumaWorkerKiller |
+
## Metrics shared directory
GitLab's Prometheus client requires a directory to store metrics data shared between multi-process services.
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index 69f110805b7..3631ea0822f 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -34,10 +34,10 @@ feature for CentOS 6, follow [the instructions on how to build and install a cus
By default, GitLab manages an `authorized_keys` file, which contains all the
public SSH keys for users allowed to access GitLab. However, to maintain a
-single source of truth, [Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html) needs to be configured to perform SSH fingerprint
+single source of truth, [Geo](../geo/replication/index.md) needs to be configured to perform SSH fingerprint
lookups via database lookup.
-As part of [setting up Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html#setup-instructions),
+As part of [setting up Geo](../geo/replication/index.md#setup-instructions),
you will be required to follow the steps outlined below for both the primary and
secondary nodes, but note that the `Write to "authorized keys" file` checkbox
only needs to be unchecked on the primary node since it will be reflected
diff --git a/doc/administration/packages.md b/doc/administration/packages.md
index 5b9a13e3859..0d5f784b71e 100644
--- a/doc/administration/packages.md
+++ b/doc/administration/packages.md
@@ -8,12 +8,12 @@ The Packages feature allows GitLab to act as a repository for the following:
| Software repository | Description | Available in GitLab version |
| ------------------- | ----------- | --------------------------- |
-| [Maven Repository](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html) | The GitLab Maven Repository enables every project in GitLab to have its own space to store [Maven](https://maven.apache.org/) packages. | 11.3+ |
-| [NPM Registry](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html) | The GitLab NPM Registry enables every project in GitLab to have its own space to store [NPM](https://www.npmjs.com/) packages. | 11.7+ |
+| [Maven Repository](../user/project/packages/maven_repository.md) | The GitLab Maven Repository enables every project in GitLab to have its own space to store [Maven](https://maven.apache.org/) packages. | 11.3+ |
+| [NPM Registry](../user/project/packages/npm_registry.md) | The GitLab NPM Registry enables every project in GitLab to have its own space to store [NPM](https://www.npmjs.com/) packages. | 11.7+ |
Don't you see your package management system supported yet?
Please consider contributing
-to GitLab. This [development documentation](https://docs.gitlab.com/ee/development/packages.html) will guide you through the process.
+to GitLab. This [development documentation](../development/packages.md) will guide you through the process.
## Enabling the Packages feature
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 1373bd56fe3..3a7ca517d56 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -291,6 +291,20 @@ Pages access control is disabled by default. To enable it:
1. [Reconfigure GitLab][reconfigure].
1. Users can now configure it in their [projects' settings](../../user/project/pages/introduction.md#gitlab-pages-access-control-core-only).
+### Running behind a proxy
+
+Like the rest of GitLab, Pages can be used in those environments where external
+internet connectivity is gated by a proxy. In order to use a proxy for GitLab
+pages:
+
+1. Configure in `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_pages['http_proxy'] = 'http://example:8080'
+ ```
+
+1. [Reconfigure Gitlab][reconfigure] for the changes to take effect.
+
## Activate verbose logging for daemon
Verbose logging was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2533) in
@@ -376,6 +390,7 @@ Follow the steps below to configure GitLab Pages in a separate server.
gitaly['enable'] = false
alertmanager['enable'] = false
node_exporter['enable'] = false
+ gitlab_rails['auto_migrate'] = false
```
1. Run `sudo gitlab-ctl reconfigure`.
1. On `app1` apply the following changes to `/etc/gitlab/gitlab.rb`:
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index b295b7d5dc4..0b4c1ae15d6 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -205,25 +205,6 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
```
-## Create or repair repository hooks symlink
-
-If the GitLab shell hooks directory location changes or another circumstance
-leads to the hooks symlink becoming missing or invalid, run this Rake task
-to create or repair the symlinks.
-
-**Omnibus Installation**
-
-```
-sudo gitlab-rake gitlab:shell:create_hooks
-```
-
-**Source Installation**
-
-```
-cd /home/git/gitlab
-sudo -u git -H bundle exec rake gitlab:shell:create_hooks RAILS_ENV=production
-```
-
## Check TCP connectivity to a remote site
Sometimes you need to know if your GitLab installation can connect to a TCP
diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md
index f43bba0a7a7..6ca23aabdec 100644
--- a/doc/administration/raketasks/project_import_export.md
+++ b/doc/administration/raketasks/project_import_export.md
@@ -32,5 +32,4 @@ bundle exec rake gitlab:import_export:data RAILS_ENV=production
```
[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
-[feature-flags]: https://docs.gitlab.com/ee/api/features.html
[tmp]: ../../development/shared_files.md
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index 1689b0a57d6..4aafc06cfdc 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -62,7 +62,7 @@ files and add the full paths of the alternative repository storage paths. In
the example below, we add two more mountpoints that are named `nfs` and `cephfs`
respectively.
-NOTE: **Note:** This example uses NFS and CephFS. We do not recommend using EFS for storage as it may impact GitLab's performance. See the [relevant documentation](./high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
+NOTE: **Note:** This example uses NFS and CephFS. We do not recommend using EFS for storage as it may impact GitLab's performance. See the [relevant documentation](high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
**For installations from source**
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 4db3cbb9958..38842693d73 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -106,6 +106,11 @@ enabled for individual projects by executing
be on hashed storage, should not be a fork itself, and hashed storage should be
enabled for all new projects.
+DANGER: **Danger:**
+Do not run `git prune` or `git gc` in pool repositories! This can
+cause data loss in "real" repositories that depend on the pool in
+question.
+
### How to migrate to Hashed Storage
To start a migration, enable Hashed Storage for new projects:
diff --git a/doc/analytics/README.md b/doc/analytics/README.md
index 6b63edb5174..bfb15f6c4f3 100644
--- a/doc/analytics/README.md
+++ b/doc/analytics/README.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/group/index.html#user-contribution-analysis-starter'
+redirect_to: '../user/group/index.md#user-contribution-analysis-starter'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/group/index.html#user-contribution-analysis-starter)
+This document was moved to [another location](../user/group/index.md#user-contribution-analysis-starter)
diff --git a/doc/analytics/contribution_analytics.md b/doc/analytics/contribution_analytics.md
index 38d71263bc1..e36f55071a4 100644
--- a/doc/analytics/contribution_analytics.md
+++ b/doc/analytics/contribution_analytics.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/group/contribution_analytics/index.html'
+redirect_to: '../user/group/contribution_analytics/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/group/contribution_analytics/index.html).
+This document was moved to [another location](../user/group/contribution_analytics/index.md).
diff --git a/doc/api/README.md b/doc/api/README.md
index 439f58c1253..3a1064b787e 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -21,72 +21,83 @@ See also:
The following API resources are available in the project context:
-| Resource | Available endpoints |
-|:------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
-| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
-| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
-| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
-| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
-| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
-| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
-| [Deployments](deployments.md) | `/projects/:id/deployments` |
-| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` |
-| [Environments](environments.md) | `/projects/:id/environments` |
-| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
-| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
-| [Issue boards](boards.md) | `/projects/:id/boards` |
-| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` |
-| [Labels](labels.md) | `/projects/:id/labels` |
-| [Members](members.md) | `/projects/:id/members` (also available for groups) |
-| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) |
-| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` |
-| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) |
-| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) |
-| [Pipelines](pipelines.md) | `/projects/:id/pipelines` |
-| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` |
-| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` |
-| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) |
-| [Project badges](project_badges.md) | `/projects/:id/badges` |
-| [Project clusters](project_clusters.md) | `/projects/:id/clusters` |
-| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` |
-| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` |
-| [Project milestones](milestones.md) | `/projects/:id/milestones` |
-| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
-| [Project templates](project_templates.md) | `/projects/:id/templates` |
-| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
-| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
-| [Releases](releases/index.md) | `/projects/:id/releases` |
-| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` |
-| [Repositories](repositories.md) | `/projects/:id/repository` |
-| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
-| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
-| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` |
-| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
-| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) |
-| [Services](services.md) | `/projects/:id/services` |
-| [Tags](tags.md) | `/projects/:id/repository/tags` |
-| [Wikis](wikis.md) | `/projects/:id/wikis` |
+| Resource | Available endpoints |
+|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
+| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
+| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
+| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
+| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
+| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
+| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
+| [Deployments](deployments.md) | `/projects/:id/deployments` |
+| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) |
+| [Environments](environments.md) | `/projects/:id/environments` |
+| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
+| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
+| [Issue boards](boards.md) | `/projects/:id/boards` |
+| [Issue links](issue_links.md) **[STARTER]** | `/projects/:id/issues/.../links` |
+| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` |
+| [Labels](labels.md) | `/projects/:id/labels` |
+| [Managed licenses](managed_licenses.md) **[ULTIMATE]** | `/projects/:id/managed_licenses` |
+| [Members](members.md) | `/projects/:id/members` (also available for groups) |
+| [Merge request approvals](merge_request_approvals.md) **[STARTER]** | `/projects/:id/approvals`, `/projects/:id/merge_requests/.../approvals` |
+| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) |
+| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` (also available for groups) |
+| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) |
+| [Packages](packages.md) **[PREMIUM]** | `/projects/:id/packages` |
+| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) |
+| [Pipelines](pipelines.md) | `/projects/:id/pipelines` |
+| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` |
+| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` |
+| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) |
+| [Project badges](project_badges.md) | `/projects/:id/badges` |
+| [Project clusters](project_clusters.md) | `/projects/:id/clusters` |
+| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` |
+| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` |
+| [Project milestones](milestones.md) | `/projects/:id/milestones` |
+| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
+| [Project templates](project_templates.md) | `/projects/:id/templates` |
+| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
+| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
+| [Releases](releases/index.md) | `/projects/:id/releases` |
+| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` |
+| [Repositories](repositories.md) | `/projects/:id/repository` |
+| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
+| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
+| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) |
+| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
+| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) |
+| [Services](services.md) | `/projects/:id/services` |
+| [Tags](tags.md) | `/projects/:id/repository/tags` |
+| [Vulnerabilities](vulnerabilities.md) **[ULTIMATE]** | `/projects/:id/vulnerabilities` (also available for groups) |
+| [Wikis](wikis.md) | `/projects/:id/wikis` |
### Group resources
The following API resources are available in the group context:
-| Resource | Available endpoints |
-|:--------------------------------------------------|:---------------------------------------------------------------------------------|
-| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) |
-| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) |
-| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` |
-| [Group badges](group_badges.md) | `/groups/:id/badges` |
-| [Group issue boards](group_boards.md) | `/groups/:id/boards` |
-| [Group labels](group_labels.md) | `/groups/:id/labels` |
-| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
-| [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
-| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
-| [Members](members.md) | `/groups/:id/members` (also available for projects) |
-| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
-| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) |
-| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) |
+| Resource | Available endpoints |
+|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) |
+| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) |
+| [Discussions](discussions.md) (threaded comments) **[ULTIMATE]** | `/groups/:id/epics/.../discussions` (also available for projects) |
+| [Epic issues](epic_issues.md) **[ULTIMATE]** | `/groups/:id/epics/.../issues` |
+| [Epic links](epic_links.md) **[ULTIMATE]** | `/groups/:id/epics/.../epics` |
+| [Epics](epics.md) **[ULTIMATE]** | `/groups/:id/epics` |
+| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` |
+| [Group badges](group_badges.md) | `/groups/:id/badges` |
+| [Group issue boards](group_boards.md) | `/groups/:id/boards` |
+| [Group labels](group_labels.md) | `/groups/:id/labels` |
+| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
+| [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
+| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
+| [Members](members.md) | `/groups/:id/members` (also available for projects) |
+| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
+| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) |
+| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) |
+| [Resource label events](resource_label_events.md) | `/groups/:id/epics/.../resource_label_events` (also available for projects) |
+| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) |
### Standalone resources
@@ -102,9 +113,11 @@ The following API resources are available outside of project and group contexts
| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) |
| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) |
| [Feature flags](features.md) | `/features` |
+| [Geo Nodes](geo_nodes.md) **[PREMIUM ONLY]** | `/geo_nodes` |
| [Import repository from GitHub](import.md) | `/import/github` |
| [Issues](issues.md) | `/issues` (also available for groups and projects) |
| [Keys](keys.md) | `/keys` |
+| [License](license.md) **[CORE ONLY]** | `/license` |
| [Markdown](markdown.md) | `/markdown` |
| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
| [Namespaces](namespaces.md) | `/namespaces` |
@@ -131,6 +144,11 @@ Endpoints are available for:
- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md).
- [Open source license templates](templates/licenses.md).
+## SCIM **[SILVER ONLY]**
+
+[GitLab.com Silver and above](https://about.gitlab.com/pricing/) provides an [SCIM API](scim.md) that implements [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644) and provides
+the `/Users` endpoint. The base URL is: `/api/scim/v2/groups/:group_path/Users/`.
+
## Road to GraphQL
Going forward, we will start on moving to
@@ -186,8 +204,6 @@ curl "https://gitlab.example.com/api/v4/projects"
The API uses JSON to serialize data. You don't need to specify `.json` at the
end of an API URL.
-All of the API endpoints that use the `POST`, `PUT` or `PATCH` method support params in the request body, with `Content-Type` `application/x-www-form-urlencoded`, `multipart/form-data` or `application/json`.
-
## Authentication
Most API requests require authentication, or will only return public data when
diff --git a/doc/api/boards.md b/doc/api/boards.md
index 28c73db6b98..a96206f5df3 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -141,6 +141,173 @@ Example response:
}
```
+## Create a board **[STARTER]**
+
+Creates a board.
+
+```
+POST /projects/:id/boards
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `name` | string | yes | The name of the new board |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards?name=newboard
+```
+
+Example response:
+
+```json
+ {
+ "id": 1,
+ "project": {
+ "id": 5,
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site"
+ },
+ "name": "newboard",
+ "milestone": {
+ "id": 12
+ "title": "10.0"
+ },
+ "lists" : [
+ {
+ "id" : 1,
+ "label" : {
+ "name" : "Testing",
+ "color" : "#F0AD4E",
+ "description" : null
+ },
+ "position" : 1
+ },
+ {
+ "id" : 2,
+ "label" : {
+ "name" : "Ready",
+ "color" : "#FF0000",
+ "description" : null
+ },
+ "position" : 2
+ },
+ {
+ "id" : 3,
+ "label" : {
+ "name" : "Production",
+ "color" : "#FF5F00",
+ "description" : null
+ },
+ "position" : 3
+ }
+ ]
+ }
+```
+
+## Update a board **[STARTER]**
+
+> [Introduced][ee-5954] in [GitLab Starter](https://about.gitlab.com/pricing/) 11.1.
+
+Updates a board.
+
+```
+PUT /projects/:id/boards/:board_id
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `board_id` | integer | yes | The ID of a board |
+| `name` | string | no | The new name of the board |
+| `assignee_id` | integer | no | The assignee the board should be scoped to |
+| `milestone_id` | integer | no | The milestone the board should be scoped to |
+| `labels` | string | no | Comma-separated list of label names which the board should be scoped to |
+| `weight` | integer | no | The weight range from 0 to 9, to which the board should be scoped to |
+
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1?name=new_name&milestone_id=43&assignee_id=1&labels=Doing&weight=4
+```
+
+Example response:
+
+```json
+ {
+ "id": 1,
+ "project": {
+ "id": 5,
+ "name": "Diaspora Project Site",
+ "name_with_namespace": "Diaspora / Diaspora Project Site",
+ "path": "diaspora-project-site",
+ "path_with_namespace": "diaspora/diaspora-project-site",
+ "created_at": "2018-07-03T05:48:49.982Z",
+ "default_branch": null,
+ "tag_list": [],
+ "ssh_url_to_repo": "ssh://user@example.com/diaspora/diaspora-project-site.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
+ "web_url": "http://example.com/diaspora/diaspora-project-site",
+ "readme_url": null,
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "last_activity_at": "2018-07-03T05:48:49.982Z"
+ },
+ "lists": [],
+ "name": "new_name",
+ "group": null,
+ "milestone": {
+ "id": 43,
+ "iid": 1,
+ "project_id": 15,
+ "title": "Milestone 1",
+ "description": "Milestone 1 desc",
+ "state": "active",
+ "created_at": "2018-07-03T06:36:42.618Z",
+ "updated_at": "2018-07-03T06:36:42.618Z",
+ "due_date": null,
+ "start_date": null,
+ "web_url": "http://example.com/root/board1/milestones/1"
+ },
+ "assignee": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://example.com/root"
+ },
+ "labels": [{
+ "id": 10,
+ "name": "Doing",
+ "color": "#5CB85C",
+ "description": null
+ }],
+ "weight": 4
+ }
+```
+
+## Delete a board **[STARTER]**
+
+Deletes a board.
+
+```
+DELETE /projects/:id/boards/:board_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `board_id` | integer | yes | The ID of a board |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1
+```
+
## List board lists
Get a list of the board's lists.
@@ -237,7 +404,15 @@ POST /projects/:id/boards/:board_id/lists
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `board_id` | integer | yes | The ID of a board |
-| `label_id` | integer | yes | The ID of a label |
+| `label_id` | integer | no | The ID of a label |
+| `assignee_id` **[PREMIUM]** | integer | no | The ID of a user |
+| `milestone_id` **[PREMIUM]** | integer | no | The ID of a milestone |
+
+NOTE: **Note**:
+Label, assignee and milestone arguments are mutually exclusive,
+that is, only one of them are accepted in a request.
+Check the [Issue Board docs](../user/project/issue_board.md#summary-of-features-per-tier)
+for more information regarding the required license for each list type.
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1/lists?label_id=5
@@ -307,3 +482,5 @@ DELETE /projects/:id/boards/:board_id/lists/:list_id
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1
```
+
+[ee-5954]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5954
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 92f53c7b5e6..25015fad9e3 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -75,6 +75,7 @@ POST /projects/:id/repository/commits
| `branch` | string | yes | Name of the branch to commit into. To create a new branch, also provide `start_branch`. |
| `commit_message` | string | yes | Commit message |
| `start_branch` | string | no | Name of the branch to start the new commit from |
+| `start_project` | integer/string | no | The project ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) to start the commit from. Defaults to the value of `id`. |
| `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. |
| `author_email` | string | no | Specify the commit author's email address |
| `author_name` | string | no | Specify the commit author's name |
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index 07a6201b10b..9defef4fd53 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -1,6 +1,12 @@
# Discussions API
-Discussions are set of related notes on snippets, issues, merge requests or commits.
+Discussions are a set of related notes on:
+
+- Snippets
+- Issues
+- Epics **[ULTIMATE]**
+- Merge requests
+- Commits
This includes system notes, which are notes about changes to the object (for example, when a milestone changes, there will be a corresponding system note). Label notes are not part of this API, but recorded as separate events in [resource label events](resource_label_events.md).
@@ -424,6 +430,214 @@ Parameters:
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/636
```
+## Epics **[ULTIMATE]**
+
+### List group epic discussions
+
+Gets a list of all discussions for a single epic.
+
+```
+GET /groups/:id/epics/:epic_id/discussions
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | ------------ |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+
+```json
+[
+ {
+ "id": "6a9c1750b37d513a43987b574953fceb50b03ce7",
+ "individual_note": false,
+ "notes": [
+ {
+ "id": 1126,
+ "type": "DiscussionNote",
+ "body": "discussion text",
+ "attachment": null,
+ "author": {
+ "id": 1,
+ "name": "root",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+ "web_url": "http://localhost:3000/root"
+ },
+ "created_at": "2018-03-03T21:54:39.668Z",
+ "updated_at": "2018-03-03T21:54:39.668Z",
+ "system": false,
+ "noteable_id": 3,
+ "noteable_type": "Epic",
+ "noteable_id": null,
+ "resolvable": false
+ },
+ {
+ "id": 1129,
+ "type": "DiscussionNote",
+ "body": "reply to the discussion",
+ "attachment": null,
+ "author": {
+ "id": 1,
+ "name": "root",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+ "web_url": "http://localhost:3000/root"
+ },
+ "created_at": "2018-03-04T13:38:02.127Z",
+ "updated_at": "2018-03-04T13:38:02.127Z",
+ "system": false,
+ "noteable_id": 3,
+ "noteable_type": "Epic",
+ "noteable_id": null,
+ "resolvable": false
+ }
+ ]
+ },
+ {
+ "id": "87805b7c09016a7058e91bdbe7b29d1f284a39e6",
+ "individual_note": true,
+ "notes": [
+ {
+ "id": 1128,
+ "type": null,
+ "body": "a single comment",
+ "attachment": null,
+ "author": {
+ "id": 1,
+ "name": "root",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon",
+ "web_url": "http://localhost:3000/root"
+ },
+ "created_at": "2018-03-04T09:17:22.520Z",
+ "updated_at": "2018-03-04T09:17:22.520Z",
+ "system": false,
+ "noteable_id": 3,
+ "noteable_type": "Epic",
+ "noteable_id": null,
+ "resolvable": false
+ }
+ ]
+ }
+]
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions
+```
+
+### Get single epic discussion
+
+Returns a single discussion for a specific group epic
+
+```
+GET /groups/:id/epics/:epic_id/discussions/:discussion_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `discussion_id` | integer | yes | The ID of a discussion |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
+```
+
+### Create new epic discussion
+
+Creates a new discussion to a single group epic. This is similar to creating
+a note but but another comments (replies) can be added to it later.
+
+```
+POST /groups/:id/epics/:epic_id/discussions
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `body` | string | yes | The content of a discussion |
+| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions?body=comment
+```
+
+### Add note to existing epic discussion
+
+Adds a new note to the discussion. This can also
+[create a discussion from a single comment](../user/discussions/#start-a-discussion-by-replying-to-a-standard-comment).
+
+```
+POST /groups/:id/epics/:epic_id/discussions/:discussion_id/notes
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `discussion_id` | integer | yes | The ID of a discussion |
+| `note_id` | integer | yes | The ID of a discussion note |
+| `body` | string | yes | The content of a discussion |
+| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
+```
+
+### Modify existing epic discussion note
+
+Modify existing discussion note of an epic.
+
+```
+PUT /groups/:id/epics/:epic_id/discussions/:discussion_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `discussion_id` | integer | yes | The ID of a discussion |
+| `note_id` | integer | yes | The ID of a discussion note |
+| `body` | string | yes | The content of a discussion |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
+```
+
+### Delete an epic discussion note
+
+Deletes an existing discussion note of an epic.
+
+```
+DELETE /groups/:id/epics/:epic_id/discussions/:discussion_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `discussion_id` | integer | yes | The ID of a discussion |
+| `note_id` | integer | yes | The ID of a discussion note |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/636
+```
+
## Merge requests
### List project merge request discussions
diff --git a/doc/api/epic_issues.md b/doc/api/epic_issues.md
index 438a3361dcc..ec59ea7068e 100644
--- a/doc/api/epic_issues.md
+++ b/doc/api/epic_issues.md
@@ -7,6 +7,7 @@ If a user is not a member of a group and the group is private, a `GET` request o
Epics are available only in Ultimate. If epics feature is not available a `403` status code will be returned.
## List issues for an epic
+
Gets all issues that are assigned to an epic and the authenticated user has access to.
```
diff --git a/doc/api/epic_links.md b/doc/api/epic_links.md
index 619ae6ea2dc..9ad90a6d0f1 100644
--- a/doc/api/epic_links.md
+++ b/doc/api/epic_links.md
@@ -12,6 +12,7 @@ If a user is not a member of a group and the group is private, a `GET` request o
Epics are available only in the [Ultimate/Gold tier](https://about.gitlab.com/pricing/). If the epics feature is not available, a `403` status code will be returned.
## List epics related to a given epic
+
Gets all child epics of an epic.
```
@@ -125,7 +126,7 @@ POST /groups/:id/epics/:epic_iid/epics
| --------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `epic_iid` | integer | yes | The internal ID of the (future parent) epic. |
-| `title` | integer | yes | The global ID of the child epic. Internal ID can't be used because they can conflict with epics from other groups. |
+| `title` | string | yes | The title of a newly created epic. |
```bash
curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/5/epics?title=Newpic
@@ -141,6 +142,7 @@ Example response:
"group_id": 49,
"parent_id": 23,
"has_children": false,
+ "has_issues": false,
"reference": "&2",
"url": "http://localhost/groups/group16/-/epics/2",
"relation_url": "http://localhost/groups/group16/-/epics/1/links/24"
diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md
index a1cb524499f..ea31abdd87e 100644
--- a/doc/api/geo_nodes.md
+++ b/doc/api/geo_nodes.md
@@ -192,12 +192,10 @@ Example response:
"job_artifacts_failed_count": nil,
"job_artifacts_synced_missing_on_primary_count": 0,
"job_artifacts_synced_in_percentage": "0.00%",
- "repositories_count": 41,
"projects_count": 41,
"repositories_failed_count": nil,
"repositories_synced_count": nil,
"repositories_synced_in_percentage": "0.00%",
- "wikis_count": 41,
"wikis_failed_count": nil,
"wikis_synced_count": nil,
"wikis_synced_in_percentage": "0.00%",
@@ -257,12 +255,10 @@ Example response:
"job_artifacts_failed_count": 1,
"job_artifacts_synced_missing_on_primary_count": 0,
"job_artifacts_synced_in_percentage": "50.00%",
- "repositories_count": 41,
"projects_count": 41,
"repositories_failed_count": 1,
"repositories_synced_count": 40,
"repositories_synced_in_percentage": "97.56%",
- "wikis_count": 41,
"wikis_failed_count": 0,
"wikis_synced_count": 41,
"wikis_synced_in_percentage": "100.00%",
@@ -300,7 +296,8 @@ Example response:
]
```
-Note: fields `wikis_count` and `repositories_count` are deprecated and will be deleted soon. Please use `projects_count` instead.
+NOTE: **Note:**
+In GitLab 12.0, deprecated fields `wikis_count` and `repositories_count` were removed. Use `projects_count` instead.
## Retrieve status about a specific Geo node
@@ -337,12 +334,10 @@ Example response:
"job_artifacts_failed_count": 1,
"job_artifacts_synced_missing_on_primary_count": 0,
"job_artifacts_synced_in_percentage": "50.00%",
- "repositories_count": 41,
"projects_count": 41,
"repositories_failed_count": 1,
"repositories_synced_count": 40,
"repositories_synced_in_percentage": "97.56%",
- "wikis_count": 41,
"wikis_failed_count": 0,
"wikis_synced_count": 41,
"wikis_synced_in_percentage": "100.00%",
@@ -362,7 +357,8 @@ Example response:
Note: The `health_status` parameter can only be in an "Healthy" or "Unhealthy" state, while the `health` parameter can be empty, "Healthy", or contain the actual error message.
-Note: Fields `wikis_count` and `repositories_count` are deprecated and will be deleted soon. Please use `projects_count` instead.
+NOTE: **Note:**
+In GitLab 12.0, deprecated fields `wikis_count` and `repositories_count` were removed. Use `projects_count` instead.
## Retrieve project sync or verification failures that occurred on the current node
diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md
index 10e1ef0e533..88e657a5d2f 100644
--- a/doc/api/graphql/index.md
+++ b/doc/api/graphql/index.md
@@ -47,6 +47,15 @@ A first iteration of a GraphQL API includes the following queries
1. `project` : Within a project it is also possible to fetch a `mergeRequest` by IID.
1. `group` : Only basic group information is currently supported.
+1. `namespace` : Within a namespace it is also possible to fetch `projects`.
+
+### Multiplex queries
+
+GitLab supports batching queries into a single request using
+[apollo-link-batch-http](https://www.apollographql.com/docs/link/links/batch-http). More
+info about multiplexed queries is also available for
+[graphql-ruby](https://graphql-ruby.org/queries/multiplex.html) the
+library GitLab uses on the backend.
## GraphiQL
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 907b443d355..20789a1d4a4 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -68,6 +68,7 @@ GET /groups?statistics=true
"statistics": {
"storage_size" : 212,
"repository_size" : 33,
+ "wiki_size" : 100,
"lfs_objects_size" : 123,
"job_artifacts_size" : 57
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 4fb3626f637..0d96cfa1b21 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -135,7 +135,11 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/1/issues/76/award_emoji",
"project":"http://example.com/api/v4/projects/1"
},
- "subscribed": false
+ "subscribed": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -265,7 +269,11 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
},
- "subscribed": false
+ "subscribed": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -403,7 +411,11 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
},
- "subscribed": false
+ "subscribed": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -500,6 +512,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -583,6 +599,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -674,6 +694,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -780,6 +804,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -865,6 +893,10 @@ Example response:
"notes": "http://example.com/api/v4/projects/1/issues/2/notes",
"award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
"project": "http://example.com/api/v4/projects/1"
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
}
}
```
@@ -931,7 +963,11 @@ Example response:
"due_date": null,
"web_url": "http://example.com/example/example/issues/12",
"confidential": false,
- "discussion_locked": false
+ "discussion_locked": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1029,7 +1065,11 @@ Example response:
"due_date": null,
"web_url": "http://example.com/example/example/issues/110",
"confidential": false,
- "discussion_locked": false
+ "discussion_locked": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
},
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10",
"body": "Vel voluptas atque dicta mollitia adipisci qui at.",
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index d51451b9c83..72973b69117 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -346,30 +346,58 @@ Example of response
> **Notes**:
>
> - [Introduced][ce-2893] in GitLab 8.5.
+> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
+> in [GitLab Premium][ee] 9.5.
-Get job artifacts of a project.
+Get the job's artifacts zipped archive of a project.
```
GET /projects/:id/jobs/:job_id/artifacts
```
-| Attribute | Type | Required | Description |
-|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
-| `job_id` | integer | yes | ID of a job. |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | yes | ID of a job. |
+| `job_token` **[PREMIUM]** | string | no | To be used with [triggers] for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
-Example requests:
+Example request using the `PRIVATE-TOKEN` header:
```sh
-curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts"
+curl --output artifacts.zip --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"
```
+To use this in a [`script` definition](../ci/yaml/README.md#script) inside
+`.gitlab-ci.yml` **[PREMIUM]**, you can use either:
+
+- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the job with ID
+ `42`. Note that the command is wrapped into single quotes since it contains a
+ colon (`:`):
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"'
+ ```
+
+- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the job with ID `42`:
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts?job_token=$CI_JOB_TOKEN"'
+ ```
+
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
-| 200 | Serves the artifacts file |
-| 404 | Build not found or no artifacts |
+| 200 | Serves the artifacts file. |
+| 404 | Build not found or no artifacts.|
[ce-2893]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2893
@@ -378,9 +406,13 @@ Possible response status codes:
> **Notes**:
>
> - [Introduced][ce-5347] in GitLab 8.10.
+> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
+> in [GitLab Premium][ee] 9.5.
-Download the artifacts archive from the given reference name and job provided the
-job finished successfully.
+Download the artifacts zipped archive from the given reference name and job,
+provided the job finished successfully. This is the same as
+[getting the job's artifacts](#get-job-artifacts), but by defining the job's
+name instead of its ID.
```
GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
@@ -388,24 +420,51 @@ GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
Parameters
-| Attribute | Type | Required | Description |
-|------------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
-| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
-| `job` | string | yes | The name of the job. |
+| Attribute | Type | Required | Description |
+|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
+| `job` | string | yes | The name of the job. |
+| `job_token` **[PREMIUM]** | string | no | To be used with [triggers] for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
-Example requests:
+Example request using the `PRIVATE-TOKEN` header:
```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
```
+To use this in a [`script` definition](../ci/yaml/README.md#script) inside
+`.gitlab-ci.yml` **[PREMIUM]**, you can use either:
+
+- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the `test` job
+ of the `master` branch. Note that the command is wrapped into single quotes
+ since it contains a colon (`:`):
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test"'
+ ```
+
+- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
+ For example, the following job will download the artifacts of the `test` job
+ of the `master` branch:
+
+ ```yaml
+ artifact_download:
+ stage: test
+ script:
+ - 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"'
+ ```
+
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
-| 200 | Serves the artifacts file |
-| 404 | Build not found or no artifacts |
+| 200 | Serves the artifacts file. |
+| 404 | Build not found or no artifacts.|
[ce-5347]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5347
@@ -414,7 +473,7 @@ Possible response status codes:
> Introduced in GitLab 10.0
Download a single artifact file from a job with a specified ID from within
-the job's artifacts archive. The file is extracted from the archive and
+the job's artifacts zipped archive. The file is extracted from the archive and
streamed to the client.
```
@@ -783,3 +842,7 @@ Example of response
"user": null
}
```
+
+[ee]: https://about.gitlab.com/pricing/
+[ee-2346]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2346
+[triggers]: ../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline-premium
diff --git a/doc/api/members.md b/doc/api/members.md
index 0593d2c20ea..8784d577f99 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -62,7 +62,9 @@ Example response:
## List all members of a group or project including inherited members
Gets a list of group or project members viewable by the authenticated user, including inherited members through ancestor groups.
-Returns multiple times the same user (with different member attributes) when the user is a member of the project/group and of one or more ancestor group.
+When a user is a member of the project/group and of one or more ancestor groups the user is returned only once with the project access_level (if exists)
+or the access_level for the user in the first group which he belongs to in the project groups ancestors chain.
+**Note:** We plan to [change](https://gitlab.com/gitlab-org/gitlab-ce/issues/62284) this behavior to return highest access_level instead.
```
GET /groups/:id/members/all
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 7992af15448..dd7810c3403 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -138,7 +138,11 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
- "squash": false
+ "squash": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -280,7 +284,11 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
- "squash": false
+ "squash": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -410,7 +418,11 @@ Parameters:
"human_time_estimate": null,
"human_total_time_spent": null
},
- "squash": false
+ "squash": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
]
```
@@ -545,7 +557,11 @@ Parameters:
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
"diverged_commits_count": 2,
- "rebase_in_progress": false
+ "rebase_in_progress": false,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -579,7 +595,7 @@ Parameters:
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=80&d=identicon",
"web_url": "http://localhost/user2"
- },
+ }
]
```
@@ -702,7 +718,11 @@ Parameters:
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
- }
+ },
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ },
"changes": [
{
"old_path": "VERSION",
@@ -865,7 +885,11 @@ POST /projects/:id/merge_requests
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1002,7 +1026,11 @@ Must include at least one non-required attribute from above.
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1155,7 +1183,11 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1313,7 +1345,11 @@ Parameters:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1349,7 +1385,7 @@ If the rebase operation is ongoing, the response will include the following:
```json
{
- "rebase_in_progress": true
+ "rebase_in_progress": true,
"merge_error": null
}
```
@@ -1360,7 +1396,7 @@ the following:
```json
{
"rebase_in_progress": false,
- "merge_error": null,
+ "merge_error": null
}
```
@@ -1369,7 +1405,7 @@ If the rebase operation fails, the response will include the following:
```json
{
"rebase_in_progress": false,
- "merge_error": "Rebase failed. Please rebase locally",
+ "merge_error": "Rebase failed. Please rebase locally"
}
```
@@ -1576,7 +1612,11 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
@@ -1705,7 +1745,11 @@ Example response:
"head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f",
"start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00"
},
- "diverged_commits_count": 2
+ "diverged_commits_count": 2,
+ "task_completion_status":{
+ "count":0,
+ "completed_count":0
+ }
}
```
diff --git a/doc/api/notes.md b/doc/api/notes.md
index dfde80c6441..c09129c22d4 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -1,6 +1,11 @@
# Notes API
-Notes are comments on snippets, issues or merge requests.
+Notes are comments on:
+
+- Snippets
+- Issues
+- Merge requests
+- Epics **[ULTIMATE]**
This includes system notes, which are notes about changes to the object (for example, when a milestone changes, there will be a corresponding system note). Label notes are not part of this API, but recorded as separate events in [resource label events](resource_label_events.md).
@@ -390,3 +395,126 @@ Parameters:
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/7/notes/1602
```
+
+## Epics **[ULTIMATE]**
+
+### List all epic notes
+
+Gets a list of all notes for a single epic. Epic notes are comments users can post to an epic.
+
+```
+GET /groups/:id/epics/:epic_id/notes
+GET /groups/:id/epics/:epic_id/notes?sort=asc&order_by=updated_at
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of a group epic |
+| `sort` | string | no | Return epic notes sorted in `asc` or `desc` order. Default is `desc` |
+| `order_by` | string | no | Return epic notes ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/notes
+```
+
+### Get single epic note
+
+Returns a single note for a given epic.
+
+```
+GET /groups/:id/epics/:epic_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `note_id` | integer | yes | The ID of a note |
+
+```json
+{
+ "id": 52,
+ "title": "Epic",
+ "file_name": "epic.rb",
+ "author": {
+ "id": 1,
+ "username": "pipin",
+ "email": "admin@example.com",
+ "name": "Pip",
+ "state": "active",
+ "created_at": "2013-09-30T13:46:01Z"
+ },
+ "expires_at": null,
+ "updated_at": "2013-10-02T07:34:20Z",
+ "created_at": "2013-10-02T07:34:20Z"
+}
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/notes/1
+```
+
+### Create new epic note
+
+Creates a new note for a single epic. Epic notes are comments users can post to an epic.
+If you create a note where the body only contains an Award Emoji, you'll receive this object back.
+
+```
+POST /groups/:id/epics/:epic_id/notes
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `body` | string | yes | The content of a note |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
+```
+
+### Modify existing epic note
+
+Modify existing note of an epic.
+
+```
+PUT /groups/:id/epics/:epic_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `note_id` | integer | yes | The ID of a note |
+| `body` | string | yes | The content of a note |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
+```
+
+### Delete an epic note
+
+Deletes an existing note of an epic.
+
+```
+DELETE /groups/:id/epics/:epic_id/notes/:note_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `note_id` | integer | yes | The ID of a note |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/52/notes/1659
+```
diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md
index c831cc52a93..327781f6c93 100644
--- a/doc/api/project_clusters.md
+++ b/doc/api/project_clusters.md
@@ -167,6 +167,7 @@ Parameters:
| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
| `platform_kubernetes_attributes[namespace]` | String | no | The unique namespace related to the project |
| `platform_kubernetes_attributes[authorization_type]` | String | no | The cluster authorization type: `rbac`, `abac` or `unknown_authorization`. Defaults to `rbac`. |
+| `environment_scope` | String | no | The associated environment to the cluster. Defaults to `*` **[PREMIUM]** |
Example request:
@@ -256,6 +257,7 @@ Parameters:
| `platform_kubernetes_attributes[token]` | String | no | The token to authenticate against Kubernetes |
| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
| `platform_kubernetes_attributes[namespace]` | String | no | The unique namespace related to the project |
+| `environment_scope` | String | no | The associated environment to the cluster **[PREMIUM]** |
NOTE: **Note:**
`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
diff --git a/doc/api/project_level_variables.md b/doc/api/project_level_variables.md
index 4a6f5624394..66a749e4811 100644
--- a/doc/api/project_level_variables.md
+++ b/doc/api/project_level_variables.md
@@ -1,4 +1,4 @@
-# Project-level Variables API
+# Project-level Variables API
## List project variables
@@ -52,7 +52,9 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
{
"key": "TEST_VARIABLE_1",
"variable_type": "env_var",
- "value": "TEST_1"
+ "value": "TEST_1",
+ "protected": false,
+ "masked": true
}
```
@@ -64,13 +66,15 @@ Create a new variable.
POST /projects/:id/variables
```
-| Attribute | Type | required | Description |
-|-----------------|---------|----------|-----------------------|
-| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
-| `value` | string | yes | The `value` of a variable |
-| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
-| `protected` | boolean | no | Whether the variable is protected |
+| Attribute | Type | required | Description |
+|---------------------|---------|----------|-----------------------|
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
+| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
+| `protected` | boolean | no | Whether the variable is protected |
+| `masked` | boolean | no | Whether the variable is masked |
+| `environment_scope` | string | no | The `environment_scope` of the variable **[PREMIUM]** |
```
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
@@ -80,8 +84,11 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
{
"key": "NEW_VARIABLE",
"value": "new value",
+ "protected": false,
"variable_type": "env_var",
- "protected": false
+ "protected": false,
+ "masked": false,
+ "environment_scope": "*"
}
```
@@ -93,13 +100,15 @@ Update a project's variable.
PUT /projects/:id/variables/:key
```
-| Attribute | Type | required | Description |
-|-----------------|---------|----------|-------------------------|
-| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `key` | string | yes | The `key` of a variable |
-| `value` | string | yes | The `value` of a variable |
-| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
-| `protected` | boolean | no | Whether the variable is protected |
+| Attribute | Type | required | Description |
+|---------------------|---------|----------|-------------------------|
+| `id` | integer/string | yes | The ID of a project or [urlencoded NAMESPACE/PROJECT_NAME of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `key` | string | yes | The `key` of a variable |
+| `value` | string | yes | The `value` of a variable |
+| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
+| `protected` | boolean | no | Whether the variable is protected |
+| `masked` | boolean | no | Whether the variable is masked |
+| `environment_scope` | string | no | The `environment_scope` of the variable **[PREMIUM]** |
```
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
@@ -110,7 +119,9 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
"key": "NEW_VARIABLE",
"value": "updated value",
"variable_type": "env_var",
- "protected": true
+ "protected": true,
+ "masked": false,
+ "environment_scope": "*"
}
```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 951961e45ff..75669d85803 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -153,6 +153,7 @@ When the user is authenticated and `simple` is not set this returns something li
"commit_count": 37,
"storage_size": 1038090,
"repository_size": 1038090,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
@@ -234,6 +235,7 @@ When the user is authenticated and `simple` is not set this returns something li
"commit_count": 12,
"storage_size": 2066080,
"repository_size": 2066080,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
@@ -342,6 +344,7 @@ GET /users/:user_id/projects
"commit_count": 37,
"storage_size": 1038090,
"repository_size": 1038090,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
@@ -423,6 +426,7 @@ GET /users/:user_id/projects
"commit_count": 12,
"storage_size": 2066080,
"repository_size": 2066080,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
@@ -548,6 +552,7 @@ GET /projects/:id
"commit_count": 37,
"storage_size": 1038090,
"repository_size": 1038090,
+ "wiki_size" : 0,
"lfs_objects_size": 0,
"job_artifacts_size": 0
},
diff --git a/doc/api/repository_storage_health.md b/doc/api/repository_storage_health.md
deleted file mode 100644
index edf4b04acea..00000000000
--- a/doc/api/repository_storage_health.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Circuitbreaker API
-
-NOTE: **Deprecated:**
-Support of the circuit breaker is removed, as Gitaly can be configured to
-to work without NFS and [communicate solely over HTTP](../administration/gitaly/index.md).
diff --git a/doc/api/resource_label_events.md b/doc/api/resource_label_events.md
index e1f9ffa9472..f0a7ac4e41d 100644
--- a/doc/api/resource_label_events.md
+++ b/doc/api/resource_label_events.md
@@ -88,6 +88,92 @@ Parameters:
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/resource_label_events/1
```
+## Epics **[ULTIMATE]**
+
+### List group epic label events
+
+Gets a list of all label events for a single epic.
+
+```
+GET /groups/:id/epics/:epic_id/resource_label_events
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ---------------- | ---------- | ------------ |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+
+```json
+[
+ {
+ "id": 106,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-19T11:43:01.746Z",
+ "resource_type": "Epic",
+ "resource_id": 33,
+ "label": {
+ "id": 73,
+ "name": "a1",
+ "color": "#34495E",
+ "description": ""
+ },
+ "action": "add"
+ },
+ {
+ "id": 107,
+ "user": {
+ "id": 1,
+ "name": "Administrator",
+ "username": "root",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "http://gitlab.example.com/root"
+ },
+ "created_at": "2018-08-19T11:43:01.746Z",
+ "resource_type": "Epic",
+ "resource_id": 33,
+ "label": {
+ "id": 37,
+ "name": "glabel2",
+ "color": "#A8D695",
+ "description": ""
+ },
+ "action": "add"
+ }
+]
+```
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/11/resource_label_events
+```
+
+### Get single epic label event
+
+Returns a single label event for a specific group epic
+
+```
+GET /groups/:id/epics/:epic_id/resource_label_events/:resource_label_event_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `epic_id` | integer | yes | The ID of an epic |
+| `resource_label_event_id` | integer | yes | The ID of a label event |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/1/epics/11/resource_label_events/107
+```
+
## Merge requests
### List project merge request label events
diff --git a/doc/api/scim.md b/doc/api/scim.md
index 4595c6f2ed3..3870ea788e7 100644
--- a/doc/api/scim.md
+++ b/doc/api/scim.md
@@ -1,4 +1,4 @@
-# SCIM API
+# SCIM API **[SILVER ONLY]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9388) in [GitLab Silver](https://about.gitlab.com/pricing/) 11.10.
diff --git a/doc/api/search.md b/doc/api/search.md
index c2dd72479c1..da81c8321c9 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -19,6 +19,8 @@ GET /search
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs, users.
+If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md). **[STARTER]**
+
The response depends on the requested scope.
### Scope: projects
@@ -281,6 +283,98 @@ Example response:
]
```
+### Scope: wiki_blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=wiki_blobs&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "home",
+ "data": "hello\n\nand bye\n\nend",
+ "filename": "home.md",
+ "id": null,
+ "ref": "master",
+ "startline": 5,
+ "project_id": 6
+ }
+]
+```
+
+### Scope: commits **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=commits&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "id": "4109c2d872d5fdb1ed057400d103766aaea97f98",
+ "short_id": "4109c2d8",
+ "title": "goodbye $.browser",
+ "created_at": "2013-02-18T22:02:54.000Z",
+ "parent_ids": [
+ "59d05353ab575bcc2aa958fe1782e93297de64c9"
+ ],
+ "message": "goodbye $.browser\n",
+ "author_name": "angus croll",
+ "author_email": "anguscroll@gmail.com",
+ "authored_date": "2013-02-18T22:02:54.000Z",
+ "committer_name": "angus croll",
+ "committer_email": "anguscroll@gmail.com",
+ "committed_date": "2013-02-18T22:02:54.000Z",
+ "project_id": 6
+ }
+]
+```
+
+### Scope: blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+Filters are available for this scope:
+- filename
+- path
+- extension
+
+to use a filter simply include it in your query like so: `a query filename:some_name*`.
+
+You may use wildcards (`*`) to use glob matching.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=blobs&search=installation
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "README",
+ "data": "```\n\n## Installation\n\nQuick start using the [pre-built",
+ "filename": "README.md",
+ "id": null,
+ "ref": "master",
+ "startline": 46,
+ "project_id": 6
+ }
+]
+```
+
### Scope: users
```bash
@@ -320,6 +414,8 @@ GET /groups/:id/search
Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users.
+If Elasticsearch is enabled additional scopes available are blobs, wiki_blobs and commits. Find more about [the feature](../integration/elasticsearch.md). **[STARTER]**
+
The response depends on the requested scope.
### Scope: projects
@@ -520,6 +616,98 @@ Example response:
]
```
+### Scope: wiki_blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/6/search?scope=wiki_blobs&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "home",
+ "data": "hello\n\nand bye\n\nend",
+ "filename": "home.md",
+ "id": null,
+ "ref": "master",
+ "startline": 5,
+ "project_id": 6
+ }
+]
+```
+
+### Scope: commits **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/6/search?scope=commits&search=bye
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "id": "4109c2d872d5fdb1ed057400d103766aaea97f98",
+ "short_id": "4109c2d8",
+ "title": "goodbye $.browser",
+ "created_at": "2013-02-18T22:02:54.000Z",
+ "parent_ids": [
+ "59d05353ab575bcc2aa958fe1782e93297de64c9"
+ ],
+ "message": "goodbye $.browser\n",
+ "author_name": "angus croll",
+ "author_email": "anguscroll@gmail.com",
+ "authored_date": "2013-02-18T22:02:54.000Z",
+ "committer_name": "angus croll",
+ "committer_email": "anguscroll@gmail.com",
+ "committed_date": "2013-02-18T22:02:54.000Z",
+ "project_id": 6
+ }
+]
+```
+
+### Scope: blobs **[STARTER]**
+
+This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled.
+
+Filters are available for this scope:
+- filename
+- path
+- extension
+
+to use a filter simply include it in your query like so: `a query filename:some_name*`.
+
+You may use wildcards (`*`) to use glob matching.
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/6/search?scope=blobs&search=installation
+```
+
+Example response:
+
+```json
+
+[
+ {
+ "basename": "README",
+ "data": "```\n\n## Installation\n\nQuick start using the [pre-built",
+ "filename": "README.md",
+ "id": null,
+ "ref": "master",
+ "startline": 46,
+ "project_id": 6
+ }
+]
+```
+
### Scope: users
```bash
@@ -556,7 +744,6 @@ GET /projects/:id/search
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
-| `ref` | string | no | The name of a repository branch or tag to search on. The project's default branch is used by default. This is only applicable for scopes: commits, blobs, and wiki_blobs. |
Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users.
@@ -851,7 +1038,7 @@ Blobs searches are performed on both filenames and contents. Search results:
times in the content.
```bash
-curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation&ref=feature
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation
```
Example response:
@@ -864,7 +1051,7 @@ Example response:
"data": "```\n\n## Installation\n\nQuick start using the [pre-built",
"filename": "README.md",
"id": null,
- "ref": "feature",
+ "ref": "master",
"startline": 46,
"project_id": 6
}
diff --git a/doc/api/services.md b/doc/api/services.md
index 742abccb69e..898cfad7254 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -754,6 +754,7 @@ Parameters:
| `recipients` | string | yes | Comma-separated list of recipient email addresses |
| `add_pusher` | boolean | no | Add pusher to recipients list |
| `notify_only_broken_pipelines` | boolean | no | Notify only broken pipelines |
+| `notify_only_default_branch` | boolean | no | Send notifications only for the default branch ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28271)) |
### Delete Pipeline-Emails service
@@ -1100,6 +1101,75 @@ Get JetBrains TeamCity CI service settings for a project.
GET /projects/:id/services/teamcity
```
+## Jenkins CI **[STARTER]**
+
+A continuous integration and build server
+
+### Create/Edit Jenkins CI service
+
+Set Jenkins CI service for a project.
+
+```
+PUT /projects/:id/services/jenkins
+```
+
+Parameters:
+
+- `jenkins_url` (**required**) - Jenkins URL like http://jenkins.example.com
+- `project_name` (**required**) - The URL-friendly project name. Example: my_project_name
+- `username` (optional) - A user with access to the Jenkins server, if applicable
+- `password` (optional) - The password of the user
+
+### Delete Jenkins CI service
+
+Delete Jenkins CI service for a project.
+
+```
+DELETE /projects/:id/services/jenkins
+```
+
+### Get Jenkins CI service settings
+
+Get Jenkins CI service settings for a project.
+
+```
+GET /projects/:id/services/jenkins
+```
+
+## Jenkins CI (Deprecated) Service
+
+A continuous integration and build server
+
+### Create/Edit Jenkins CI (Deprecated) service
+
+Set Jenkins CI (Deprecated) service for a project.
+
+```
+PUT /projects/:id/services/jenkins-deprecated
+```
+
+Parameters:
+
+- `project_url` (**required**) - Jenkins project URL like http://jenkins.example.com/job/my-project/
+- `multiproject_enabled` (optional) - Multi-project mode is configured in Jenkins GitLab Hook plugin
+- `pass_unstable` (optional) - Unstable builds will be treated as passing
+
+### Delete Jenkins CI (Deprecated) service
+
+Delete Jenkins CI (Deprecated) service for a project.
+
+```
+DELETE /projects/:id/services/jenkins-deprecated
+```
+
+### Get Jenkins CI (Deprecated) service settings
+
+Get Jenkins CI (Deprecated) service settings for a project.
+
+```
+GET /projects/:id/services/jenkins-deprecated
+```
+
[jira-doc]: ../user/project/integrations/jira.md
[old-jira-api]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/doc/api/services.md#jira
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index f90447e124e..1ce0b1e7a62 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -165,15 +165,15 @@ Parameters:
|:--------------|:-------|:---------|:---------------------------------------------------|
| `title` | string | yes | Title of a snippet. |
| `file_name` | string | yes | Name of a snippet file. |
-| `content` | string | yes | Content of a snippet. |
+| `code` | string | yes | Content of a snippet. |
| `description` | string | no | Description of a snippet. |
-| `visibility` | string | no | Snippet's [visibility](#snippet-visibility-level). |
+| `visibility` | string | yes | Snippet's [visibility](#snippet-visibility-level). |
Example request:
```sh
curl --request POST \
- --data '{"title": "This is a snippet", "content": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' \
+ --data '{"title": "This is a snippet", "code": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' \
--header 'Content-Type: application/json' \
--header "PRIVATE-TOKEN: valid_api_token" \
https://gitlab.example.com/api/v4/snippets
@@ -222,14 +222,14 @@ Parameters:
| `title` | string | no | Title of a snippet. |
| `file_name` | string | no | Name of a snippet file. |
| `description` | string | no | Description of a snippet. |
-| `content` | string | no | Content of a snippet. |
+| `code` | string | no | Content of a snippet. |
| `visibility` | string | no | Snippet's [visibility](#snippet-visibility-level). |
Example request:
```sh
curl --request PUT \
- --data '{"title": "foo", "content": "bar"}' \
+ --data '{"title": "foo", "code": "bar"}' \
--header 'Content-Type: application/json' \
--header "PRIVATE-TOKEN: valid_api_token" \
https://gitlab.example.com/api/v4/snippets/1
diff --git a/doc/api/users.md b/doc/api/users.md
index d3e67d3d510..47028c679b8 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -140,8 +140,7 @@ GET /users
"can_create_project": true,
"two_factor_enabled": true,
"external": false,
- "private_profile": false,
- "highest_role":10
+ "private_profile": false
}
]
```
@@ -257,7 +256,8 @@ Parameters:
"can_create_project": true,
"two_factor_enabled": true,
"external": false,
- "private_profile": false
+ "private_profile": false,
+ "highest_role":10
}
```
diff --git a/doc/api/vulnerabilities.md b/doc/api/vulnerabilities.md
index 87f77613ad3..390d0966244 100644
--- a/doc/api/vulnerabilities.md
+++ b/doc/api/vulnerabilities.md
@@ -1,4 +1,4 @@
-# Vulnerabilities API
+# Vulnerabilities API **[ULTIMATE]**
Every API call to vulnerabilities must be authenticated.
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ee/index.md b/doc/articles/how_to_configure_ldap_gitlab_ee/index.md
index 4ce96fcb230..3e6f3130437 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ee/index.md
+++ b/doc/articles/how_to_configure_ldap_gitlab_ee/index.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html'
+redirect_to: '../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html).
+This document was moved to [another location](../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md).
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 27bde2ac0f1..8b70699cdb5 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -1,6 +1,7 @@
---
comments: false
description: "Learn how to use GitLab CI/CD, the GitLab built-in Continuous Integration, Continuous Deployment, and Continuous Delivery toolset to build, test, and deploy your application."
+type: index
---
# GitLab CI/CD
@@ -31,6 +32,9 @@ your app.
For a complete overview of these methodologies and GitLab CI/CD,
read the [Introduction to CI/CD with GitLab](introduction/index.md).
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For a video demonstration of GitLab CI/CD, see [Demo: CI/CD with GitLab](https://www.youtube.com/watch?v=1iXFbchozdY).
+
## Getting started
GitLab CI/CD is configured by a file called `.gitlab-ci.yml` placed
@@ -123,12 +127,10 @@ Its feature set is listed on the table below according to DevOps stages.
## Examples
-GitLab provides examples of configuring GitLab CI/CD in the form of:
+Find example project code and tutorials for using GitLab CI/CD with a variety of app frameworks, languages, and platforms
+on the [CI Examples](examples/README.md) page.
-- A collection of [examples and other resources](examples/README.md).
-- Example projects that are available at the [`gitlab-examples`](https://gitlab.com/gitlab-examples) group. For example, see:
- - [`multi-project-pipelines`](https://gitlab.com/gitlab-examples/multi-project-pipelines) for examples of implementing multi-project pipelines.
- - [`review-apps-nginx`](https://gitlab.com/gitlab-examples/review-apps-nginx/) provides an example of using Review Apps.
+GitLab also provides [example projects](https://gitlab.com/gitlab-examples) pre-configured to use GitLab CI/CD.
## Administration **[CORE ONLY]**
@@ -160,6 +162,33 @@ See also the [Why CI/CD?](https://docs.google.com/presentation/d/1OGgk2Tcxbpl7DJ
As GitLab CI/CD has evolved, certain breaking changes have
been necessary. These are:
+#### 12.0
+
+- [Use refspec to clone/fetch git
+ repository](https://gitlab.com/gitlab-org/gitlab-runner/issues/4069).
+- [Old cache
+ configuration](https://gitlab.com/gitlab-org/gitlab-runner/issues/4070).
+- [Old metrics server
+ configuration](https://gitlab.com/gitlab-org/gitlab-runner/issues/4072).
+- [Remove
+ `FF_K8S_USE_ENTRYPOINT_OVER_COMMAND`](https://gitlab.com/gitlab-org/gitlab-runner/issues/4073).
+- [Remove Linux distributions that reach
+ EOL](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1130).
+- [Update command line API for helper
+ images](https://gitlab.com/gitlab-org/gitlab-runner/issues/4013).
+- [Remove old `git clean`
+ flow](https://gitlab.com/gitlab-org/gitlab-runner/issues/4175).
+
+#### 11.0
+
+- No breaking changes.
+
+#### 10.0
+
+- No breaking changes.
+
+#### 9.0
+
- [CI variables renaming for GitLab 9.0](variables/deprecated_variables.md#gitlab-90-renamed-variables). Read about the
deprecated CI variables and what you should use for GitLab 9.0+.
- [New CI job permissions model](../user/project/new_ci_build_permissions_model.md).
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index 3f72fe3e9eb..9a5a3624c73 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -1,3 +1,7 @@
+---
+type: index, concepts, howto
+---
+
# Cache dependencies in GitLab CI/CD
GitLab CI/CD provides a caching mechanism that can be used to save time
@@ -60,7 +64,7 @@ In summary:
- Caches are disabled if not defined globally or per job (using `cache:`).
- Caches are available for all jobs in your `.gitlab-ci.yml` if enabled globally.
-- Caches can be used by subsequent pipelines of that very same job (a script in
+- Caches can be used by subsequent pipelines of that same job (a script in
a stage) in which the cache was created (if not defined globally).
- Caches are stored where the Runner is installed **and** uploaded to S3 if
[distributed cache is enabled](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching).
@@ -87,7 +91,7 @@ cache, when declaring `cache` in your jobs, use one or a mix of the following:
that share their cache.
- [Use sticky Runners](../runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
that will be only available to a particular project.
-- [Use a `key`](../yaml/README.md#cachekey) that fits your workflow (e.g.,
+- [Use a `key`](../yaml/README.md#cachekey) that fits your workflow (for example,
different caches on each branch). For that, you can take advantage of the
[CI/CD predefined variables](../variables/README.md#predefined-environment-variables).
@@ -100,7 +104,7 @@ From the perspective of the Runner, in order for cache to work effectively, one
of the following must be true:
- Use a single Runner for all your jobs.
-- Use multiple Runners (in autoscale mode or not) that use.
+- Use multiple Runners (in autoscale mode or not) that use
[distributed caching](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching),
where the cache is stored in S3 buckets (like shared Runners on GitLab.com).
- Use multiple Runners (not in autoscale mode) of the same architecture that
@@ -420,7 +424,7 @@ mismatch and a few ideas how to fix it.
Let's explore some examples.
----
+#### Examples
Let's assume you have only one Runner assigned to your project, so the cache
will be stored in the Runner's machine by default. If two jobs, A and B,
@@ -462,8 +466,6 @@ job B:
To fix that, use different `keys` for each job.
----
-
In another case, let's assume you have more than one Runners assigned to your
project, but the distributed cache is not enabled. We want the second time the
pipeline is run, `job A` and `job B` to re-use their cache (which in this case
@@ -526,3 +528,15 @@ Behind the scenes, this works by increasing a counter in the database, and the
value of that counter is used to create the key for the cache by appending an
integer to it: `-1`, `-2`, etc. After a push, a new key is generated and the
old cache is not valid anymore.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/chatops/README.md b/doc/ci/chatops/README.md
index 5ecdf0f8c54..241134783da 100644
--- a/doc/ci/chatops/README.md
+++ b/doc/ci/chatops/README.md
@@ -1,3 +1,7 @@
+---
+type: index, concepts, howto
+---
+
# GitLab ChatOps
> **Notes:**
@@ -10,7 +14,10 @@ GitLab ChatOps provides a method to interact with CI/CD jobs through chat servic
## How it works
-GitLab ChatOps is built upon two existing features, [GitLab CI/CD](../README.md) and [Slack Slash Commmands](../../user/project/integrations/slack_slash_commands.md).
+GitLab ChatOps is built upon two existing features:
+
+- [GitLab CI/CD](../README.md).
+- [Slack Slash Commands](../../user/project/integrations/slack_slash_commands.md).
A new `run` action has been added to the [slash commands](../../integration/slash_commands.md), which takes two arguments: a `<job name>` to execute and the `<job arguments>`. When executed, ChatOps will look up the specified job name and attempt to match it to a corresponding job in [.gitlab-ci.yml](../yaml/README.md). If a matching job is found on `master`, a pipeline containing just that job is scheduled. Two additional [CI/CD variables](../variables/README.md#predefined-environment-variables) are passed to the job: `CHAT_INPUT` contains any additional arguments, and `CHAT_CHANNEL` is set to the name of channel the action was triggered in.
@@ -22,9 +29,9 @@ After the job has finished, its output is sent back to Slack provided it has com
Since ChatOps is built upon GitLab CI/CD, the job has all the same features and functions available. There a few best practices to consider however when creating ChatOps jobs:
-* It is strongly recommended to set `only: [chat]` so the job does not run as part of the standard CI pipeline.
-* If the job is set to `when: manual`, the pipeline will be created however the job will wait to be started.
-* It is important to keep in mind that there is very limited support for access control. If the user who triggered the slash command is a developer in the project, the job will run. The job itself can utilize existing [CI/CD variables](../variables/README.html#predefined-environment-variables) like `GITLAB_USER_ID` to perform additional rights validation, however these variables can be [overridden](../variables/README.html#priority-of-environment-variables).
+- It is strongly recommended to set `only: [chat]` so the job does not run as part of the standard CI pipeline.
+- If the job is set to `when: manual`, the pipeline will be created however the job will wait to be started.
+- It is important to keep in mind that there is limited support for access control. If the user who triggered the slash command is a developer in the project, the job will run. The job itself can utilize existing [CI/CD variables](../variables/README.html#predefined-environment-variables) like `GITLAB_USER_ID` to perform additional rights validation, however these variables can be [overridden](../variables/README.html#priority-of-environment-variables).
### Controlling the ChatOps reply
@@ -59,3 +66,15 @@ You can find and download the official GitLab ChatOps icon here.
![GitLab ChatOps bot icon](img/gitlab-chatops-icon-small.png)
[Download bigger image](img/gitlab-chatops-icon.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
index 1c8e12d229c..9126eb65f07 100644
--- a/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
+++ b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
@@ -1,8 +1,15 @@
+---
+type: howto
+---
+
# Using GitLab CI/CD with a Bitbucket Cloud repository **[PREMIUM]**
-GitLab CI/CD can be used with Bitbucket Cloud by creating a
-[CI/CD project](https://docs.gitlab.com/ee/user/project/ci_cd_for_external_repo.html) and connecting
-your Git repository via URL.
+GitLab CI/CD can be used with Bitbucket Cloud by:
+
+1. Creating a [CI/CD project](https://docs.gitlab.com/ee/user/project/ci_cd_for_external_repo.html).
+1. Connecting your Git repository via URL.
+
+To use GitLab CI/CD with a Bitbucket Cloud repository:
1. In GitLab create a **CI/CD for external repo**, select **Repo by URL** and
create the project.
@@ -16,13 +23,13 @@ your Git repository via URL.
with `api` scope. This will be used to authenticate requests from the web
hook that will be created in Bitbucket to notify GitLab of new commits.
-1. In Bitbucket from **Settings > Webhooks** create a new web hook to notify
+1. In Bitbucket, from **Settings > Webhooks**, create a new web hook to notify
GitLab of new commits.
The web hook URL should be set to the GitLab API to trigger pull mirroring,
using the Personal Access Token we just generated for authentication.
- ```
+ ```text
https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
```
@@ -33,27 +40,27 @@ your Git repository via URL.
After saving, test the web hook by pushing a change to your Bitbucket
repository.
-1. In Bitbucket create an **App Password** from **Bitbucket Settings > App
+1. In Bitbucket, create an **App Password** from **Bitbucket Settings > App
Passwords** to authenticate the build status script setting commit build
statuses in Bitbucket. Repository write permissions are required.
![Bitbucket Cloud webhook](img/bitbucket_app_password.png)
-1. In GitLab from **Settings > CI/CD > Environment variables** add variables to allow
- communication with Bitbucket via the Bitbucket API.
+1. In GitLab, from **Settings > CI/CD > Environment variables**, add variables to allow
+ communication with Bitbucket via the Bitbucket API:
- `BITBUCKET_ACCESS_TOKEN`: the Bitbucket app password created above
+ `BITBUCKET_ACCESS_TOKEN`: the Bitbucket app password created above.
- `BITBUCKET_USERNAME`: the username of the Bitbucket account
+ `BITBUCKET_USERNAME`: the username of the Bitbucket account.
- `BITBUCKET_NAMESPACE`: set this if your GitLab and Bitbucket namespaces differ
+ `BITBUCKET_NAMESPACE`: set this if your GitLab and Bitbucket namespaces differ.
- `BITBUCKET_REPOSITORY`: set this if your GitLab and Bitbucket project names differ
+ `BITBUCKET_REPOSITORY`: set this if your GitLab and Bitbucket project names differ.
-1. In Bitbucket add a script to push the pipeline status to Bitbucket.
+1. In Bitbucket, add a script to push the pipeline status to Bitbucket.
> Note: changes made in GitLab will be overwritten by any changes made
- upstream in Bitbucket.
+ > upstream in Bitbucket.
Create a file `build_status` and insert the script below and run
`chmod +x build_status` in your terminal to make the script executable.
@@ -111,7 +118,7 @@ your Git repository via URL.
1. Still in Bitbucket, create a `.gitlab-ci.yml` file to use the script to push
pipeline success and failures to Bitbucket.
- ```
+ ```yaml
stages:
- test
- ci_status
@@ -145,3 +152,15 @@ GitLab is now configured to mirror changes from Bitbucket, run CI/CD pipelines
configured in `.gitlab-ci.yml` and push the status to Bitbucket.
[pull-mirroring]: ../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/ci_cd_for_external_repos/github_integration.md b/doc/ci/ci_cd_for_external_repos/github_integration.md
index 0e2acf957e0..2a8ddb610f9 100644
--- a/doc/ci/ci_cd_for_external_repos/github_integration.md
+++ b/doc/ci/ci_cd_for_external_repos/github_integration.md
@@ -1,24 +1,21 @@
+---
+type: howto
+---
+
# Using GitLab CI/CD with a GitHub repository **[PREMIUM]**
GitLab CI/CD can be used with **GitHub.com** and **GitHub Enterprise** by
-creating a [CI/CD project](https://docs.gitlab.com/ee/user/project/ci_cd_for_external_repo.html) to connect your GitHub repository to
+creating a [CI/CD project](index.md) to connect your GitHub repository to
GitLab.
-NOTE: **Note:**
-To use **GitHub Enterprise** with **GitLab.com** you should use the
-manual method.
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+Watch a video on [Using GitLab CI/CD pipelines with GitHub repositories](https://www.youtube.com/watch?v=qgl3F2j-1cI).
## Connect with GitHub integration
If the [GitHub integration](../../integration/github.md) has been enabled by your GitLab
administrator:
-NOTE: **Note:**
-Due to a 10-token limitation on the [GitHub OAuth Implementation](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#creating-multiple-tokens-for-oauth-apps),
-if you import more than 10 times, your oldest imported project's token will be
-revoked. See issue [#9147](https://gitlab.com/gitlab-org/gitlab-ee/issues/9147)
-for more information.
-
1. In GitLab create a **CI/CD for external repo** project and select
**GitHub**.
@@ -31,23 +28,33 @@ for more information.
1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD](../quick_start/README.md).
-GitLab will import the project, enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter), enable
-[GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html), and create a web hook
-on GitHub to notify GitLab of new commits.
+GitLab will:
+
+1. Import the project.
+1. Enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter).
+1. Enable [GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html).
+1. Create a web hook on GitHub to notify GitLab of new commits.
+
+CAUTION: **Caution:**
+Due to a 10-token limitation on the [GitHub OAuth Implementation](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#creating-multiple-tokens-for-oauth-apps),
+if you import more than 10 times, your oldest imported project's token will be
+revoked. See issue [#9147](https://gitlab.com/gitlab-org/gitlab-ee/issues/9147)
+for more information.
## Connect with Personal Access Token
-NOTE: **Note:** Personal access tokens can only be used to connect GitHub.com
+NOTE: **Note:**
+Personal access tokens can only be used to connect GitHub.com
repositories to GitLab.
If you are not using the [GitHub integration](../../integration/github.md), you can
still perform a one-off authorization with GitHub to grant GitLab access your
repositories:
-1. Open https://github.com/settings/tokens/new to create a **Personal Access
+1. Open <https://github.com/settings/tokens/new> to create a **Personal Access
Token**. This token with be used to access your repository and push commit
statuses to GitHub.
-
+
The `repo` and `admin:repo_hook` should be enable to allow GitLab access to
your project, update commit statuses, and create a web hook to notify
GitLab of new commits.
@@ -62,20 +69,26 @@ repositories:
1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD](../quick_start/README.md).
-GitLab will import the project, enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter), enable
-[GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html), and create a web hook
-on GitHub to notify GitLab of new commits.
+GitLab will:
+
+1. Import the project.
+1. Enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter).
+1. Enable [GitHub project integration](https://docs.gitlab.com/ee/user/project/integrations/github.html).
+1. Create a web hook on GitHub to notify GitLab of new commits.
## Connect manually
+NOTE: **Note:**
+To use **GitHub Enterprise** with **GitLab.com** use this method.
+
If the [GitHub integration](../../integration/github.md) is not enabled, or is enabled
for a different GitHub instance, you GitLab CI/CD can be manually enabled for
-your repository.
+your repository:
-1. In GitHub open https://github.com/settings/tokens/new create a **Personal
+1. In GitHub open <https://github.com/settings/tokens/new> create a **Personal
Access Token.** GitLab will use this token to access your repository and
push commit statuses.
-
+
Enter a **Token description** and update the scope to allow:
`repo` so that GitLab can access your project and update commit statuses
@@ -109,3 +122,15 @@ your repository.
![Create web hook](img/github_push_webhook.png)
1. In GitHub add a `.gitlab-ci.yml` to configure GitLab CI/CD.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/ci_cd_for_external_repos/index.md b/doc/ci/ci_cd_for_external_repos/index.md
index 450ffe512dc..d46e451c609 100644
--- a/doc/ci/ci_cd_for_external_repos/index.md
+++ b/doc/ci/ci_cd_for_external_repos/index.md
@@ -1,27 +1,35 @@
+---
+type: index, howto
+---
+
# GitLab CI/CD for external repositories **[PREMIUM]**
+>[Introduced][ee-4642] in [GitLab Premium][eep] 10.6.
+
NOTE: **Note:**
This feature [is available for free](https://about.gitlab.com/2019/03/21/six-more-months-ci-cd-github/) to
GitLab.com users until September 22nd, 2019.
->[Introduced][ee-4642] in [GitLab Premium][eep] 10.6.
+GitLab CI/CD can be used with:
+
+- [GitHub](github_integration.md).
+- [Bitbucket Cloud](bitbucket_integration.md).
+- Any other Git server.
-GitLab CI/CD can be used with GitHub or any other Git server.
Instead of moving your entire project to GitLab, you can connect your
external repository to get the benefits of GitLab CI/CD.
-- [GitHub](github_integration.md)
-- [Bitbucket Cloud](bitbucket_integration.md)
-
Connecting an external repository will set up [repository mirroring][mirroring]
and create a lightweight project where issues, merge requests, wiki, and
snippets disabled. These features
[can be re-enabled later][settings].
-1. From your GitLab dashboard click **New project**
-1. Switch to the **CI/CD for external repo** tab
-1. Choose **GitHub** or **Repo by URL**
-1. The next steps are similar to the [import flow](../../user/project/import/index.md)
+To connect to an external repository:
+
+1. From your GitLab dashboard, click **New project**.
+1. Switch to the **CI/CD for external repo** tab.
+1. Choose **GitHub** or **Repo by URL**.
+1. The next steps are similar to the [import flow](../../user/project/import/index.md).
![CI/CD for external repository project creation](img/ci_cd_for_external_repo.png)
diff --git a/doc/ci/docker/README.md b/doc/ci/docker/README.md
index 446f5b54f0c..f76471b50f2 100644
--- a/doc/ci/docker/README.md
+++ b/doc/ci/docker/README.md
@@ -1,9 +1,13 @@
---
comments: false
+type: index
---
# Docker integration
+GitLab CI/CD can be combined with [Docker](https://www.docker.com) to enable
+integration between the two.
+
The following documentation is available for using GitLab CI/CD with Docker:
- [Using Docker images](using_docker_images.md).
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 5222cc45bc4..b4c4bea6447 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -1,16 +1,18 @@
+---
+type: concepts, howto
+---
+
# Building Docker images with GitLab CI/CD
GitLab CI/CD allows you to use Docker Engine to build and test docker-based projects.
-TIP: **Tip:**
-This also allows to you to use `docker-compose` and other docker-enabled tools.
One of the new trends in Continuous Integration/Deployment is to:
-1. Create an application image
-1. Run tests against the created image
-1. Push image to a remote registry
-1. Deploy to a server from the pushed image
+1. Create an application image.
+1. Run tests against the created image.
+1. Push image to a remote registry.
+1. Deploy to a server from the pushed image.
It's also useful when your application already has the `Dockerfile` that can be
used to create and test an image:
@@ -75,7 +77,7 @@ GitLab Runner then executes job scripts as the `gitlab-runner` user.
- docker run my-docker-image /script/to/run/tests
```
-1. You can now use `docker` command and install `docker-compose` if needed.
+1. You can now use `docker` command (and **install** `docker-compose` if needed).
NOTE: **Note:**
By adding `gitlab-runner` to the `docker` group you are effectively granting `gitlab-runner` full root permissions.
@@ -85,8 +87,10 @@ For more information please read [On Docker security: `docker` group considered
The second approach is to use the special docker-in-docker (dind)
[Docker image](https://hub.docker.com/_/docker/) with all tools installed
-(`docker` and `docker-compose`) and run the job script in context of that
-image in privileged mode.
+(`docker`) and run the job script in context of that
+image in privileged mode.
+
+NOTE: **Note:** `docker-compose` is not part of docker-in-docker (dind). In case you'd like to use `docker-compose` in your CI builds, please follow the [installation instructions for docker-compose](https://docs.docker.com/compose/install/) provided by docker.
In order to do that, follow the steps:
@@ -113,7 +117,7 @@ In order to do that, follow the steps:
The above command will create a `config.toml` entry similar to this:
- ```
+ ```toml
[[runners]]
url = "https://gitlab.com/"
token = TOKEN
@@ -142,9 +146,12 @@ In order to do that, follow the steps:
# The 'docker' hostname is the alias of the service container as described at
# https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services
#
- # Note that if you're using Kubernetes executor, the variable should be set to
- # tcp://localhost:2375 because of how Kubernetes executor connects services
+ # Note that if you're using the Kubernetes executor, the variable should be set to
+ # tcp://localhost:2375/ because of how the Kubernetes executor connects services
# to the job container
+ # DOCKER_HOST: tcp://localhost:2375/
+ #
+ # For non-Kubernetes executors, we use tcp://docker:2375/
DOCKER_HOST: tcp://docker:2375/
# When using dind, it's wise to use the overlayfs driver for
# improved performance.
@@ -224,7 +231,7 @@ In order to do that, follow the steps:
The above command will create a `config.toml` entry similar to this:
- ```
+ ```toml
[[runners]]
url = "https://gitlab.com/"
token = REGISTRATION_TOKEN
@@ -267,9 +274,9 @@ aware of the following implications:
create containers with specific names, they may conflict with each other.
- Sharing files and directories from the source repo into containers may not
work as expected since volume mounting is done in the context of the host
- machine, not the build container, e.g.:
+ machine, not the build container. For example:
- ```
+ ```sh
docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
```
@@ -334,7 +341,7 @@ NOTE: **Note:**
The shared Runners on GitLab.com use the `overlay2` driver by default.
By default, when using `docker:dind`, Docker uses the `vfs` storage driver which
-copies the filesystem on every run. This is a very disk-intensive operation
+copies the filesystem on every run. This is a disk-intensive operation
which can be avoided if a different driver is used, for example `overlay2`.
### Requirements
@@ -342,13 +349,13 @@ which can be avoided if a different driver is used, for example `overlay2`.
1. Make sure a recent kernel is used, preferably `>= 4.2`.
1. Check whether the `overlay` module is loaded:
- ```
+ ```sh
sudo lsmod | grep overlay
```
If you see no result, then it isn't loaded. To load it use:
- ```
+ ```sh
sudo modprobe overlay
```
@@ -356,7 +363,7 @@ which can be avoided if a different driver is used, for example `overlay2`.
On Ubuntu systems, this is done by editing `/etc/modules`. Just add the
following line into it:
- ```
+ ```text
overlay
```
@@ -364,7 +371,7 @@ which can be avoided if a different driver is used, for example `overlay2`.
You can enable the driver for each project individually by editing the project's `.gitlab-ci.yml`:
-```
+```yaml
variables:
DOCKER_DRIVER: overlay2
```
@@ -568,3 +575,15 @@ deploy:
[docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
[2fa]: ../../user/profile/account/two_factor_authentication.md
[pat]: ../../user/profile/personal_access_tokens.md
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 13c26bc5f47..29578efacbb 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Using Docker images
GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use
@@ -45,10 +49,10 @@ The `image` keyword is the name of the Docker image the Docker executor
will run to perform the CI tasks.
By default, the executor will only pull images from [Docker Hub][hub],
-but this can be configured in the `gitlab-runner/config.toml` by setting
+however this can be configured in the `gitlab-runner/config.toml` by setting
the [Docker pull policy][] to allow using local images.
-For more information about images and Docker Hub please read
+For more information about images and Docker Hub, please read
the [Docker Fundamentals][] documentation.
## What is a service
@@ -95,8 +99,8 @@ required for the CI/CD job to proceed and is accessed by network.
To make sure this works, the Runner:
-1. checks which ports are exposed from the container by default
-1. starts a special container that waits for these ports to be accessible
+1. Checks which ports are exposed from the container by default.
+1. Starts a special container that waits for these ports to be accessible.
When the second stage of the check fails, either because there is no opened port in the
service, or the service was not started properly before the timeout and the port is not
@@ -106,7 +110,7 @@ In most cases it will affect the job, but there may be situations when the job
will still succeed even if that warning was printed. For example:
- The service was started a little after the warning was raised, and the job is
- not using the linked service from the very beginning. In that case, when the
+ not using the linked service from the beginning. In that case, when the
job needed to access the service, it may have been already there waiting for
connections.
- The service container is not providing any networking service, but it's doing
@@ -143,9 +147,9 @@ job:
If you need to have `php`, `node` and `go` available for your script, you should
either:
-- choose an existing Docker image that contains all required tools, or
-- create your own Docker image, which will have all the required tools included
- and use that in your job
+- Choose an existing Docker image that contains all required tools.
+- Create your own Docker image, which will have all the required tools included
+ and use that in your job.
### Accessing the services
@@ -167,18 +171,18 @@ access to it from your build container under two hostnames to choose from:
- `tutum-wordpress`
- `tutum__wordpress`
->**Note:**
+NOTE: **Note:**
Hostnames with underscores are not RFC valid and may cause problems in 3rd party
applications.
The default aliases for the service's hostname are created from its image name
following these rules:
-- Everything after the colon (`:`) is stripped
+- Everything after the colon (`:`) is stripped.
- Slash (`/`) is replaced with double underscores (`__`) and the primary alias
- is created
+ is created.
- Slash (`/`) is replaced with a single dash (`-`) and the secondary alias is
- created (requires GitLab Runner v1.1.0 or higher)
+ created (requires GitLab Runner v1.1.0 or higher).
To override the default behavior, you can
[specify a service alias](#available-settings-for-services).
@@ -333,7 +337,7 @@ services:
```
The Runner will still start two containers using the `mysql:latest` image,
-but now each of them will also be accessible with the alias configured
+however now each of them will also be accessible with the alias configured
in `.gitlab-ci.yml` file.
### Setting a command for the service
@@ -408,8 +412,6 @@ you should check which one your Runner is using. Specifically:
The syntax of `image:entrypoint` is similar to [Dockerfile's `ENTRYPOINT`][entrypoint].
-----
-
Let's assume you have a `super/sql:experimental` image with some SQL database
inside it and you would like to use it as a base image for your job because you
want to execute some tests with this database binary. Let's also assume that
@@ -443,7 +445,7 @@ image:
Look for the `[runners.docker]` section:
-```
+```toml
[runners.docker]
image = "ruby:2.1"
services = ["mysql:latest", "postgres:latest"]
@@ -464,16 +466,39 @@ that runner.
> - 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:
+
+- [Statically defined credentials](#using-statically-defined-credentials). That is, a username and password for a specific registry.
+- [Credentials Store](#using-credentials-store). For more information, see [the relevant Docker documentation](https://docs.docker.com/engine/reference/commandline/login/#credentials-store).
+- [Credential Helpers](#using-credential-helpers). For more information, see [the relevant Docker documentation](https://docs.docker.com/engine/reference/commandline/login/#credential-helpers).
+
+To define which should be used, the GitLab Runner process reads the configuration in the following order:
+
+- `DOCKER_AUTH_CONFIG` variable provided as either:
+ - A [variable](../variables/README.md#gitlab-cicd-environment-variables) in `.gitlab-ci.yml`.
+ - A project's variables stored on the projects **Settings > CI/CD** page.
+- `DOCKER_AUTH_CONFIG` variable provided as environment variable in `config.toml` of the Runner.
+- `config.json` file placed in `$HOME/docker` directory of the user running GitLab Runner process.
+ If the `--user` flag is provided to run the GitLab Runner child processes as unprivileged user,
+ the home directory of the main GitLab Runner process user will be used.
+
+NOTE: **Note:**
+GitLab Runner reads this configuration **only** from `config.toml` and ignores it if
+it's provided as an environment variable. This is because GitLab Runnner uses **only**
+`config.toml` configuration and doesn't interpolate **ANY** environment variables at
+runtime.
+
+### Using statically-defined credentials
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.
Let's also assume that these are the login credentials:
-| Key | Value |
-|----------|---------------------------|
-| registry | registry.example.com:5000 |
-| username | my_username |
-| password | my_password |
+| Key | Value |
+|:---------|:----------------------------|
+| registry | `registry.example.com:5000` |
+| username | `my_username` |
+| password | `my_password` |
To configure access for `registry.example.com:5000`, follow these steps:
@@ -534,12 +559,85 @@ To configure access for `registry.example.com:5000`, follow these steps:
You can add configuration for as many registries as you want, adding more
registries to the `"auths"` hash as described above.
-NOTE: **Note:** The full `hostname:port` combination is required everywhere
+NOTE: **Note:**
+The full `hostname:port` combination is required everywhere
for the Runner to match the `DOCKER_AUTH_CONFIG`. For example, if
`registry.example.com:5000/namespace/image:tag` is specified in `.gitlab-ci.yml`,
then the `DOCKER_AUTH_CONFIG` must also specify `registry.example.com:5000`.
Specifying only `registry.example.com` will not work.
+
+### Using Credentials Store
+
+> Support for using Credentials Store was added in GitLab Runner 9.5.
+
+To configure credentials store, follow these steps:
+
+1. To use a credentials store, you need an external helper program to interact with a specific keychain or external store.
+Make sure helper program is available in GitLab Runner `$PATH`.
+
+1. Make GitLab Runner use it. There are two ways to accomplish this. Either:
+ - Create a
+ [variable](../variables/README.md#gitlab-cicd-environment-variables)
+ `DOCKER_AUTH_CONFIG` with the content of the
+ Docker configuration file as the value:
+
+ ```json
+ {
+ "credsStore": "osxkeychain"
+ }
+ ```
+
+ - Or, if you are running self-hosted Runners, add the above JSON to
+ `${GITLAB_RUNNER_HOME}/.docker/config.json`. GitLab Runner will read this config file
+ and will use the needed helper for this specific repository.
+
+NOTE: **Note:** `credsStore` is used to access ALL the registries.
+If you will want to use both images from private registry and public images from DockerHub,
+pulling from DockerHub will fail, because Docker daemon will try to use the same credentials for **ALL** the registries.
+
+### Using Credential Helpers
+
+> Support for using Credential Helpers was added in GitLab Runner 12.0
+
+As an example, let's assume that you want to use the `aws_account_id.dkr.ecr.region.amazonaws.com/private/image:latest`
+image which is private and requires you to log in into a private container registry.
+
+To configure access for `aws_account_id.dkr.ecr.region.amazonaws.com`, follow these steps:
+
+1. Make sure `docker-credential-ecr-login` is available in GitLab Runner's `$PATH`.
+
+1. Make GitLab Runner use it. There are two ways to accomplish this. Either:
+ - Create a [variable](../variables/README.md#gitlab-cicd-environment-variables)
+ `DOCKER_AUTH_CONFIG` with the content of the
+ Docker configuration file as the value:
+
+ ```json
+ {
+ "credHelpers": {
+ "aws_account_id.dkr.ecr.region.amazonaws.com": "ecr-login"
+ }
+ }
+ ```
+
+ - Or, if you are running self-hosted Runners,
+ add the above JSON to `${GITLAB_RUNNER_HOME}/.docker/config.json`.
+ GitLab Runner will read this config file and will use the needed helper for this
+ specific repository.
+
+1. You can now use any private image from `aws_account_id.dkr.ecr.region.amazonaws.com` defined in
+ `image` and/or `services` in your `.gitlab-ci.yml` file:
+
+ ```yaml
+ image: aws_account_id.dkr.ecr.region.amazonaws.com/private/image:latest
+ ```
+
+ In the example above, GitLab Runner will look at `aws_account_id.dkr.ecr.region.amazonaws.com` for the
+ image `private/image:latest`.
+
+You can add configuration for as many registries as you want, adding more
+registries to the `"credHelpers"` hash as described above.
+
## Configuring services
Many services accept environment variables which allow you to easily change
@@ -551,8 +649,9 @@ service containers.
For all possible configuration variables check the documentation of each image
provided in their corresponding Docker hub page.
-*Note: All variables will be passed to all services containers. It's not
-designed to distinguish which variable should go where.*
+NOTE: **Note:**
+All variables will be passed to all services containers. It's not
+designed to distinguish which variable should go where.
### PostgreSQL service example
@@ -582,8 +681,9 @@ time.
## How to debug a job locally
-*Note: The following commands are run without root privileges. You should be
-able to run Docker with your regular user account.*
+NOTE: **Note:**
+The following commands are run without root privileges. You should be
+able to run Docker with your regular user account.
First start with creating a file named `build_script`:
@@ -602,7 +702,7 @@ is specific to your project.
Then create some service containers:
-```
+```sh
docker run -d --name service-mysql mysql:latest
docker run -d --name service-postgres postgres:latest
```
@@ -614,7 +714,7 @@ respectively. They will both run in the background (`-d`).
Finally, create a build container by executing the `build_script` file we
created earlier:
-```
+```sh
docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script
```
@@ -626,7 +726,7 @@ piped using STDIN to the bash interpreter which in turn executes the
When you finish testing and no longer need the containers, you can remove them
with:
-```
+```sh
docker rm -f -v build service-mysql service-postgres
```
diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md
index f354cdb398e..50f1ac3d54a 100644
--- a/doc/ci/docker/using_kaniko.md
+++ b/doc/ci/docker/using_kaniko.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Building images with kaniko and GitLab CI/CD
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45512) in GitLab 11.2.
@@ -9,17 +13,18 @@ container images from a Dockerfile, inside a container or Kubernetes cluster.
kaniko solves two problems with using the
[docker-in-docker build](using_docker_build.md#use-docker-in-docker-executor) method:
-1. Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
- in order to function, which is a significant security concern.
-1. Docker-in-docker generally incurs a performance penalty and can be quite slow.
+- Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
+ in order to function, which is a significant security concern.
+- Docker-in-docker generally incurs a performance penalty and can be quite slow.
## Requirements
In order to utilize kaniko with GitLab, a [GitLab Runner](https://docs.gitlab.com/runner/)
-using either the [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html),
-[Docker](https://docs.gitlab.com/runner/executors/docker.html), or
-[Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html)
-executors is required.
+using one of the following executors is required:
+
+- [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html).
+- [Docker](https://docs.gitlab.com/runner/executors/docker.html).
+- [Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html).
## Building a Docker image with kaniko
@@ -34,14 +39,17 @@ few important details:
- A Docker `config.json` file needs to be created with the authentication
information for the desired container registry.
----
+In the following example, kaniko is used to:
+
+1. Build a Docker image.
+1. Then push it to [GitLab Container Registry](../../user/project/container_registry.md).
-In the following example, kaniko is used to build a Docker image and then push
-it to [GitLab Container Registry](../../user/project/container_registry.md).
The job will run only when a tag is pushed. A `config.json` file is created under
`/kaniko/.docker` with the needed GitLab Container Registry credentials taken from the
[environment variables](../variables/README.md#predefined-environment-variables)
-GitLab CI/CD provides. In the last step, kaniko uses the `Dockerfile` under the
+GitLab CI/CD provides.
+
+In the last step, kaniko uses the `Dockerfile` under the
root directory of the project, builds the Docker image and pushes it to the
project's Container Registry while tagging it with the Git tag:
@@ -80,3 +88,15 @@ store:
...
-----END CERTIFICATE-----" >> /kaniko/ssl/certs/ca-certificates.crt
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md
index 7aa7de97c43..56200142055 100644
--- a/doc/ci/enable_or_disable_ci.md
+++ b/doc/ci/enable_or_disable_ci.md
@@ -1,25 +1,32 @@
+---
+type: howto
+---
+
# How to enable or disable GitLab CI/CD
-To effectively use GitLab CI/CD, you need a valid [`.gitlab-ci.yml`](yaml/README.md)
-file present at the root directory of your project and a
-[runner](runners/README.md) properly set up. You can read our
-[quick start guide](quick_start/README.md) to get you started.
+To effectively use GitLab CI/CD, you need:
+
+- A valid [`.gitlab-ci.yml`](yaml/README.md) file present at the root directory
+ of your project.
+- A [runner](runners/README.md) properly set up.
+
+You can read our [quick start guide](quick_start/README.md) to get you started.
If you are using an external CI/CD server like Jenkins or Drone CI, it is advised
to disable GitLab CI/CD in order to not have any conflicts with the commits status
API.
----
-
GitLab CI/CD is exposed via the `/pipelines` and `/jobs` pages of a project.
Disabling GitLab CI/CD in a project does not delete any previous jobs.
In fact, the `/pipelines` and `/jobs` pages can still be accessed, although
it's hidden from the left sidebar menu.
-GitLab CI/CD is enabled by default on new installations and can be disabled either
-individually under each project's settings, or site-wide by modifying the
-settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations
-respectively.
+GitLab CI/CD is enabled by default on new installations and can be disabled
+either:
+
+- Individually under each project's settings.
+- Site-wide by modifying the settings in `gitlab.yml` and `gitlab.rb` for source
+ and Omnibus installations respectively.
## Per-project user setting
@@ -36,10 +43,10 @@ and `gitlab.rb` for source and Omnibus installations respectively.
Two things to note:
-1. Disabling GitLab CI/CD, will affect only newly-created projects. Projects that
- had it enabled prior to this modification, will work as before.
-1. Even if you disable GitLab CI/CD, users will still be able to enable it in the
- project's settings.
+- Disabling GitLab CI/CD, will affect only newly-created projects. Projects that
+ had it enabled prior to this modification, will work as before.
+- Even if you disable GitLab CI/CD, users will still be able to enable it in the
+ project's settings.
For installations from source, open `gitlab.yml` with your editor and set
`builds` to `false`:
@@ -54,12 +61,32 @@ default_projects_features:
builds: false
```
-Save the file and restart GitLab: `sudo service gitlab restart`.
+Save the file and restart GitLab:
+
+```sh
+sudo service gitlab restart
+```
For Omnibus installations, edit `/etc/gitlab/gitlab.rb` and add the line:
-```
+```ruby
gitlab_rails['gitlab_default_projects_features_builds'] = false
```
-Save the file and reconfigure GitLab: `sudo gitlab-ctl reconfigure`.
+Save the file and reconfigure GitLab:
+
+```sh
+sudo gitlab-ctl reconfigure
+```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 5a14ac17aec..bd419965a9c 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Environments and deployments
> Introduced in GitLab 8.9.
@@ -88,7 +92,7 @@ deploy_staging:
- master
```
-We have defined 3 [stages](yaml/README.md#stages):
+We have defined three [stages](yaml/README.md#stages):
- `test`
- `build`
@@ -131,7 +135,7 @@ In summary, with the above `.gitlab-ci.yml` we have achieved the following:
> the name given in `.gitlab-ci.yml` (with any variables expanded), while the
> second is a "cleaned-up" version of the name, suitable for use in URLs, DNS,
> etc.
-
+>
> Starting with GitLab 9.3, the environment URL is exposed to the Runner via
> `$CI_ENVIRONMENT_URL`. The URL is expanded from `.gitlab-ci.yml`, or if
> the URL was not defined there, the external URL from the environment is used.
@@ -613,6 +617,10 @@ versions of the app, all without leaving GitLab.
![Monitoring dashboard](img/environments_monitoring.png)
+#### Linking to external dashboard
+
+Add a [button to the Monitoring dashboard](../user/project/operations/linking_to_an_external_dashboard.md) linking directly to your existing external dashboards.
+
### Web terminals
> Web terminals were added in GitLab 8.15 and are only available to project Maintainers and Owners.
@@ -667,9 +675,24 @@ fetch = +refs/environments/*:refs/remotes/origin/environments/*
### Scoping environments with specs **[PREMIUM]**
-Some GitLab [Enterprise Edition](https://about.gitlab.com/pricing/) features can
-behave differently for each environment. For example, you can
-[create a secret variable to be injected only into a production environment](variables/README.md#limiting-environment-scopes-of-environment-variables-premium).
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
+
+You can limit the environment scope of a variable by
+defining which environments it can be available for.
+
+Wildcards can be used, and the default environment scope is `*`, which means
+any jobs will have this variable, not matter if an environment is defined or
+not.
+
+For example, if the environment scope is `production`, then only the jobs
+having the environment `production` defined would have this specific variable.
+Wildcards (`*`) can be used along with the environment name, therefore if the
+environment scope is `review/*` then any jobs with environment names starting
+with `review/` would have that particular variable.
+
+Some GitLab features can behave differently for each environment.
+For example, you can
+[create a secret variable to be injected only into a production environment](variables/README.md#limiting-environment-scopes-of-environment-variables-premium). **[PREMIUM]**
In most cases, these features use the _environment specs_ mechanism, which offers
an efficient way to implement scoping within each environment group.
@@ -693,7 +716,7 @@ Each environment can be matched with the following environment spec:
As you can see, you can use specific matching for selecting a particular environment,
and also use wildcard matching (`*`) for selecting a particular environment group,
-such as [Review apps](review_apps/index.md) (`review/*`).
+such as [Review Apps](review_apps/index.md) (`review/*`).
NOTE: **Note:**
The most _specific_ spec takes precedence over the other wildcard matching.
@@ -712,3 +735,15 @@ Below are some links you may find interesting:
- [A blog post on Deployments & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
- [Review Apps - Use dynamic environments to deploy your code for every branch](review_apps/index.md)
- [Deploy Boards for your applications running on Kubernetes](https://docs.gitlab.com/ee/user/project/deploy_boards.html) **[PREMIUM]**
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md
index ab5c0e2dbad..b72ebe838b8 100644
--- a/doc/ci/environments/protected_environments.md
+++ b/doc/ci/environments/protected_environments.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Protected Environments **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6303) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.3.
@@ -47,3 +51,15 @@ Maintainers can:
- Update existing protected environments at any time by changing the access in the
**Allowed to Deploy** dropdown menu.
- Unprotect a protected environment by clicking the **Unprotect** button for that environment.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 340a41c196b..d2ea30839a4 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -1,47 +1,42 @@
---
comments: false
+type: index
---
# GitLab CI/CD Examples
-Examples are a useful way of understanding how to implement GitLab CI/CD for your specific use case.
+This page contains links to a variety of examples that can help you understand how to
+implement [GitLab CI/CD](../README.md) for your specific use case.
Examples are available in several forms. As a collection of:
- `.gitlab-ci.yml` [template files](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates) maintained in GitLab. When you create a new file via the UI,
GitLab will give you the option to choose one of these templates. This will allow you to quickly bootstrap your project for CI/CD.
If your favorite programming language or framework are missing, we would love your help by sending a merge request with a new `.gitlab-ci.yml` to this project.
-- Repositories with [example projects](https://gitlab.com/gitlab-examples) for various languages. You can fork and adjust them to your own needs.
+- Repositories with [example projects](https://gitlab.com/gitlab-examples) for various languages. You can fork and adjust them to your own needs. Projects include demonstrations of [multi-project pipelines](https://gitlab.com/gitlab-examples/multi-project-pipelines) and using [Review Apps with a static site served by nginx](https://gitlab.com/gitlab-examples/review-apps-nginx/).
- Examples and [other resources](#other-resources) listed below.
## CI/CD examples
-The following table lists examples for different use cases:
-
-| Use case | Resource |
-|:-----------------------------------------------|:------------------------------------------------------------------------------------------------------------------------|
-| Browser performance testing | [Browser Performance Testing with the Sitespeed.io container](browser_performance.md). |
-| Clojure | [Test a Clojure application with GitLab CI/CD](test-clojure-application.md). |
-| Code quality analysis | [Analyze your project's Code Quality](code_quality.md). **[STARTER]** |
-| Container scanning | [Container Scanning with GitLab CI/CD](../../user/application_security/container_scanning/index.md). **[ULTIMATE]** |
-| Dependency scanning | [Dependency Scanning with GitLab CI/CD](../../user/application_security/dependency_scanning/index.md). **[ULTIMATE]** |
-| Deployment with `dpl` | [Using `dpl` as deployment tool](deployment/README.md). |
-| Dynamic application<br>security testing (DAST) | [Dynamic Application Security Testing with GitLab CI/CD](../../user/application_security/dast/index.md). **[ULTIMATE]** |
-| Elixir | [Testing a Phoenix application with GitLab CI/CD](test_phoenix_app_with_gitlab_ci_cd/index.md). |
-| Game development | [DevOps and Game Dev with GitLab CI/CD](devops_and_game_dev_with_gitlab_ci_cd/index.md). |
-| GitLab Pages | See the [GitLab Pages](../../user/project/pages/index.md) documentation for a complete example. |
-| Java | [Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD](deploy_spring_boot_to_cloud_foundry/index.md). |
-| JUnit | [JUnit test reports](../junit_test_reports.md). |
-| License management | [Dependencies license management with GitLab CI/CD](../../user/application_security/license_management/index.md). **[ULTIMATE]** |
-| Maven | [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md). |
-| PHP | [Testing PHP projects](php.md). |
-| PHP | [Running Composer and NPM scripts with deployment via SCP in GitLab CI/CD](deployment/composer-npm-deploy.md). |
-| PHP | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
-| Python | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). |
-| Ruby | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). |
-| Scala | [Test and deploy a Scala application to Heroku](test-scala-application.md). |
-| Static application<br>security testing (SAST) | [Static Application Security Testing with GitLab CI/CD](../../user/application_security/sast/index.md). **[ULTIMATE]** |
-| Testing | [End-to-end testing with GitLab CI/CD and WebdriverIO](end_to_end_testing_webdriverio/index.md). |
+The following table lists examples with step-by-step tutorials that are contained in this section.
+
+| Use case | Resource |
+|:----------------------------|:---------------------------------------------------------------------------------------------------------------------------|
+| Browser performance testing | [Browser Performance Testing with the Sitespeed.io container](browser_performance.md). |
+| Clojure | [Test a Clojure application with GitLab CI/CD](test-clojure-application.md). |
+| Deployment with Dpl | [Using `dpl` as deployment tool](deployment/README.md). |
+| Elixir | [Testing a Phoenix application with GitLab CI/CD](test_phoenix_app_with_gitlab_ci_cd/index.md). |
+| End-to-end testing | [End-to-end testing with GitLab CI/CD and WebdriverIO](end_to_end_testing_webdriverio/index.md). |
+| Game development | [DevOps and Game Dev with GitLab CI/CD](devops_and_game_dev_with_gitlab_ci_cd/index.md). |
+| GitLab Pages | See the [GitLab Pages](../../user/project/pages/index.md) documentation for a complete example of deploying a static site. |
+| Java with Spring Boot | [Deploy a Spring Boot application to Cloud Foundry with GitLab CI/CD](deploy_spring_boot_to_cloud_foundry/index.md). |
+| Java with Maven | [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md). |
+| PHP with PHPunit, atoum | [Testing PHP projects](php.md). |
+| PHP with NPM, SCP | [Running Composer and NPM scripts with deployment via SCP in GitLab CI/CD](deployment/composer-npm-deploy.md). |
+| PHP with Laravel, Ennvoy | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
+| Python on Heroku | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). |
+| Ruby on Heroku | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). |
+| Scala on Heroku | [Test and deploy a Scala application to Heroku](test-scala-application.md). |
### Contributing examples
@@ -50,25 +45,30 @@ language users and GitLab by sending a merge request with a guide for that langu
You may want to apply for the [GitLab Community Writers Program](https://about.gitlab.com/community-writers/)
to get paid for writing complete articles for GitLab.
-### Adding templates to your GitLab installation **[PREMIUM ONLY]**
+## Adding templates to your GitLab installation **[PREMIUM ONLY]**
If you want to have customized examples and templates for your own self-managed GitLab instance available to your team, your GitLab administrator can [designate an instance template repository](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html) that contains examples and templates specific to your enterprise.
## Other resources
-This section provides further resources to help you get familiar with different aspects of GitLab CI/CD.
-
-NOTE: **Note:**
-These resources may no longer reflect the current state of GitLab CI/CD.
+This section provides further resources to help you get familiar with various uses of GitLab CI/CD.
+Note that older articles and videos may not reflect the state of the latest GitLab release.
### CI/CD in the cloud
For examples of setting up GitLab CI/CD for cloud-based environments, see:
- [How to set up multi-account AWS SAM deployments with GitLab CI](https://about.gitlab.com/2019/02/04/multi-account-aws-sam-deployments-with-gitlab-ci/)
+- [Automating Kubernetes Deployments with GitLab CI/CD](https://www.youtube.com/watch?v=wEDRfAz6_Uw)
- [How to autoscale continuous deployment with GitLab Runner on DigitalOcean](https://about.gitlab.com/2018/06/19/autoscale-continuous-deployment-gitlab-runner-digital-ocean/)
- [How to create a CI/CD pipeline with Auto Deploy to Kubernetes using GitLab and Helm](https://about.gitlab.com/2017/09/21/how-to-create-ci-cd-pipeline-with-autodeploy-to-kubernetes-using-gitlab-and-helm/)
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+See also the following video overviews:
+
+- [Containers, Schedulers, and GitLab CI](https://www.youtube.com/watch?v=d-9awBxEbvQ).
+- [Deploying to IBM Cloud with GitLab CI/CD](https://www.youtube.com/watch?v=6ZF4vgKMd-g).
+
### Customer stories
For some customer experiences with GitLab CI/CD, see:
@@ -83,7 +83,6 @@ For some examples to help get you started, see:
- [GitLab CI/CD's 2018 highlights](https://about.gitlab.com/2019/01/21/gitlab-ci-cd-features-improvements/)
- [A beginner's guide to continuous integration](https://about.gitlab.com/2018/01/22/a-beginners-guide-to-continuous-integration/)
-- [Making CI easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/)
### Implementing GitLab CI/CD
diff --git a/doc/ci/examples/artifactory_and_gitlab/index.md b/doc/ci/examples/artifactory_and_gitlab/index.md
index 589912e7a2a..e85a13f2187 100644
--- a/doc/ci/examples/artifactory_and_gitlab/index.md
+++ b/doc/ci/examples/artifactory_and_gitlab/index.md
@@ -4,6 +4,7 @@ author: Fabio Busatto
author_gitlab: bikebilly
level: intermediate
article_type: tutorial
+type: tutorial
date: 2017-08-15
---
@@ -16,8 +17,8 @@ to build a [Maven](https://maven.apache.org/) project, deploy it to [Artifactory
You'll create two different projects:
-- `simple-maven-dep`: the app built and deployed to Artifactory (available at <https://gitlab.com/gitlab-examples/maven/simple-maven-dep>)
-- `simple-maven-app`: the app using the previous one as a dependency (available at <https://gitlab.com/gitlab-examples/maven/simple-maven-app>)
+- `simple-maven-dep`: the app built and deployed to Artifactory (see the [simple-maven-dep](https://gitlab.com/gitlab-examples/maven/simple-maven-dep) example project)
+- `simple-maven-app`: the app using the previous one as a dependency (see the [simple-maven-app](https://gitlab.com/gitlab-examples/maven/simple-maven-app) example project)
We assume that you already have a GitLab account on [GitLab.com](https://gitlab.com/), and that you know the basic usage of Git and [GitLab CI/CD](https://about.gitlab.com/product/continuous-integration/).
We also assume that an Artifactory instance is available and reachable from the internet, and that you have valid credentials to deploy on it.
diff --git a/doc/ci/examples/browser_performance.md b/doc/ci/examples/browser_performance.md
index 442d0788d37..4c42811edf4 100644
--- a/doc/ci/examples/browser_performance.md
+++ b/doc/ci/examples/browser_performance.md
@@ -1,13 +1,17 @@
-# Browser Performance Testing with the Sitespeed.io container
+---
+type: howto
+---
-CAUTION: **Caution:**
+# Browser Performance Testing with the sitespeed.io container
+
+NOTE: **Note:**
The job definition shown below is supported on GitLab 11.5 and later versions.
It also requires the GitLab Runner 11.5 or later.
For earlier versions, use the [previous job definitions](#previous-job-definitions).
This example shows how to run the
-[Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) on
-your code by using GitLab CI/CD and [Sitespeed.io](https://www.sitespeed.io)
+[sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) on
+your code by using GitLab CI/CD and [sitespeed.io](https://www.sitespeed.io)
using Docker-in-Docker.
First, you need GitLab Runner with
@@ -38,20 +42,20 @@ performance:
```
The above example will create a `performance` job in your CI/CD pipeline and will run
-Sitespeed.io against the webpage you defined in `URL` to gather key metrics.
+sitespeed.io against the webpage you defined in `URL` to gather key metrics.
The [GitLab plugin](https://gitlab.com/gitlab-org/gl-performance) for
-Sitespeed.io is downloaded in order to save the report as a
+sitespeed.io is downloaded in order to save the report as a
[Performance report artifact](../yaml/README.md#artifactsreportsperformance-premium)
that you can later download and analyze.
Due to implementation limitations we always take the latest Performance artifact available.
-The full HTML Sitespeed.io report will also be saved as an artifact, and if you have
+The full HTML sitespeed.io report will also be saved as an artifact, and if you have
[GitLab Pages](../../user/project/pages/index.md) enabled, it can be viewed
directly in your browser.
-For further customization options of Sitespeed.io, including the ability to
-provide a list of URLs to test, please consult
-[their documentation](https://www.sitespeed.io/documentation/sitespeed.io/configuration/).
+For further customization options for sitespeed.io, including the ability to
+provide a list of URLs to test, please see the
+[Sitespeed.io Configuration](https://www.sitespeed.io/documentation/sitespeed.io/configuration/) documentation.
TIP: **Tip:**
For [GitLab Premium](https://about.gitlab.com/pricing/) users, key metrics are automatically
@@ -73,7 +77,7 @@ set this up:
1. In the `performance` job, read the previous artifact into an environment
variable, like `$CI_ENVIRONMENT_URL`, and use it to parameterize the test
URLs.
-1. You can now run the Sitespeed.io container against the desired hostname and
+1. You can now run the sitespeed.io container against the desired hostname and
paths.
Your `.gitlab-ci.yml` file would look like:
diff --git a/doc/ci/examples/code_quality.md b/doc/ci/examples/code_quality.md
index 186d4527bb6..4a9bfa51528 100644
--- a/doc/ci/examples/code_quality.md
+++ b/doc/ci/examples/code_quality.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Analyze your project's Code Quality
CAUTION: **Caution:**
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index c622dd86828..538843ab8dc 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -3,6 +3,7 @@ author: Dylan Griffith
author_gitlab: DylanGriffith
level: intermediate
article_type: tutorial
+type: tutorial
date: 2018-06-07
last_updated: 2019-04-08
description: "Continuous Deployment of a Spring Boot application to Cloud Foundry with GitLab CI/CD"
@@ -26,13 +27,13 @@ using GitLab CI/CD, read through the blog post [Continuous Delivery of a Spring
## Requirements
-_We assume you are familiar with Java, GitLab, Cloud Foundry, and GitLab CI/CD._
+This tutorial assumes you are familiar with Java, GitLab, Cloud Foundry, and GitLab CI/CD.
-To follow along with this tutorial you will need the following:
+To follow along, you will need:
- An account on [Pivotal Web Services (PWS)](https://run.pivotal.io/) or any
- other Cloud Foundry instance
-- An account on GitLab
+ other Cloud Foundry (CF) instance.
+- An account on GitLab.
NOTE: **Note:**
You will need to replace the `api.run.pivotal.io` URL in the all below
diff --git a/doc/ci/examples/deployment/README.md b/doc/ci/examples/deployment/README.md
index 010ba6b66a2..26b10c7eeaf 100644
--- a/doc/ci/examples/deployment/README.md
+++ b/doc/ci/examples/deployment/README.md
@@ -1,12 +1,14 @@
+---
+type: tutorial
+---
+
# Using Dpl as deployment tool
-[Dpl](https://github.com/travis-ci/dpl) (dee-pee-ell) is a deploy tool made for
+[Dpl](https://github.com/travis-ci/dpl) (prouncounced like the letters D-P-L) is a deploy tool made for
continuous deployment that's developed and used by Travis CI, but can also be
used with GitLab CI.
->**Note:**
-We recommend to use Dpl if you're deploying to any of these services:
-<https://github.com/travis-ci/dpl#supported-providers>.
+Dpl can be used to deploy to any of the [supported providers](https://github.com/travis-ci/dpl#supported-providers).
## Requirements
@@ -50,8 +52,8 @@ To use different provider take a look at long list of [Supported Providers](http
## Using Dpl with Docker
-When you use GitLab Runner you most likely configured it to use your server's shell commands.
-This means that all commands are run in context of local user (ie. gitlab_runner or gitlab_ci_multi_runner).
+In most cases, you will have configured [GitLab Runner](https://docs.gitlab.com/runner/) to use your server's shell commands.
+This means that all commands are run in the context of local user (e.g. gitlab_runner or gitlab_ci_multi_runner).
It also means that most probably in your Docker container you don't have the Ruby runtime installed.
You will have to install it:
diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md
index 4758ccad5aa..79b3cbd0c69 100644
--- a/doc/ci/examples/deployment/composer-npm-deploy.md
+++ b/doc/ci/examples/deployment/composer-npm-deploy.md
@@ -1,8 +1,12 @@
+---
+type: tutorial
+---
+
# Running Composer and NPM scripts with deployment via SCP in GitLab CI/CD
-This guide covers the building dependencies of a PHP project while compiling assets via an NPM script.
+This guide covers the building of dependencies of a PHP project while compiling assets via an NPM script using [GitLab CI/CD](../../README.md).
-While is possible to create your own image with custom PHP and Node JS versions, for brevity, we will use an existing [Docker image](https://hub.docker.com/r/tetraweb/php/) that contains both PHP and NodeJS installed.
+While it is possible to create your own image with custom PHP and Node JS versions, for brevity, we will use an existing [Docker image](https://hub.docker.com/r/tetraweb/php/) that contains both PHP and NodeJS installed.
```yaml
image: tetraweb/php
@@ -46,9 +50,9 @@ To make this work, you need to add a GitLab CI/CD Variable (accessible on _gitla
### Security tip
-Create a user that has access **only** to the folder that needs to be updated!
+Create a user that has access **only** to the folder that needs to be updated.
-After you create that variable, you need to make sure that key will be added to the docker container on run:
+After you create that variable, you need to make sure that key will be added to the Docker container on run:
```yaml
before_script:
@@ -68,7 +72,7 @@ In order, this means that:
And this is basically all you need in the `before_script` section.
-## How to deploy things
+## How to deploy
As we stated above, we need to deploy the `build` folder from the docker image to our server. To do so, we create a new job:
@@ -93,7 +97,7 @@ Here's the breakdown:
1. `ssh-add ...` we will add that private key you added on the web UI to the docker container
1. We will connect via `ssh` and create a new `_tmp` folder
1. We will connect via `scp` and upload the `build` folder (which was generated by a `npm` script) to our previously created `_tmp` folder
-1. We will connect again to `ssh` and move the `live` folder to an `_old` folder, then move `_tmp` to `live`.
+1. We will connect again via `ssh` and move the `live` folder to an `_old` folder, then move `_tmp` to `live`.
1. We connect to ssh and remove the `_old` folder
What's the deal with the artifacts? We just tell GitLab CI to keep the `build` directory (later on, you can download that as needed).
@@ -109,14 +113,14 @@ If you're using this only for stage server, you could do this in two steps:
The problem is that there will be a small period of time when you won't have the app on your server.
-So we use so many steps because we want to make sure that at any given time we have a functional app in place.
+Therefore, for a production environment we use additional steps to ensure that at any given time, a functional app is in place.
## Where to go next
-Since this was a WordPress project, I gave real life code snippets. Some ideas you can pursuit:
+Since this was a WordPress project, I gave real life code snippets. Some further ideas you can pursue:
-- Having a slightly different script for `master` branch will allow you to deploy to a production server from that branch and to a stage server from any other branches;
-- Instead of pushing it live, you can push it to WordPress official repo (with creating a SVN commit & stuff);
+- Having a slightly different script for `master` branch will allow you to deploy to a production server from that branch and to a stage server from any other branches.
+- Instead of pushing it live, you can push it to WordPress official repo (with creating a SVN commit, etc.).
- You could generate i18n text domains on the fly.
---
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
index d6ad00a77da..50e61cafeb9 100644
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -3,6 +3,7 @@ author: Ryan Hall
author_gitlab: blitzgren
level: intermediate
article_type: tutorial
+type: tutorial
date: 2018-03-07
last_updated: 2019-03-11
---
@@ -14,7 +15,7 @@ platforms without the use of plugins like Adobe Flash. Furthermore, by using Git
single game developers, as well as game dev teams, can easily host browser-based games online.
In this tutorial, we'll focus on DevOps, as well as testing and hosting games with Continuous
-Integration/Deployment methods. We assume you are familiar with GitLab, javascript,
+Integration/Deployment methods using [GitLab CI/CD](../../README.md). We assume you are familiar with GitLab, JavaScript,
and the basics of game development.
## The game
diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
index bd221b7145e..7f1beb96bbf 100644
--- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md
+++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
@@ -3,6 +3,7 @@ author: Vincent Tunru
author_gitlab: Vinnl
level: advanced
article_type: user guide
+type: tutorial
date: 2019-02-18
description: 'Confidence checking your entire app every time a new feature is added can quickly become repetitive. Learn how to automate it with GitLab CI/CD.'
---
@@ -22,7 +23,9 @@ However, looking at the freshly deployed code to check whether it still looks an
expected is repetitive manual work, which means it is a prime candidate for automation. This is
where automated [end-to-end testing](https://martinfowler.com/bliki/BroadStackTest.html) comes in:
having the computer run through a few simple scenarios that requires the proper functioning of all
-layers of your application, from the frontend to the database. In this article, we will discuss how
+layers of your application, from the frontend to the database.
+
+In this article, we will discuss how
to write such end-to-end tests, and how to set up GitLab CI/CD to automatically run these tests
against your new code, on a branch-by-branch basis. For the scope of this article, we will walk you
through the process of setting up GitLab CI/CD for end-to-end testing Javascript-based applications
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
index f56d5429fb7..d7308a3a5ec 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
@@ -4,6 +4,7 @@ author: Mehran Rasulian
author_gitlab: mehranrasulian
level: intermediate
article_type: tutorial
+type: tutorial
date: 2017-08-31
last_updated: 2019-03-06
---
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index c1048f3d2e3..c459bb7001f 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -1,8 +1,12 @@
+---
+type: tutorial
+---
+
# Testing PHP projects
This guide covers basic building instructions for PHP projects.
-There are covered two cases: testing using the Docker executor and testing
+Two testing scenarios are covered: using the Docker executor and
using the Shell executor.
## Test PHP projects using the Docker executor
@@ -245,7 +249,7 @@ before_script:
...
```
-## Access private packages / dependencies
+## Access private packages or dependencies
If your test suite needs to access a private repository, you need to configure
[the SSH keys](../ssh_keys/README.md) in order to be able to clone it.
@@ -254,7 +258,7 @@ If your test suite needs to access a private repository, you need to configure
Most of the time you will need a running database in order for your tests to
run. If you are using the Docker executor you can leverage Docker's ability to
-link to other containers. In GitLab Runner lingo, this can be achieved by
+link to other containers. With GitLab Runner, this can be achieved by
defining a `service`.
This functionality is covered in [the CI services](../services/README.md)
@@ -279,7 +283,7 @@ We have set up an [Example PHP Project][php-example-repo] for your convenience
that runs on [GitLab.com](https://gitlab.com) using our publicly available
[shared runners](../runners/README.md).
-Want to hack on it? Simply fork it, commit and push your changes. Within a few
+Want to hack on it? Simply fork it, commit, and push your changes. Within a few
moments the changes will be picked by a public runner and the job will begin.
[php-hub]: https://hub.docker.com/r/_/php/
diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
index 47d20a4e1c1..f9d185f187c 100644
--- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
@@ -1,8 +1,12 @@
+---
+type: tutorial
+---
+
# Test and deploy a Python application with GitLab CI/CD
This example will guide you how to run tests in your Python application and deploy it automatically as Heroku application.
-You can checkout the [example source](https://gitlab.com/ayufan/python-getting-started).
+You can also view or fork the complete [example source](https://gitlab.com/ayufan/python-getting-started).
## Configure project
@@ -46,9 +50,9 @@ production:
This project has three jobs:
-- `test` - used to test Django application,
-- `staging` - used to automatically deploy staging environment every push to `master` branch
-- `production` - used to automatically deploy production environment for every created tag
+- `test` - used to test Django application.
+- `staging` - used to automatically deploy staging environment every push to `master` branch.
+- `production` - used to automatically deploy production environment for every created tag.
## Store API keys
@@ -67,8 +71,9 @@ You can do this through the [Dashboard](https://dashboard.heroku.com/).
## Create Runner
First install [Docker Engine](https://docs.docker.com/installation/).
+
To build this project you also need to have [GitLab Runner](https://docs.gitlab.com/runner).
-You can use public runners available on `gitlab.com`, but you can register your own:
+You can use public runners available on `gitlab.com` or you can register your own:
```sh
gitlab-runner register \
@@ -81,6 +86,6 @@ gitlab-runner register \
--docker-postgres latest
```
-With the command above, you create a runner that uses [python:3.5](https://hub.docker.com/r/_/python/) image and uses [postgres](https://hub.docker.com/r/_/postgres/) database.
+With the command above, you create a runner that uses the [python:3.5](https://hub.docker.com/r/_/python/) image and uses a [postgres](https://hub.docker.com/r/_/postgres/) database.
-To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
+To access the PostgreSQL database, connect to `host: postgres` as user `postgres` with no password.
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index 3a0ddf001b8..79d54b52b5a 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -1,8 +1,12 @@
+---
+type: tutorial
+---
+
# Test and deploy a Ruby application with GitLab CI/CD
-This example will guide you how to run tests in your Ruby on Rails application and deploy it automatically as Heroku application.
+This example will guide you through how to run tests in your Ruby on Rails application and deploy it automatically as a Heroku application.
-You can checkout the example [source](https://gitlab.com/ayufan/ruby-getting-started) and check [CI status](https://gitlab.com/ayufan/ruby-getting-started/builds?scope=all).
+You can also view or fork the complete [example source](https://gitlab.com/ayufan/ruby-getting-started) and view the logs of its past [CI jobs](https://gitlab.com/ayufan/ruby-getting-started/-/jobs?scope=finished).
## Configure the project
@@ -53,13 +57,14 @@ Find your Heroku API key in [Manage Account](https://dashboard.heroku.com/accoun
## Create Heroku application
For each of your environments, you'll need to create a new Heroku application.
-You can do this through the [Dashboard](https://dashboard.heroku.com/).
+You can do this through the [Heroku Dashboard](https://dashboard.heroku.com/).
## Create Runner
First install [Docker Engine](https://docs.docker.com/installation/).
+
To build this project you also need to have [GitLab Runner](https://docs.gitlab.com/runner/).
-You can use public runners available on `gitlab.com`, but you can register your own:
+You can use public runners available on `gitlab.com` or register your own:
```sh
gitlab-runner register \
@@ -72,6 +77,6 @@ gitlab-runner register \
--docker-postgres latest
```
-With the command above, you create a Runner that uses [ruby:2.2](https://hub.docker.com/r/_/ruby/) image and uses [postgres](https://hub.docker.com/r/_/postgres/) database.
+With the command above, you create a Runner that uses the [ruby:2.2](https://hub.docker.com/r/_/ruby/) image and uses a [postgres](https://hub.docker.com/r/_/postgres/) database.
-To access PostgreSQL database you need to connect to `host: postgres` as user `postgres` without password.
+To access the PostgreSQL database, connect to `host: postgres` as user `postgres` with no password.
diff --git a/doc/ci/examples/test-clojure-application.md b/doc/ci/examples/test-clojure-application.md
index 3b1026d174f..5cda8702b56 100644
--- a/doc/ci/examples/test-clojure-application.md
+++ b/doc/ci/examples/test-clojure-application.md
@@ -1,8 +1,15 @@
+---
+type: tutorial
+---
+
+NOTE: **Note:**
+This document has not been updated recently and could be out of date. For the latest documentation, see the [GitLab CI/CD](../README.md) page and the [GitLab CI/CD Pipeline Configuration Reference](../yaml/README.md).
+
# Test a Clojure application with GitLab CI/CD
-This example will guide you how to run tests in your Clojure application.
+This example will guide you how to run tests on your Clojure application.
-You can checkout the example [source](https://gitlab.com/dzaporozhets/clojure-web-application) and check [CI status](https://gitlab.com/dzaporozhets/clojure-web-application/builds?scope=all).
+You can view or fork the [example source](https://gitlab.com/dzaporozhets/clojure-web-application) and view the logs of its past [CI jobs](https://gitlab.com/dzaporozhets/clojure-web-application/builds?scope=finished).
## Configure the project
@@ -28,8 +35,9 @@ test:
- lein test
```
-In before script we install JRE and [Leiningen](http://leiningen.org/).
-Sample project uses [migratus](https://github.com/yogthos/migratus) library to manage database migrations.
-So we added database migration as last step of `before_script` section
+In `before_script`, we install JRE and [Leiningen](http://leiningen.org/).
+
+The sample project uses the [migratus](https://github.com/yogthos/migratus) library to manage database migrations, and
+we have added a database migration as the last step of `before_script`.
-You can use public runners available on `gitlab.com` for testing your application with such configuration.
+You can use public runners available on `gitlab.com` for testing your application with this configuration.
diff --git a/doc/ci/examples/test-scala-application.md b/doc/ci/examples/test-scala-application.md
index e1164b8d03a..0e33a1ba060 100644
--- a/doc/ci/examples/test-scala-application.md
+++ b/doc/ci/examples/test-scala-application.md
@@ -1,9 +1,12 @@
+---
+type: tutorial
+---
+
# Test and deploy a Scala application to Heroku
This example demonstrates the integration of GitLab CI with Scala
-applications using SBT. Checkout the example
-[project](https://gitlab.com/gitlab-examples/scala-sbt) and
-[build status](https://gitlab.com/gitlab-examples/scala-sbt/builds).
+applications using SBT. You can view or fork the [example project](https://gitlab.com/gitlab-examples/scala-sbt)
+and view the logs of its past [CI jobs](https://gitlab.com/gitlab-examples/scala-sbt/-/jobs?scope=finished).
## Add `.gitlab-ci.yml` file to project
@@ -41,12 +44,14 @@ deploy:
- dpl --provider=heroku --app=gitlab-play-sample-app --api-key=$HEROKU_API_KEY
```
-The `before_script` installs [SBT](http://www.scala-sbt.org/) and
-displays the version that is being used. The `test` stage executes SBT
-to compile and test the project.
-[scoverage](https://github.com/scoverage/sbt-scoverage) is used as an SBT
+In the above configuration:
+
+- The `before_script` installs [SBT](http://www.scala-sbt.org/) and
+displays the version that is being used.
+- The `test` stage executes SBT to compile and test the project.
+ - [sbt-scoverage](https://github.com/scoverage/sbt-scoverage) is used as an SBT
plugin to measure test coverage.
-The `deploy` stage automatically deploys the project to Heroku using dpl.
+- The `deploy` stage automatically deploys the project to Heroku using dpl.
You can use other versions of Scala and SBT by defining them in
`build.sbt`.
diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
index 4a5fda661df..ec25ca1bfc3 100644
--- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
@@ -3,6 +3,7 @@ author: Alexandre S Hostert
author_gitlab: Hostert
level: beginner
article_type: tutorial
+type: tutorial
date: 2018-02-20
last_updated: 2019-03-06
---
@@ -16,11 +17,11 @@ simultaneous users.
That's why we're hearing so much about Phoenix today.
-In this tutorial, we'll teach you how to set up GitLab CI/CD to build and test a Phoenix
+In this tutorial, we'll teach you how to set up [GitLab CI/CD](../../README.md) to build and test a Phoenix
application.
-_We assume that you know how to create a Phoenix app, run tests locally, and how to work with Git
-and GitLab UI._
+The tutorial assumes that you know how to create a Phoenix app, run tests locally, and how to work with Git
+and the GitLab UI.
## Introduction
diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md
index 37078230b34..551044dd76f 100644
--- a/doc/ci/git_submodules.md
+++ b/doc/ci/git_submodules.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Using Git submodules with GitLab CI
> **Notes:**
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index 7109b2ec583..1387d4df500 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Interactive Web Terminals
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/50144) in GitLab 11.3.
diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md
index 0045fa2fb1f..1687716df2e 100644
--- a/doc/ci/introduction/index.md
+++ b/doc/ci/introduction/index.md
@@ -1,5 +1,6 @@
---
description: "An overview of Continuous Integration, Continuous Delivery, and Continuous Deployment, as well as an introduction to GitLab CI/CD."
+type: concepts
---
# Introduction to CI/CD with GitLab
@@ -204,6 +205,9 @@ With GitLab CI/CD you can also:
To see all CI/CD features, navigate back to the [CI/CD index](../README.md).
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+Watch the video [GitLab CI Live Demo](https://www.youtube.com/watch?v=pBe4t1CD8Fc) with a deeper overview of GitLab CI/CD.
+
### Setting up GitLab CI/CD for the first time
To get started with GitLab CI/CD, you need to familiarize yourself
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index 799217c9a08..fa78f53f563 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# JUnit test reports
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45318) in GitLab 11.2.
diff --git a/doc/ci/large_repositories/index.md b/doc/ci/large_repositories/index.md
index 244ccbb92b0..29d649ad717 100644
--- a/doc/ci/large_repositories/index.md
+++ b/doc/ci/large_repositories/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Optimizing GitLab for large repositories
Large repositories consisting of more than 50k files in a worktree
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
index b3ff55daea2..fe2fc790505 100644
--- a/doc/ci/merge_request_pipelines/index.md
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Pipelines for merge requests
NOTE: **Note**:
diff --git a/doc/ci/metrics_reports.md b/doc/ci/metrics_reports.md
index 83a7094faaa..3ded6673149 100644
--- a/doc/ci/metrics_reports.md
+++ b/doc/ci/metrics_reports.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Metrics Reports **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9788) in [GitLab Premium](https://about.gitlab.com/pricing) 11.10.
@@ -24,6 +28,11 @@ Consider the following examples of data that can utilize Metrics Reports:
Metrics are read from the metrics report (default: `metrics.txt`). They are parsed and displayed in the MR widget.
+All values are considered strings and string compare is used to find differences between the latest available `metrics` artifact from:
+
+- `master`
+- The feature branch
+
## How to set it up
Add a job that creates a [metrics report](yaml/README.md#artifactsreportsmetrics-premium) (default filename: `metrics.txt`). The file should conform to the [OpenMetrics](https://openmetrics.io/) format.
diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md
index 556059c01b6..50c8d82602b 100644
--- a/doc/ci/multi_project_pipelines.md
+++ b/doc/ci/multi_project_pipelines.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Multi-project pipelines **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/2121) in
@@ -22,6 +26,10 @@ and when hovering or tapping (on touchscreen devices) they will expand and be sh
Multi-project pipelines are useful for larger products that require cross-project inter-dependencies, such as those
adopting a [microservices architecture](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/).
+For a demonstration of how cross-functional development teams can use cross-pipeline
+triggering to trigger multiple pipelines for different microservices projects, see
+[Cross-project Pipeline Triggering and Visualization](https://about.gitlab.com/handbook/marketing/product-marketing/demo/#cross-project-pipeline-triggering-and-visualization-may-2019---1110).
+
## Use cases
Let's assume you deploy your web app from different projects in GitLab:
@@ -134,6 +142,35 @@ staging:
The `ENVIRONMENT` variable will be passed to every job defined in a downstream
pipeline. It will be available as an environment variable when GitLab Runner picks a job.
+In the following configuration, the `MY_VARIABLE` variable will be passed to the downstream pipeline
+that is created when the `trigger-downstream` job is queued. This is because `trigger-downstream`
+job inherits variables declared in global variables blocks, and then we pass these variables to a downstream pipeline.
+
+```yaml
+variables:
+ MY_VARIABLE: my-value
+
+trigger-downstream:
+ variables:
+ ENVIRONMENT: something
+ trigger: my/project
+```
+
+You might want to pass some information about the upstream pipeline using, for
+example, predefined variables. In order to do that, you can use interpolation
+to pass any variable. For example:
+
+```yaml
+downstream-job:
+ variables:
+ UPSTREAM_BRANCH: $CI_COMMIT_REF_NAME
+ trigger: my/project
+```
+
+In this scenario, the `UPSTREAM_BRANCH` variable with a value related to the
+upstream pipeline will be passed to the `downstream-job` job, and will be available
+within the context of all downstream builds.
+
### Limitations
Because bridge jobs are a little different to regular jobs, it is not
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 4dbe1a85588..1ad3d516df9 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Creating and using CI/CD pipelines
> Introduced in GitLab 8.8.
@@ -266,9 +270,6 @@ Clicking on an individual job will show you its job trace, and allow you to:
- Retry the job.
- Erase the job trace.
-NOTE: **Note:**
-To prevent jobs from being bypassed or run out of order, canceled jobs can only be retried when the whole pipeline they belong to is retried.
-
### Seeing the failure reason for jobs
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17782) in GitLab 10.7.
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 015f1c0dc0f..11bcfd5dc2c 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -1,9 +1,18 @@
+---
+type: reference
+---
+
# Getting started with GitLab CI/CD
->**Note:** Starting from version 8.0, GitLab [Continuous Integration][ci] (CI)
+NOTE: **Note:**
+Starting from version 8.0, GitLab [Continuous Integration][ci] (CI)
is fully integrated into GitLab itself and is [enabled] by default on all
projects.
+NOTE: **Note:**
+Please keep in mind that only project Maintainers and Admin users have
+the permissions to access a project's settings.
+
GitLab offers a [continuous integration][ci] service. If you
[add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository,
and configure your GitLab project to use a [Runner], then each commit or
@@ -35,11 +44,12 @@ project's **Pipelines** page.
---
-This guide assumes that you:
+This guide assumes that you have:
-- have a working GitLab instance of version 8.0+r or are using
- [GitLab.com](https://gitlab.com)
-- have a project in GitLab that you would like to use CI for
+- A working GitLab instance of version 8.0+r or are using
+ [GitLab.com](https://gitlab.com).
+- A project in GitLab that you would like to use CI for.
+- Maintainer or owner access to the project
Let's break it down to pieces and work on solving the GitLab CI puzzle.
@@ -73,6 +83,8 @@ You need to create a file named `.gitlab-ci.yml` in the root directory of your
repository. Below is an example for a Ruby on Rails project.
```yaml
+image: "ruby:2.5"
+
before_script:
- apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
- ruby -v
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index 1a71c5fd258..7b039fe6654 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -1,9 +1,13 @@
+---
+type: reference
+---
+
# Review Apps
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/21971) in GitLab 8.12. Further additions were made in GitLab 8.13 and 8.14.
> - Inspired by [Heroku's Review Apps](https://devcenter.heroku.com/articles/github-integration-review-apps), which itself was inspired by [Fourchette](https://github.com/rainforestapp/fourchette).
-Review Apps are a collaboration tool that takes the hard work out of providing an environment to showcase product changes.
+Review Apps is a collaboration tool that takes the hard work out of providing an environment to showcase product changes.
## Introduction
@@ -18,7 +22,7 @@ Review Apps:
In the above example:
-- A Review App is built every time a commit is pushed to`topic branch`.
+- A Review App is built every time a commit is pushed to `topic branch`.
- The reviewer fails two reviews before passing the third review.
- Once the review as passed, `topic branch` is merged into `master` where it's deploy to staging.
- After been approved in staging, the changes that were merged into `master` are deployed in to production.
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index ce55b231666..b089229ab58 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring GitLab Runners
In GitLab CI, Runners run the code defined in [`.gitlab-ci.yml`](../yaml/README.md).
diff --git a/doc/ci/services/README.md b/doc/ci/services/README.md
index 2eda5d23976..7fe12eb53e7 100644
--- a/doc/ci/services/README.md
+++ b/doc/ci/services/README.md
@@ -1,13 +1,18 @@
---
comments: false
+type: index
---
-# GitLab CI Services
+# GitLab CI services examples
-GitLab CI uses the `services` keyword to define what docker containers should
-be linked with your base image. Below is a list of examples you may use.
+The [`services`](../docker/using_docker_images.md#what-is-a-service)
+keyword defines a Docker image that runs during a `job` linked to the
+Docker image that the image keyword defines. This allows you to access
+the service image during build time.
+
+The service image can run any application, but the most common use
+case is to run a database container, for example:
- [Using MySQL](mysql.md)
- [Using PostgreSQL](postgres.md)
- [Using Redis](redis.md)
-- [Using Other Services](../docker/using_docker_images.md#what-is-a-service)
diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md
index 5fa378fc4c2..697452cee83 100644
--- a/doc/ci/services/mysql.md
+++ b/doc/ci/services/mysql.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Using MySQL
As many applications depend on MySQL as their database, you will eventually
diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md
index 2e6d7ae94d2..211eea26eb0 100644
--- a/doc/ci/services/postgres.md
+++ b/doc/ci/services/postgres.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Using PostgreSQL
As many applications depend on PostgreSQL as their database, you will
diff --git a/doc/ci/services/redis.md b/doc/ci/services/redis.md
index 36f71427ae7..8b227154b06 100644
--- a/doc/ci/services/redis.md
+++ b/doc/ci/services/redis.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Using Redis
As many applications depend on Redis as their key-value store, you will
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index 9ed1ec5aa5c..69591ed605c 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -1,5 +1,6 @@
---
last_updated: 2017-12-13
+type: tutorial
---
# Using SSH keys with GitLab CI/CD
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index ad80b5d8818..04c541fefe7 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -1,3 +1,7 @@
+---
+type: tutorial
+---
+
# Triggering pipelines through the API
> **Notes**:
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 67e1d316f02..df455857dee 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -1,5 +1,6 @@
---
table_display_block: true
+type: reference
---
# GitLab CI/CD environment variables
@@ -86,15 +87,14 @@ Variable types can be set via the [UI](#via-the-ui) or the [API](../../api/proje
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/13784) in GitLab 11.10
-By default, variables will be created as masked variables.
+Variables can be created as masked variables.
This means that the value of the variable will be hidden in job logs,
though it must match certain requirements to do so:
- The value must be in a single line.
-- The value must not have escape characters.
-- The value must not use variables.
-- The value must not have any whitespace.
+- The value must only consist of characters from the Base64 alphabet, defined in [RFC4648](https://tools.ietf.org/html/rfc4648).
- The value must be at least 8 characters long.
+- The value must not use variables.
If the value does not meet the requirements above, then the CI variable will fail to save.
In order to save, either alter the value to meet the masking requirements
@@ -389,21 +389,9 @@ Once you set them, they will be available for all subsequent pipelines.
### Limiting environment scopes of environment variables **[PREMIUM]**
-> [Introduced][ee-2112] in [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
-
You can limit the environment scope of a variable by
[defining which environments][envs] it can be available for.
-Wildcards can be used, and the default environment scope is `*` which means
-any jobs will have this variable, not matter if an environment is defined or
-not.
-
-For example, if the environment scope is `production`, then only the jobs
-having the environment `production` defined would have this specific variable.
-Wildcards (`*`) can be used along with the environment name, therefore if the
-environment scope is `review/*` then any jobs with environment names starting
-with `review/` would have that particular variable.
-
To learn more about about scoping environments, see [Scoping environments with specs](../environments.md#scoping-environments-with-specs-premium).
### Deployment environment variables
@@ -491,6 +479,7 @@ Below you can find supported syntax reference:
1. Equality matching using a string
> Example: `$VARIABLE == "some value"`
+
> Example: `$VARIABLE != "some value"` _(added in 11.11)_
You can use equality operator `==` or `!=` to compare a variable content to a
@@ -501,6 +490,7 @@ Below you can find supported syntax reference:
1. Checking for an undefined value
> Example: `$VARIABLE == null`
+
> Example: `$VARIABLE != null` _(added in 11.11)_
It sometimes happens that you want to check whether a variable is defined
@@ -511,6 +501,7 @@ Below you can find supported syntax reference:
1. Checking for an empty variable
> Example: `$VARIABLE == ""`
+
> Example: `$VARIABLE != ""` _(added in 11.11)_
If you want to check whether a variable is defined, but is empty, you can
@@ -520,6 +511,7 @@ Below you can find supported syntax reference:
1. Comparing two variables
> Example: `$VARIABLE_1 == $VARIABLE_2`
+
> Example: `$VARIABLE_1 != $VARIABLE_2` _(added in 11.11)_
It is possible to compare two variables. This is going to compare values
@@ -539,6 +531,7 @@ Below you can find supported syntax reference:
1. Pattern matching _(added in 11.0)_
> Example: `$VARIABLE =~ /^content.*/`
+
> Example: `$VARIABLE_1 !~ /^content.*/` _(added in 11.11)_
It is possible perform pattern matching against a variable and regular
@@ -548,6 +541,19 @@ Below you can find supported syntax reference:
Pattern matching is case-sensitive by default. Use `i` flag modifier, like
`/pattern/i` to make a pattern case-insensitive.
+1. Conjunction / Disjunction
+
+ > Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"`
+
+ > Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 =~ /thing$/ && $VARIABLE3`
+
+ > Example: `$VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/ && $VARIABLE3`
+
+ It is possible to join multiple conditions using `&&` or `||`. Any of the otherwise
+ supported syntax may be used in a conjunctive or disjunctive statement.
+ Precedence of operators follows standard Ruby 2.5 operation
+ [precedence](https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html).
+
## Debug tracing
> Introduced in GitLab Runner 1.7.
@@ -612,8 +618,8 @@ $'\''git'\'' "checkout" "-f" "-q" "dd648b2e48ce6518303b0bb580b2ee32fadaf045"
Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-machine-1480971377-317a7d0f-digital-ocean-4gb...
++ export CI=true
++ CI=true
-++ export CI_API_V4_API_URL=https://example.com:3000/api/v4
-++ CI_API_V4_API_URL=https://example.com:3000/api/v4
+++ export CI_API_V4_URL=https://example.com:3000/api/v4
+++ CI_API_V4_URL=https://example.com:3000/api/v4
++ export CI_DEBUG_TRACE=false
++ CI_DEBUG_TRACE=false
++ export CI_COMMIT_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
@@ -652,8 +658,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ GITLAB_CI=true
++ export CI=true
++ CI=true
-++ export CI_API_V4_API_URL=https://example.com:3000/api/v4
-++ CI_API_V4_API_URL=https://example.com:3000/api/v4
+++ export CI_API_V4_URL=https://example.com:3000/api/v4
+++ CI_API_V4_URL=https://example.com:3000/api/v4
++ export GITLAB_CI=true
++ GITLAB_CI=true
++ export CI_JOB_ID=7046507
@@ -717,7 +723,6 @@ MIIFQzCCBCugAwIBAgIRAL/ElDjuf15xwja1ZnCocWAwDQYJKoZIhvcNAQELBQAw'
...
```
-[ee-2112]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112
[ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 "Simple protection of CI variables"
[envs]: ../environments.md
[protected branches]: ../../user/project/protected_branches.md
diff --git a/doc/ci/variables/deprecated_variables.md b/doc/ci/variables/deprecated_variables.md
index 2642c9b0eb4..cdca5bf27fc 100644
--- a/doc/ci/variables/deprecated_variables.md
+++ b/doc/ci/variables/deprecated_variables.md
@@ -1,5 +1,12 @@
+---
+type: reference
+---
+
# Deprecated GitLab CI/CD variables
+Read through this document to learn what predefined variables
+were deprecated and their new references.
+
## GitLab 9.0 renamed variables
To follow conventions of naming across GitLab, and to further move away from the
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 4e902c042e6..4655eec51de 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Predefined environment variables reference
For an introduction on this subject, read through the
diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md
index 0470cf52654..8009b1d5e8a 100644
--- a/doc/ci/variables/where_variables_can_be_used.md
+++ b/doc/ci/variables/where_variables_can_be_used.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Where variables can be used
As it's described in the [CI/CD variables](README.md) docs, you can
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 31ff56e06f8..fa4b0378f61 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# GitLab CI/CD Pipeline Configuration Reference
GitLab CI/CD [pipelines](../pipelines.md) are configured using a YAML file called `.gitlab-ci.yml` within each project.
@@ -108,7 +112,7 @@ The following table lists available parameters for jobs:
| [`parallel`](#parallel) | How many instances of a job should be run in parallel. |
| [`trigger`](#trigger-premium) | Defines a downstream pipeline trigger. |
| [`include`](#include) | Allows this job to include external YAML files. Also available: `include:local`, `include:file`, `include:template`, and `include:remote`. |
-| [`extends`](#extends) | Configuration entry that this job is going to inherit from. |
+| [`extends`](#extends) | Configuration entries that this job is going to inherit from. |
| [`pages`](#pages) | Upload the result of a job to use with GitLab Pages. |
| [`variables`](#variables) | Define job variables on a job level. |
@@ -386,17 +390,12 @@ job:
- branches@gitlab-org/gitlab-ce
except:
- master@gitlab-org/gitlab-ce
- - release/.*@gitlab-org/gitlab-ce
+ - /^release/.*$/@gitlab-org/gitlab-ce
```
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except `master` and those with names prefixed with `release/`.
-NOTE: **Note:**
-Because `@` is used to denote the beginning of a ref's repository path,
-matching a ref name containing the `@` character in a regular expression
-requires the use of the hex character code match `\x40`.
-
If a job does not have an `only` rule, `only: ['branches', 'tags']` is set by
default. If it doesn't have an `except` rule, it is empty.
@@ -415,6 +414,28 @@ job:
only: ['branches', 'tags']
```
+#### Regular expressions
+
+Because `@` is used to denote the beginning of a ref's repository path,
+matching a ref name containing the `@` character in a regular expression
+requires the use of the hex character code match `\x40`.
+
+Only the tag or branch name can be matched by a regular expression.
+The repository path, if given, is always matched literally.
+
+If a regular expression shall be used to match the tag or branch name,
+the entire ref name part of the pattern has to be a regular expression,
+and must be surrounded by `/`.
+(With regular expression flags appended after the closing `/`.)
+So `issue-/.*/` won't work to match all tag names or branch names
+that begin with `issue-`.
+
+TIP: **Tip**
+Use anchors `^` and `$` to avoid the regular expression
+matching only a substring of the tag name or branch name.
+For example, `/^issue-.*$/` is equivalent to `/^issue-/`,
+while just `/issue/` would also match a branch called `severe-issues`.
+
### Supported `only`/`except` regexp syntax
CAUTION: **Warning:**
@@ -1168,9 +1189,9 @@ skip the download step.
> - Job artifacts are only collected for successful jobs by default.
`artifacts` is used to specify a list of files and directories which should be
-attached to the job after success.
+attached to the job when it [succeeds, fails, or always](#artifactswhen).
-The artifacts will be sent to GitLab after the job finishes successfully and will
+The artifacts will be sent to GitLab after the job finishes and will
be available for download in the GitLab UI.
[Read more about artifacts](../../user/project/pipelines/job_artifacts.md).
@@ -1986,7 +2007,7 @@ production:
- deploy
environment:
name: production
- url: https://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
+ url: https://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
only:
- master
```
@@ -2100,7 +2121,7 @@ docker-test:
> Introduced in GitLab 11.3.
-`extends` defines an entry name that a job that uses `extends` is going to
+`extends` defines entry names that a job that uses `extends` is going to
inherit from.
It is an alternative to using [YAML anchors](#anchors) and is a little
@@ -2177,6 +2198,46 @@ spinach:
script: rake spinach
```
+It's also possible to use multiple parents for `extends`.
+The algorithm used for merge is "closest scope wins", so keys
+from the last member will always shadow anything defined on other levels.
+For example:
+
+```yaml
+.only-important:
+ only:
+ - master
+ - stable
+ tags:
+ - production
+
+.in-docker:
+ tags:
+ - docker
+ image: alpine
+
+rspec:
+ extends:
+ - .only-important
+ - .in-docker
+ script:
+ - rake rspec
+```
+
+This results in the following `rspec` job:
+
+```yaml
+rspec:
+ only:
+ - master
+ - stable
+ tags:
+ - docker
+ image: alpine
+ script:
+ - rake rspec
+```
+
### Using `extends` and `include` together
`extends` works across configuration files combined with `include`.
@@ -2730,6 +2791,18 @@ using Git 2.10 or newer:
git push -o ci.skip
```
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323
[ce-6669]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6669
[ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
diff --git a/doc/customization/issue_and_merge_request_template.md b/doc/customization/issue_and_merge_request_template.md
index 01c31728c21..adaa120a37e 100644
--- a/doc/customization/issue_and_merge_request_template.md
+++ b/doc/customization/issue_and_merge_request_template.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/project/description_templates.html#setting-a-default-template-for-issues-and-merge-requests--starter'
+redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter'
---
-This document was moved to [description_templates](https://docs.gitlab.com/ee/user/project/description_templates.html#setting-a-default-template-for-issues-and-merge-requests--starter).
+This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter).
diff --git a/doc/development/README.md b/doc/development/README.md
index 2ff38d68a47..d2f09fc01de 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -20,6 +20,7 @@ description: 'Learn how to contribute to GitLab.'
- [Automatic CE->EE merge](automatic_ce_ee_merge.md)
- [Guidelines for implementing Enterprise Edition features](ee_features.md)
- [Security process for developers](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer)
+- [Requesting access to Chatops on GitLab.com](chatops_on_gitlabcom.md#requesting-access) (for GitLabbers)
## UX and frontend guides
@@ -59,6 +60,7 @@ description: 'Learn how to contribute to GitLab.'
- [DeclarativePolicy framework](policies.md)
- [How Git object deduplication works in GitLab](git_object_deduplication.md)
- [Geo development](geo.md)
+- [Routing](routing.md)
## Performance guides
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 8d2bfff3a5d..38270af682e 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -32,6 +32,21 @@ a new presenter specifically for GraphQL.
The presenter is initialized using the object resolved by a field, and
the context.
+### Exposing Global ids
+
+When exposing an `id` field on a type, we will by default try to
+expose a global id by calling `to_global_id` on the resource being
+rendered.
+
+To override this behaviour, you can implement an `id` method on the
+type for which you are exposing an id. Please make sure that when
+exposing a `GraphQL::ID_TYPE` using a custom method that it is
+globally unique.
+
+The records that are exposing a `full_path` as an `ID_TYPE` are one of
+these exceptions. Since the full path is a unique identifier for a
+`Project` or `Namespace`.
+
### Connection Types
GraphQL uses [cursor based
@@ -79,14 +94,14 @@ look like this:
{
"cursor": "Nzc=",
"node": {
- "id": "77",
+ "id": "gid://gitlab/Pipeline/77",
"status": "FAILED"
}
},
{
"cursor": "Njc=",
"node": {
- "id": "67",
+ "id": "gid://gitlab/Pipeline/67",
"status": "FAILED"
}
}
@@ -330,7 +345,7 @@ argument :project_path, GraphQL::ID_TYPE,
required: true,
description: "The project the merge request to mutate is in"
-argument :iid, GraphQL::ID_TYPE,
+argument :iid, GraphQL::STRING_TYPE,
required: true,
description: "The iid of the merge request to mutate"
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 9a012f4299b..5f32cd7eba2 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -1,3 +1,7 @@
+---
+table_display_block: true
+---
+
# GitLab Architecture Overview
## Software delivery
@@ -26,6 +30,21 @@ Gitaly executes git operations from gitlab-shell and the GitLab web app, and pro
You may also be interested in the [production architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/).
+### Simplified Component Overview
+
+This is a simplified architecture diagram that can be used to
+understand GitLab's architecture.
+
+A complete architecture diagram is available in our
+[component diagram](#component-diagram) below.
+
+![Simplified Component Overview](img/architecture_simplified.png)
+
+<!--
+To update this diagram, GitLab team members can edit this source file:
+https://docs.google.com/drawings/d/1fBzAyklyveF-i-2q-OHUIqDkYfjjxC4mq5shwKSZHLs/edit.
+ -->
+
### Component diagram
```mermaid
@@ -59,7 +78,7 @@ graph TB
PgBouncerExporter[PgBouncer Exporter] --> PgBouncer
Prometheus -- TCP 9187 --> PostgreSQLExporter
Prometheus -- TCP 9100 --> NodeExporter[Node Exporter]
- Prometheus -- TCP 9168 --> GitLabMonito[GitLab Monitor]
+ Prometheus -- TCP 9168 --> GitLabMonitor[GitLab Monitor]
Prometheus -- TCP 9127 --> PgBouncerExporter
GitLabMonitor --> PostgreSQL
GitLabMonitor --> GitLabShell
@@ -106,43 +125,43 @@ Component statuses are linked to configuration documentation for each component.
### Component list
-| Component | Description | [Omnibus GitLab](https://docs.gitlab.com/omnibus/README.html) | [GitLab chart](https://docs.gitlab.com/charts/) | [Minikube Minimal](https://docs.gitlab.com/charts/development/minikube/#deploying-gitlab-with-minimal-settings) | [GitLab.com](https://gitlab.com) | CE/EE |
-| --------- | ----------- |:--------------------:|:------------------:|:-----:|:--------:|:--------:|
-| [NGINX](#nginx) | Routes requests to appropriate components, terminates SSL | [✅][nginx-omnibus] | [✅][nginx-charts] | [⚙][nginx-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | CE & EE |
-| [Unicorn (GitLab Rails)](#unicorn) | Handles requests for the web interface and API | [✅][unicorn-omnibus] | [✅][unicorn-charts] | [✅][unicorn-charts] | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#unicorn) | CE & EE |
-| [Sidekiq](#sidekiq) | Background jobs processor | [✅][sidekiq-omnibus] | [✅][sidekiq-charts] | [✅](https://docs.gitlab.com/charts/charts/gitlab/sidekiq/index.html) | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#sidekiq) | CE & EE |
-| [Gitaly](#gitaly) | Git RPC service for handling all git calls made by GitLab | [✅][gitaly-omnibus] | [✅][gitaly-charts] | [✅][gitaly-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | CE & EE |
-| [GitLab Workhorse](#gitlab-workhorse) | Smart reverse proxy, handles large HTTP requests | [✅][workhorse-omnibus] | [✅][workhorse-charts] | [✅][workhorse-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | CE & EE |
-| [GitLab Shell](#gitlab-shell) | Handles `git` over SSH sessions | [✅][shell-omnibus] | [✅][shell-charts] | [✅][shell-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | CE & EE |
-| [GitLab Pages](#gitlab-pages) | Hosts static websites | [⚙][pages-omnibus] | [❌][pages-charts] | [❌][pages-charts] | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#gitlab-pages) | CE & EE |
-| [Registry](#registry) | Container registry, allows pushing and pulling of images | [⚙][registry-omnibus] | [✅][registry-charts] | [✅][registry-charts] | [✅](https://docs.gitlab.com/ee/user/project/container_registry.html#build-and-push-images) | CE & EE |
-| [Redis](#redis) | Caching service | [✅][redis-omnibus] | [✅][redis-omnibus] | [✅][redis-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | CE & EE |
-| [PostgreSQL](#postgresql) | Database | [✅][postgres-omnibus] | [✅][postgres-charts] | [✅][postgres-charts] | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#postgresql) | CE & EE |
-| [PgBouncer](#pgbouncer) | Database connection pooling, failover | [⚙][pgbouncer-omnibus] | [❌][pgbouncer-charts] | [❌][pgbouncer-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#database-architecture) | EE Only |
-| [Consul](#consul) | Database node discovery, failover | [⚙][consul-omnibus] | [❌][consul-charts] | [❌][consul-charts] | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#consul) | EE Only |
-| [GitLab self-monitoring: Prometheus](#prometheus) | Time-series database, metrics collection, and query service | [✅][prometheus-omnibus] | [✅][prometheus-charts] | [⚙][prometheus-charts] | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#prometheus) | CE & EE |
-| [GitLab self-monitoring: Alertmanager](#alertmanager) | Deduplicates, groups, and routes alerts from Prometheus | [✅][alertmanager-omnibus] | [✅][alertmanager-charts] | [⚙][alertmanager-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE |
-| [GitLab self-monitoring: Grafana](#grafana) | Metrics dashboard | [⚙][grafana-omnibus] | [⤓][grafana-charts] | [⤓][grafana-charts] | [✅](https://dashboards.gitlab.com/d/RZmbBr7mk/gitlab-triage?refresh=30s) | CE & EE |
-| [GitLab self-monitoring: Sentry](#sentry) | Track errors generated by the GitLab instance | [⤓][sentry-omnibus] | [❌][sentry-charts] | [❌][sentry-charts] | [✅](https://about.gitlab.com/handbook/support/workflows/services/gitlab_com/500_errors.html#searching-sentry) | CE & EE |
-| [GitLab self-monitoring: Jaeger](#jaeger) | View traces generated by the GitLab instance | [❌][jaeger-omnibus] | [❌][jaeger-charts] | [❌][jaeger-charts] | [❌](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104) | CE & EE |
-| [Redis Exporter](#redis-exporter) | Prometheus endpoint with Redis metrics | [✅][redis-exporter-omnibus] | [✅][redis-exporter-charts] | [✅][redis-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE |
-| [Postgres Exporter](#postgres-exporter) | Prometheus endpoint with PostgreSQL metrics | [✅][postgres-exporter-omnibus] | [✅][postgres-exporter-charts] | [✅][postgres-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE |
-| [PgBouncer Exporter](#pgbouncer-exporter) | Prometheus endpoint with PgBouncer metrics | [⚙][pgbouncer-exporter-omnibus] | [❌][pgbouncer-exporter-charts] | [❌][pgbouncer-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE |
-| [GitLab Monitor](#gitlab-monitor) | Generates a variety of GitLab metrics | [✅][gitlab-monitor-omnibus] | [❌][gitab-monitor-charts] | [❌][gitab-monitor-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE |
-| [Node Exporter](#node-exporter) | Prometheus endpoint with system metrics | [✅][node-exporter-omnibus] | [❌][node-exporter-charts] | [❌][node-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | CE & EE |
-| [Mattermost](#mattermost) | Open-source Slack alternative | [⚙][mattermost-omnibus] | [⤓][mattermost-charts] | [⤓][mattermost-charts] | [⤓](https://docs.gitlab.com/ee/user/project/integrations/mattermost_slash_commands.html#manual-configuration), [⤓](https://docs.gitlab.com/ee/user/project/integrations/mattermost.html) | CE & EE |
-| [Minio](#minio) | Object storage service | [⤓][minio-omnibus] | [✅][minio-charts] | [✅][minio-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#storage-architecture) | CE & EE |
-| [Runner](#gitlab-runner) | Executes GitLab CI jobs | [⤓][runner-omnibus] | [✅][runner-charts] | [⚙][runner-charts] | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#shared-runners) | CE & EE |
-| [Database Migrations](#database-migrations) | Database migrations | [✅][database-migrations-omnibus] | [✅]() | [✅][database-migrations-charts] | [✅][database-migrations-charts] | CE & EE |
-| [Certificate Management](#certificate-management) | TLS Settings, Let's Encrypt | [✅][certificate-management-omnibus] | [✅][certificate-management-charts] | [⚙][certificate-management-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#secrets-management) | CE & EE |
-| [GitLab Geo Node](#gitlab-geo) | Geographically distributed GitLab nodes | [⚙][geo-omnibus] | [❌][geo-charts] | [❌][geo-charts] | ✅ | EE Only |
-| [LDAP Authentication](#ldap-authentication) | Authenticate users against centralized LDAP directory | [⤓][ldap-omnibus] | [⤓][ldap-charts] | [⤓][ldap-charts] | [❌](https://about.gitlab.com/pricing/#gitlab-com) | CE & EE |
-| [Outbound email (SMTP)](#outbound-email) | Send email messages to users | [⤓][outbound-email-omnibus] | [⤓][outbound-email-charts] | [⤓][outbound-email-charts] | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#mail-configuration) | CE & EE |
-| [Inbound email (SMTP)](#inbound-email) | Receive messages to update issues | [⤓][inbound-email-omnibus] | [⤓][inbound-email-charts] | [⤓][inbound-email-charts] | [✅](https://docs.gitlab.com/ee/user/gitlab_com/#mail-configuration) | CE & EE |
-| [ElasticSearch](#elasticsearch) | Improved search within GitLab | [⤓][elasticsearch-omnibus] | [⤓][elasticsearch-charts] | [⤓][elasticsearch-charts] | [❌](https://gitlab.com/groups/gitlab-org/-/epics/153) | EE Only |
-| [Sentry integration](#sentry) | Error tracking for deployed apps | [⤓][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] | EE Only |
-| [Kubernetes cluster apps](#kubernetes-cluster-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] | CE & EE |
+| Component | Description | [Omnibus GitLab](https://docs.gitlab.com/omnibus/) | [GitLab chart](https://docs.gitlab.com/charts/) | [Minikube Minimal](https://docs.gitlab.com/charts/development/minikube/#deploying-gitlab-with-minimal-settings) | [GitLab.com](https://gitlab.com) | [Source](../install/installation.md) | [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit) | CE/EE |
+| --------- | ----------- |:--------------------:|:------------------:|:-----:|:--------:|:--------:|:-------:|:-------:|
+| [NGINX](#nginx) | Routes requests to appropriate components, terminates SSL | [✅][nginx-omnibus] | [✅][nginx-charts] | [⚙][nginx-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⤓][nginx-source] | ❌ | CE & EE |
+| [Unicorn (GitLab Rails)](#unicorn) | Handles requests for the web interface and API | [✅][unicorn-omnibus] | [✅][unicorn-charts] | [✅][unicorn-charts] | [✅](../user/gitlab_com/index.md#unicorn) | [⚙][unicorn-source] | [✅][gitlab-yml] | CE & EE |
+| [Sidekiq](#sidekiq) | Background jobs processor | [✅][sidekiq-omnibus] | [✅][sidekiq-charts] | [✅](https://docs.gitlab.com/charts/charts/gitlab/sidekiq/index.html) | [✅](../user/gitlab_com/index.md#sidekiq) | [✅][gitlab-yml] | [✅][gitlab-yml] | CE & EE |
+| [Gitaly](#gitaly) | Git RPC service for handling all git calls made by GitLab | [✅][gitaly-omnibus] | [✅][gitaly-charts] | [✅][gitaly-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][gitaly-source] | ✅ | CE & EE |
+| [GitLab Workhorse](#gitlab-workhorse) | Smart reverse proxy, handles large HTTP requests | [✅][workhorse-omnibus] | [✅][workhorse-charts] | [✅][workhorse-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][workhorse-source] | ✅ | CE & EE |
+| [GitLab Shell](#gitlab-shell) | Handles `git` over SSH sessions | [✅][shell-omnibus] | [✅][shell-charts] | [✅][shell-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][shell-source] | [✅][gitlab-yml] | CE & EE |
+| [GitLab Pages](#gitlab-pages) | Hosts static websites | [⚙][pages-omnibus] | [❌][pages-charts] | [❌][pages-charts] | [✅](../user/gitlab_com/index.md#gitlab-pages) | [⚙][pages-source] | [⚙][pages-gdk] | CE & EE |
+| [Registry](#registry) | Container registry, allows pushing and pulling of images | [⚙][registry-omnibus] | [✅][registry-charts] | [✅][registry-charts] | [✅](../user/project/container_registry.md#build-and-push-images) | [⤓][registry-source] | [⚙][registry-gdk] | CE & EE |
+| [Redis](#redis) | Caching service | [✅][redis-omnibus] | [✅][redis-omnibus] | [✅][redis-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⤓][redis-source] | ✅ | CE & EE |
+| [PostgreSQL](#postgresql) | Database | [✅][postgres-omnibus] | [✅][postgres-charts] | [✅][postgres-charts] | [✅](../user/gitlab_com/index.md#postgresql) | [⤓][postgres-source] | ✅ | CE & EE |
+| [PgBouncer](#pgbouncer) | Database connection pooling, failover | [⚙][pgbouncer-omnibus] | [❌][pgbouncer-charts] | [❌][pgbouncer-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#database-architecture) | ❌ | ❌ | EE Only |
+| [Consul](#consul) | Database node discovery, failover | [⚙][consul-omnibus] | [❌][consul-charts] | [❌][consul-charts] | [✅](../user/gitlab_com/index.md#consul) | ❌ | ❌ | EE Only |
+| [GitLab self-monitoring: Prometheus](#prometheus) | Time-series database, metrics collection, and query service | [✅][prometheus-omnibus] | [✅][prometheus-charts] | [⚙][prometheus-charts] | [✅](../user/gitlab_com/index.md#prometheus) | ❌ | ❌ | CE & EE |
+| [GitLab self-monitoring: Alertmanager](#alertmanager) | Deduplicates, groups, and routes alerts from Prometheus | [⚙][alertmanager-omnibus] | [✅][alertmanager-charts] | [⚙][alertmanager-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
+| [GitLab self-monitoring: Grafana](#grafana) | Metrics dashboard | [⚙][grafana-omnibus] | [⤓][grafana-charts] | [⤓][grafana-charts] | [✅](https://dashboards.gitlab.com/d/RZmbBr7mk/gitlab-triage?refresh=30s) | ❌ | ❌ | CE & EE |
+| [GitLab self-monitoring: Sentry](#sentry) | Track errors generated by the GitLab instance | [⤓][sentry-omnibus] | [❌][sentry-charts] | [❌][sentry-charts] | [✅](https://about.gitlab.com/handbook/support/workflows/services/gitlab_com/500_errors.html#searching-sentry) | [⤓][gitlab-yml] | [⤓][gitlab-yml] | CE & EE |
+| [GitLab self-monitoring: Jaeger](#jaeger) | View traces generated by the GitLab instance | [❌][jaeger-omnibus] | [❌][jaeger-charts] | [❌][jaeger-charts] | [❌](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104) | [⤓][jaeger-source] | [⚙][jaeger-gdk] | CE & EE |
+| [Redis Exporter](#redis-exporter) | Prometheus endpoint with Redis metrics | [✅][redis-exporter-omnibus] | [✅][redis-exporter-charts] | [✅][redis-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
+| [Postgres Exporter](#postgres-exporter) | Prometheus endpoint with PostgreSQL metrics | [✅][postgres-exporter-omnibus] | [✅][postgres-exporter-charts] | [✅][postgres-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
+| [PgBouncer Exporter](#pgbouncer-exporter) | Prometheus endpoint with PgBouncer metrics | [⚙][pgbouncer-exporter-omnibus] | [❌][pgbouncer-exporter-charts] | [❌][pgbouncer-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
+| [GitLab Monitor](#gitlab-monitor) | Generates a variety of GitLab metrics | [✅][gitlab-monitor-omnibus] | [❌][gitab-monitor-charts] | [❌][gitab-monitor-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
+| [Node Exporter](#node-exporter) | Prometheus endpoint with system metrics | [✅][node-exporter-omnibus] | [❌][node-exporter-charts] | [❌][node-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
+| [Mattermost](#mattermost) | Open-source Slack alternative | [⚙][mattermost-omnibus] | [⤓][mattermost-charts] | [⤓][mattermost-charts] | [⤓](../user/project/integrations/mattermost_slash_commands.md#manual-configuration), [⤓](../user/project/integrations/mattermost.html) | ❌ | ❌ | CE & EE |
+| [MinIO](#minio) | Object storage service | [⤓][minio-omnibus] | [✅][minio-charts] | [✅][minio-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#storage-architecture) | ❌ | [⚙][minio-gdk] | CE & EE |
+| [Runner](#gitlab-runner) | Executes GitLab CI jobs | [⤓][runner-omnibus] | [✅][runner-charts] | [⚙][runner-charts] | [✅](../user/gitlab_com/index.md#shared-runners) | [⚙][runner-source] | [⚙][runner-gdk] | CE & EE |
+| [Database Migrations](#database-migrations) | Database migrations | [✅][database-migrations-omnibus] | [✅][database-migrations-charts] | [✅][database-migrations-charts] | ✅ | [⚙][database-migrations-source] | ✅ | CE & EE |
+| [Certificate Management](#certificate-management) | TLS Settings, Let's Encrypt | [✅][certificate-management-omnibus] | [✅][certificate-management-charts] | [⚙][certificate-management-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#secrets-management) | [⚙][certificate-management-source] | [⚙][certificate-management-gdk] | CE & EE |
+| [GitLab Geo Node](#gitlab-geo) | Geographically distributed GitLab nodes | [⚙][geo-omnibus] | [❌][geo-charts] | [❌][geo-charts] | ✅ | ❌ | [⚙][geo-gdk] | EE Only |
+| [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 |
+| [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 |
### Component details
@@ -164,13 +183,14 @@ GitLab can be considered to have two layers from a process perspective:
- [Project page](https://github.com/prometheus/alertmanager/blob/master/README.md)
- Configuration: [Omnibus][alertmanager-omnibus], [Charts][alertmanager-charts]
- Layer: Monitoring
+- Process: `alertmanager`
[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by Prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue gitlab-ce#45740](https://gitlab.com/gitlab-org/gitlab-ce/issues/45740) about what we will be alerting on.
#### Certificate management
- Project page: [Omnibus](https://github.com/certbot/certbot/blob/master/README.rst), [Charts](https://github.com/jetstack/cert-manager/blob/master/README.md)
-- Configuration: [Omnibus][certificate-management-omnibus], [Charts][certificate-management-charts]
+- Configuration: [Omnibus][certificate-management-omnibus], [Charts][certificate-management-charts], [Source][certificate-management-source], [GDK][certificate-management-gdk]
- Layer: Core Service (Processor)
#### Consul
@@ -183,13 +203,13 @@ Consul is a tool for service discovery and configuration. Consul is distributed,
#### Database migrations
-- Configuration: [Omnibus][registry-omnibus], [Charts][registry-charts]
+- Configuration: [Omnibus][registry-omnibus], [Charts][registry-charts], [Source][database-migrations-source]
- Layer: Core Service (Data)
#### Elasticsearch
- [Project page](https://github.com/elastic/elasticsearch/blob/master/README.textile)
-- Configuration: [Omnibus][elasticsearch-omnibus], [Charts][elasticsearch-charts]
+- Configuration: [Omnibus][elasticsearch-omnibus], [Charts][elasticsearch-charts], [Source][elasticsearch-source], [GDK][elasticsearch-gdk]
- Layer: Core Service (Data)
Elasticsearch is a distributed RESTful search engine built for the cloud.
@@ -197,14 +217,15 @@ Elasticsearch is a distributed RESTful search engine built for the cloud.
#### Gitaly
- [Project page](https://gitlab.com/gitlab-org/gitaly/blob/master/README.md)
-- Configuration: [Omnibus][gitaly-omnibus], [Charts][gitaly-charts]
+- Configuration: [Omnibus][gitaly-omnibus], [Charts][gitaly-charts], [Source][gitaly-source]
- Layer: Core Service (Data)
+- Process: `gitaly`
Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab (think GitLab.com or High Availability Deployments). As of 11.3.0, this service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly).
#### Gitlab Geo
-- Configuration: [Omnibus][geo-omnibus], [Charts][geo-charts]
+- Configuration: [Omnibus][geo-omnibus], [Charts][geo-charts], [GDK][geo-gdk]
- Layer: Core Service (Processor)
#### Gitlab Monitor
@@ -212,12 +233,13 @@ Gitaly is a service designed by GitLab to remove our need for NFS for Git storag
- [Project page](https://gitlab.com/gitlab-org/gitlab-monitor)
- Configuration: [Omnibus][gitlab-monitor-omnibus], [Charts][gitlab-monitor-charts]
- Layer: Monitoring
+- Process: `gitlab-monitor`
GitLab Monitor is a process designed in house that allows us to export metrics about GitLab application internals to Prometheus. You can read more [in the project's readme](https://gitlab.com/gitlab-org/gitlab-monitor).
#### Gitlab Pages
-- Configuration: [Omnibus][pages-omnibus], [Charts][pages-charts]
+- Configuration: [Omnibus][pages-omnibus], [Charts][pages-charts], [Source][pages-source], [GDK][pages-gdk]
- Layer: Core Service (Processor)
GitLab Pages is a feature that allows you to publish static websites directly from a repository in GitLab.
@@ -227,7 +249,7 @@ You can use it either for personal or business websites, such as portfolios, doc
#### Gitlab Runner
- [Project page](https://gitlab.com/gitlab-org/gitlab-runner/blob/master/README.md)
-- Configuration: [Omnibus][runner-omnibus], [Charts][runner-charts]
+- Configuration: [Omnibus][runner-omnibus], [Charts][runner-charts], [Source][runner-source], [GDK][runner-gdk]
- Layer: Core Service (Processor)
GitLab Runner runs tests and sends the results to GitLab.
@@ -237,7 +259,7 @@ GitLab CI is the open-source continuous integration service included with GitLab
#### Gitlab Shell
- [Project page](https://gitlab.com/gitlab-org/gitlab-shell/blob/master/README.md)
-- Configuration: [Omnibus][shell-omnibus], [Charts][shell-charts]
+- Configuration: [Omnibus][shell-omnibus], [Charts][shell-charts], [Source][shell-source], [GDK][gitlab-yml]
- Layer: Core Service (Processor)
[GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell) is a program designed at GitLab to handle ssh-based `git` sessions, and modifies the list of authorized keys. GitLab Shell is not a Unix shell nor a replacement for Bash or Zsh.
@@ -245,8 +267,9 @@ GitLab CI is the open-source continuous integration service included with GitLab
#### Gitlab Workhorse
- [Project page](https://gitlab.com/gitlab-org/gitlab-workhorse/blob/master/README.md)
-- Configuration: [Omnibus][gitlab-workhorse-omnibus], [Charts][gitlab-workhorse-charts]
+- Configuration: [Omnibus][gitlab-workhorse-omnibus], [Charts][gitlab-workhorse-charts], [Source][workhorse-source]
- Layer: Core Service (Processor)
+- Process: `gitlab-workhorse`
[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) is a program designed at GitLab to help alleviate pressure from Unicorn. You can read more about the [historical reasons for developing](https://about.gitlab.com/2016/04/12/a-brief-history-of-gitlab-workhorse/). It's designed to act as a smart reverse proxy to help speed up GitLab as a whole.
@@ -261,7 +284,7 @@ Grafana is an open source, feature rich metrics dashboard and graph editor for G
#### Jaeger
- [Project page](https://github.com/jaegertracing/jaeger/blob/master/README.md)
-- Configuration: [Omnibus][jaeger-omnibus], [Charts][jaeger-charts]
+- Configuration: [Omnibus][jaeger-omnibus], [Charts][jaeger-charts], [Source][jaeger-source], [GDK][jaeger-gdk]
- Layer: Monitoring
Jaeger, inspired by Dapper and OpenZipkin, is a distributed tracing system. It can be used for monitoring microservices-based distributed systems.
@@ -271,6 +294,7 @@ Jaeger, inspired by Dapper and OpenZipkin, is a distributed tracing system. It c
- [Project page](https://github.com/logrotate/logrotate/blob/master/README.md)
- Configuration: [Omnibus](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate)
- Layer: Core Service
+- Process: `logrotate`
GitLab is comprised of a large number of services that all log. We started bundling our own logrotate as of 7.4 to make sure we were logging responsibly. This is just a packaged version of the common open source offering.
@@ -285,7 +309,7 @@ Mattermost is an open source, private cloud, Slack-alternative from https://matt
#### MinIO
- [Project page](https://github.com/minio/minio/blob/master/README.md)
-- Configuration: [Omnibus][minio-omnibus], [Charts][minio-charts]
+- Configuration: [Omnibus][minio-omnibus], [Charts][minio-charts], [GDK][minio-gdk]
- Layer: Core Service (Data)
MinIO is an object storage server released under Apache License v2.0. It is compatible with Amazon S3 cloud storage service. It is best suited for storing unstructured data such as photos, videos, log files, backups and container / VM images. Size of an object can range from a few KBs to a maximum of 5TB.
@@ -293,8 +317,9 @@ MinIO is an object storage server released under Apache License v2.0. It is comp
#### NGINX
- Project page: [Omnibus](https://github.com/nginx/nginx), [Charts](https://github.com/kubernetes/ingress-nginx/blob/master/README.md)
-- Configuration: [Omnibus][nginx-omnibus], [Charts][nginx-charts]
+- Configuration: [Omnibus][nginx-omnibus], [Charts][nginx-charts], [Source][nginx-source]
- Layer: Core Service (Processor)
+- Process: `nginx`
Nginx as an ingress port for all HTTP requests and routes them to the approriate sub-systems within GitLab. We are bundling an unmodified version of the popular open source webserver.
@@ -303,6 +328,7 @@ Nginx as an ingress port for all HTTP requests and routes them to the approriate
- [Project page](https://github.com/prometheus/node_exporter/blob/master/README.md)
- Configuration: [Omnibus][node-exporter-omnibus], [Charts][node-exporter-charts]
- Layer: Monitoring
+- Process: `node-exporter`
[Node Exporter](https://github.com/prometheus/node_exporter) is a Prometheus tool that gives us metrics on the underlying machine (think CPU/Disk/Load). It's just a packaged version of the common open source offering from the Prometheus project.
@@ -325,8 +351,9 @@ Prometheus exporter for PgBouncer. Exports metrics at 9127/metrics.
#### Postgresql
- [Project page](https://github.com/postgres/postgres/blob/master/README)
-- Configuration: [Omnibus][postgres-omnibus], [Charts][postgres-charts]
+- Configuration: [Omnibus][postgres-omnibus], [Charts][postgres-charts], [Source][postgres-source]
- Layer: Core Service (Data)
+- Process: `postgresql`
GitLab packages the popular Database to provide storage for Application meta data and user information.
@@ -335,6 +362,7 @@ GitLab packages the popular Database to provide storage for Application meta dat
- [Project page](https://github.com/wrouesnel/postgres_exporter/blob/master/README.md)
- Configuration: [Omnibus][postgres-exporter-omnibus], [Charts][postgres-exporter-charts]
- Layer: Monitoring
+- Process: `postgres-exporter`
[Postgres-exporter](https://github.com/wrouesnel/postgres_exporter) is the community provided Prometheus exporter that will deliver data about Postgres to Prometheus for use in Grafana Dashboards.
@@ -343,14 +371,16 @@ GitLab packages the popular Database to provide storage for Application meta dat
- [Project page](https://github.com/prometheus/prometheus/blob/master/README.md)
- Configuration: [Omnibus][prometheus-omnibus], [Charts][prometheus-charts]
- Layer: Monitoring
+- Process: `prometheus`
Prometheus is a time-series tool that helps GitLab administrators expose metrics about the individual processes used to provide GitLab the service.
#### Redis
- [Project page](https://github.com/antirez/redis/blob/unstable/README.md)
-- Configuration: [Omnibus][redis-omnibus], [Charts][redis-charts]
+- Configuration: [Omnibus][redis-omnibus], [Charts][redis-charts], [Source][redis-source]
- Layer: Core Service (Data)
+- Process: `redis`
Redis is packaged to provide a place to store:
@@ -363,13 +393,14 @@ Redis is packaged to provide a place to store:
- [Project page](https://github.com/oliver006/redis_exporter/blob/master/README.md)
- Configuration: [Omnibus][redis-exporter-omnibus], [Charts][redis-exporter-charts]
- Layer: Monitoring
+- Process: `redis-exporter`
[Redis Exporter](https://github.com/oliver006/redis_exporter) is designed to give specific metrics about the Redis process to Prometheus so that we can graph these metrics in Grafana.
#### Registry
- [Project page](https://github.com/docker/distribution/blob/master/README.md)
-- Configuration: [Omnibus][registry-omnibus], [Charts][registry-charts]
+- Configuration: [Omnibus][registry-omnibus], [Charts][registry-charts], [Source][registry-source], [GDK][registry-gdk]
- Layer: Core Service (Processor)
The registry is what users use to store their own Docker images. The bundled
@@ -385,7 +416,7 @@ An external registry can also be configured to use GitLab as an auth endpoint.
#### Sentry
- [Project page](https://github.com/getsentry/sentry/blob/master/README.rst)
-- Configuration: [Omnibus][sentry-omnibus], [Charts][sentry-charts]
+- Configuration: [Omnibus][sentry-omnibus], [Charts][sentry-charts], [Source][gitlab-yml], [GDK][gitlab-yml]
- Layer: Monitoring
Sentry fundamentally is a service that helps you monitor and fix crashes in realtime. The server is in Python, but it contains a full API for sending events from any language, in any application.
@@ -393,40 +424,42 @@ Sentry fundamentally is a service that helps you monitor and fix crashes in real
#### Sidekiq
- [Project page](https://github.com/mperham/sidekiq/blob/master/README.md)
-- Configuration: [Omnibus][sidekiq-omnibus], [Charts][sidekiq-charts]
+- Configuration: [Omnibus][sidekiq-omnibus], [Charts][sidekiq-charts], [Source][gitlab-yml], [GDK][gitlab-yml]
- Layer: Core Service (Processor)
+- Process: `sidekiq`
Sidekiq is a Ruby background job processor that pulls jobs from the redis queue and processes them. Background jobs allow GitLab to provide a faster request/response cycle by moving work into the background.
#### Unicorn
- [Project page](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/README.md)
-- Configuration: [Omnibus][unicorn-omnibus], [Charts][unicorn-charts]
+- Configuration: [Omnibus][unicorn-omnibus], [Charts][unicorn-charts], [Source][unicorn-source], [GDK][gitlab-yml]
- Layer: Core Service (Processor)
+- Process: `unicorn`
[Unicorn](https://bogomips.org/unicorn/) is a Ruby application server that is used to run the core Rails Application that provides the user facing features in GitLab. Often process output you will see this as `bundle` or `config.ru` depending on the GitLab version.
#### LDAP Authentication
-- Configuration: [Omnibus][ldap-omnibus], [Charts][ldap-charts]
+- Configuration: [Omnibus][ldap-omnibus], [Charts][ldap-charts], [Source][gitlab-yml], [GDK][ldap-gdk]
- Layer: Core Service (Processor)
#### Outbound Email
-- Configuration: [Omnibus][outbound-email-omnibus], [Charts][outbound-email-charts]
+- Configuration: [Omnibus][outbound-email-omnibus], [Charts][outbound-email-charts], [Source][gitlab-yml], [GDK][gitlab-yml]
- Layer: Core Service (Processor)
#### Inbound Email
-- Configuration: [Omnibus][inbound-email-omnibus], [Charts][inbound-email-charts]
+- Configuration: [Omnibus][inbound-email-omnibus], [Charts][inbound-email-charts], [Source][gitlab-yml], [GDK][gitlab-yml]
- Layer: Core Service (Processor)
-#### Kubernetes Cluster Apps
+#### GitLab Managed Apps
-- Configuration: [Omnibus][managed-k8s-apps], [Charts][managed-k8s-apps]
+- Configuration: [Omnibus][managed-k8s-apps], [Charts][managed-k8s-apps], [Source][managed-k8s-apps], [GDK][managed-k8s-apps]
- Layer: Core Service (Processor)
-GitLab provides [GitLab Managed Apps](https://docs.gitlab.com/ee/user/project/clusters/#installing-applications), a one-click install for various applications which can be added directly to your configured cluster. These applications are needed for Review Apps and deployments when using Auto DevOps. You can install them after you create a cluster.
+GitLab provides [GitLab Managed Apps](../user/project/clusters/index.md#installing-applications), a one-click install for various applications which can be added directly to your configured cluster. These applications are needed for Review Apps and deployments when using Auto DevOps. You can install them after you create a cluster.
## GitLab by Request Type
@@ -595,68 +628,92 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha
[alertmanager-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template
[alertmanager-charts]: https://github.com/helm/charts/tree/master/stable/prometheus
-[nginx-omnibus]: https://docs.gitlab.com/omnibus/settings/nginx.html
-[nginx-charts]: https://docs.gitlab.com/charts/charts/nginx/index.html
+[nginx-omnibus]: https://docs.gitlab.com/omnibus/settings/
+[nginx-charts]: https://docs.gitlab.com/charts/charts/nginx/
+[nginx-source]: ../install/installation.md#9-nginx
[unicorn-omnibus]: https://docs.gitlab.com/omnibus/settings/unicorn.html
-[unicorn-charts]: https://docs.gitlab.com/charts/charts/gitlab/unicorn/index.html
+[unicorn-charts]: https://docs.gitlab.com/charts/charts/gitlab/unicorn/
+[unicorn-source]: ../install/installation.md#configure-it
+[gitlab-yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
[sidekiq-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template
-[sidekiq-charts]: https://docs.gitlab.com/charts/charts/gitlab/sidekiq/index.html
-[gitaly-omnibus]: https://docs.gitlab.com/ee/administration/gitaly/
-[gitaly-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitaly/index.html
+[sidekiq-charts]: https://docs.gitlab.com/charts/charts/gitlab/sidekiq/
+[gitaly-omnibus]: ../administration/gitaly/index.md
+[gitaly-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitaly/
+[gitaly-source]: ../install/installation.md#install-gitaly
[workhorse-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template
-[workhorse-charts]: https://docs.gitlab.com/charts/charts/gitlab/unicorn/index.html
+[workhorse-charts]: https://docs.gitlab.com/charts/charts/gitlab/unicorn/
+[workhorse-source]: ../install/installation.md#install-gitlab-workhorse
[shell-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template
-[shell-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitlab-shell/index.html
-[pages-omnibus]: https://docs.gitlab.com/ee/administration/pages/
+[shell-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitlab-shell/
+[shell-source]: ../install/installation.md#install-gitlab-shell
+[pages-omnibus]: ../administration/pages/index.md
[pages-charts]: https://gitlab.com/charts/gitlab/issues/37
-[registry-omnibus]: https://docs.gitlab.com/ee/administration/container_registry.html#container-registry-domain-configuration
-[registry-charts]: https://docs.gitlab.com/charts/charts/registry/index.html
+[pages-source]: ../install/installation.md#install-gitlab-pages
+[pages-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/pages.md
+[registry-omnibus]: ../administration/container_registry.md#container-registry-domain-configuration
+[registry-charts]: https://docs.gitlab.com/charts/charts/registry/
+[registry-source]: ../administration/container_registry.md#enable-the-container-registry
+[registry-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/registry.md
[redis-omnibus]: https://docs.gitlab.com/omnibus/settings/redis.html
-[redis-charts]: https://docs.gitlab.com/charts/charts/redis/index.html
+[redis-charts]: https://docs.gitlab.com/charts/charts/redis/
+[redis-source]: ../install/installation.md#7-redis
[postgres-omnibus]: https://docs.gitlab.com/omnibus/settings/database.html
[postgres-charts]: https://github.com/helm/charts/tree/master/stable/postgresql
-[pgbouncer-omnibus]: https://docs.gitlab.com/ee/administration/high_availability/pgbouncer.html
+[postgres-source]: ../install/installation.md#6-database
+[pgbouncer-omnibus]: ../administration/high_availability/pgbouncer.md
[pgbouncer-charts]: https://docs.gitlab.com/charts/installation/deployment.html#postgresql
-[consul-omnibus]: https://docs.gitlab.com/ee/administration/high_availability/consul.html
+[consul-omnibus]: ../administration/high_availability/consul.md
[consul-charts]: https://docs.gitlab.com/charts/installation/deployment.html#postgresql
-[prometheus-omnibus]: https://docs.gitlab.com/ee/administration/monitoring/prometheus/
+[prometheus-omnibus]: ../administration/monitoring/prometheus/index.md
[prometheus-charts]: https://github.com/helm/charts/tree/master/stable/prometheus
-[grafana-omnibus]: https://docs.gitlab.com/ee/administration/monitoring/performance/grafana_configuration.html
+[grafana-omnibus]: ../administration/monitoring/performance/grafana_configuration.md
[grafana-charts]: https://github.com/helm/charts/tree/master/stable/grafana
[sentry-omnibus]: https://docs.gitlab.com/omnibus/settings/configuration.html#error-reporting-and-logging-with-sentry
[sentry-charts]: https://gitlab.com/charts/gitlab/issues/1319
[jaeger-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104
[jaeger-charts]: https://gitlab.com/charts/gitlab/issues/1320
-[redis-exporter-omnibus]: https://docs.gitlab.com/ee/administration/monitoring/prometheus/redis_exporter.html
-[redis-exporter-charts]: https://docs.gitlab.com/charts/charts/redis/index.html
-[postgres-exporter-omnibus]: https://docs.gitlab.com/ee/administration/monitoring/prometheus/postgres_exporter.html
+[jaeger-source]: ../development/distributed_tracing.md#enabling-distributed-tracing
+[jaeger-gdk]: ../development/distributed_tracing.html#using-jaeger-in-the-gitlab-development-kit
+[redis-exporter-omnibus]: ../administration/monitoring/prometheus/redis_exporter.md
+[redis-exporter-charts]: https://docs.gitlab.com/charts/charts/redis/
+[postgres-exporter-omnibus]: ../administration/monitoring/prometheus/postgres_exporter.md
[postgres-exporter-charts]: https://github.com/helm/charts/tree/master/stable/postgresql
-[pgbouncer-exporter-omnibus]: https://docs.gitlab.com/ee/administration/monitoring/prometheus/pgbouncer_exporter.html
+[pgbouncer-exporter-omnibus]: ../administration/monitoring/prometheus/pgbouncer_exporter.md
[pgbouncer-exporter-charts]: https://docs.gitlab.com/charts/installation/deployment.html#postgresql
-[gitlab-monitor-omnibus]: https://docs.gitlab.com/ee/administration/monitoring/prometheus/gitlab_monitor_exporter.html
+[gitlab-monitor-omnibus]: ../administration/monitoring/prometheus/gitlab_monitor_exporter.md
[gitab-monitor-charts]: https://gitlab.com/charts/gitlab/issues/319
-[node-exporter-omnibus]: https://docs.gitlab.com/ee/administration/monitoring/prometheus/node_exporter.html
+[node-exporter-omnibus]: ../administration/monitoring/prometheus/node_exporter.md
[node-exporter-charts]: https://gitlab.com/charts/gitlab/issues/1332
[mattermost-omnibus]: https://docs.gitlab.com/omnibus/gitlab-mattermost/
[mattermost-charts]: https://docs.mattermost.com/install/install-mmte-helm-gitlab-helm.html
[minio-omnibus]: https://min.io/download
-[minio-charts]: https://docs.gitlab.com/charts/charts/minio/index.html
+[minio-charts]: https://docs.gitlab.com/charts/charts/minio/
+[minio-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/object_storage.md
[runner-omnibus]: https://docs.gitlab.com/runner/
[runner-charts]: https://docs.gitlab.com/runner/install/kubernetes.html
+[runner-source]: https://docs.gitlab.com/runner/
+[runner-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/runner.md
[database-migrations-omnibus]: https://docs.gitlab.com/omnibus/settings/database.html#disabling-automatic-database-migration
-[database-migrations-charts]: https://docs.gitlab.com/charts/charts/gitlab/migrations/index.html
+[database-migrations-charts]: https://docs.gitlab.com/charts/charts/gitlab/migrations/
+[database-migrations-source]: ../update/upgrading_from_source.md#14-install-libs-migrations-etc
[certificate-management-omnibus]: https://docs.gitlab.com/omnibus/settings/ssl.html
[certificate-management-charts]: https://docs.gitlab.com/charts/installation/tls.html
-[geo-omnibus]: https://docs.gitlab.com/ee/administration/geo/replication/index.html#setup-instructions
+[certificate-management-source]: ../install/installation.md#using-https
+[certificate-management-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/https.md
+[geo-omnibus]: ../administration/geo/replication/index.md#setup-instructions
[geo-charts]: https://gitlab.com/charts/gitlab/issues/8
-[ldap-omnibus]: https://docs.gitlab.com/ee/administration/auth/ldap.html
+[geo-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/geo.md
+[ldap-omnibus]: ../administration/auth/ldap.md
[ldap-charts]: https://docs.gitlab.com/charts/charts/globals.html#ldap
+[ldap-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/ldap.md
[outbound-email-omnibus]: https://docs.gitlab.com/omnibus/settings/smtp.html
[outbound-email-charts]: https://docs.gitlab.com/charts/installation/command-line-options.html#outgoing-email-configuration
-[inbound-email-omnibus]: https://docs.gitlab.com/ee/administration/incoming_email.html
+[inbound-email-omnibus]: ../administration/incoming_email.md
[inbound-email-charts]: https://docs.gitlab.com/charts/installation/command-line-options.html#incoming-email-configuration
-[elasticsearch-omnibus]: https://docs.gitlab.com/ee/integration/elasticsearch.html
-[elasticsearch-charts]: https://docs.gitlab.com/ee/integration/elasticsearch.html
-[sentry-integration]: https://docs.gitlab.com/ee/user/project/operations/error_tracking.html
-[jaeger-integration]: https://docs.gitlab.com/ee/user/project/operations/tracing.html
-[managed-k8s-apps]: https://docs.gitlab.com/ee/user/project/clusters/#installing-applications
+[elasticsearch-omnibus]: ../integration/elasticsearch.md
+[elasticsearch-charts]: ../integration/elasticsearch.md
+[elasticsearch-source]: ../integration/elasticsearch.md
+[elasticsearch-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/elasticsearch.md
+[sentry-integration]: ../user/project/operations/error_tracking.md
+[jaeger-integration]: ../user/project/operations/tracing.md
+[managed-k8s-apps]: ../user/project/clusters/index.md#installing-applications
diff --git a/doc/development/chatops_on_gitlabcom.md b/doc/development/chatops_on_gitlabcom.md
new file mode 100644
index 00000000000..c63ec53414c
--- /dev/null
+++ b/doc/development/chatops_on_gitlabcom.md
@@ -0,0 +1,21 @@
+# Chatops on GitLab.com
+
+Chatops on GitLab.com allows GitLabbers to run various automation tasks on GitLab.com using Slack.
+
+## Requesting access
+
+GitLabbers may need access to Chatops on GitLab.com for administration tasks such as:
+
+- Configuring feature flags on staging.
+- Running `EXPLAIN` queries against the GitLab.com production replica.
+
+To request access to Chatops on GitLab.com:
+
+1. Log into <https://ops.gitlab.net/users/sign_in> using the same username as for GitLab.com.
+1. Ask [anyone in the `chatops` project](https://gitlab.com/gitlab-com/chatops/project_members) to add you by running `/chatops run member add <username> gitlab-com/chatops --ops`.
+
+## See also
+
+ - [Chatops Usage](https://docs.gitlab.com/ee/ci/chatops/README.html)
+ - [Understanding EXPLAIN plans](understanding_explain_plans.md)
+ - [Feature Groups](feature_flags.md#feature-groups)
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index c4e5995714d..29e2aa1a581 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -27,7 +27,7 @@ Depending on the areas your merge request touches, it must be **approved** by on
or more [maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer):
For approvals, we use the approval functionality found in the merge request
-widget. Reviewers can add their approval by [approving additionally](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#adding-or-removing-an-approval).
+widget. Reviewers can add their approval by [approving additionally](../user/project/merge_requests/merge_request_approvals.md#adding-or-removing-an-approval).
Getting your merge request **merged** also requires a maintainer. If it requires
more than one approval, the last maintainer to review and approve it will also merge it.
@@ -43,7 +43,7 @@ It picks reviewers and maintainers from the list at the
[engineering projects](https://about.gitlab.com/handbook/engineering/projects/)
page, with these behaviours:
-1. It will not pick people whose [GitLab status](../user/profile/#current-status)
+1. It will not pick people whose [GitLab status](../user/profile/index.md#current-status)
contains the string 'OOO'.
2. [Trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer)
are three times as likely to be picked as other reviewers.
@@ -152,7 +152,7 @@ required approvers.
Maintainers must check before merging if the merge request is introducing new
vulnerabilities, by inspecting the list in the Merge Request [Security
-Widget](https://docs.gitlab.com/ee/user/project/merge_requests/#security-reports-ultimate).
+Widget](../user/project/merge_requests/index.md#security-reports-ultimate).
When in doubt, a [Security Engineer][team] can be involved. The list of detected
vulnerabilities must be either empty or containing:
@@ -286,7 +286,7 @@ experience, refactors the existing code). Then:
author has already set this option or if the merge request clearly contains a
messy commit history that is intended to be squashed.
-[squash-and-merge]: https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html#squash-and-merge
+[squash-and-merge]: ../user/project/merge_requests/squash_and_merge.md#squash-and-merge
### The right balance
@@ -319,7 +319,7 @@ reviewee.
GitLab is used in a lot of places. Many users use
our [Omnibus packages](https://about.gitlab.com/installation/), but some use
the [Docker images](https://docs.gitlab.com/omnibus/docker/), some are
-[installed from source](https://docs.gitlab.com/ce/install/installation.html),
+[installed from source](../install/installation.md),
and there are other installation methods available. GitLab.com itself is a large
Enterprise Edition instance. This has some implications:
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index 8b1d014e101..59cf5014da4 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -87,7 +87,7 @@ Sometimes style guides will be followed but the code will lack structural integr
GitLab will do its best to review community contributions as quickly as possible. Specially appointed developers review community contributions daily. You may take a look at the [team page](https://about.gitlab.com/team/) for the merge request coach who specializes in the type of code you have written and mention them in the merge request. For example, if you have written some JavaScript in your code then you should mention the frontend merge request coach. If your code has multiple disciplines you may mention multiple merge request coaches.
-GitLab receives a lot of community contributions, so if your code has not been reviewed within 4 days of its initial submission feel free to re-mention the appropriate merge request coach.
+GitLab receives a lot of community contributions, so if your code has not been reviewed within two days (excluding weekend and public holidays) of its initial submission feel free to re-mention the appropriate merge request coach.
When submitting code to GitLab, you may feel that your contribution requires the aid of an external library. If your code includes an external library please provide a link to the library, as well as reasons for including it.
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index fef94c0a4b5..e3a1dc711fd 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -7,7 +7,7 @@ scheduling into milestones. Labelling is a task for everyone.
Most issues will have labels for at least one of the following:
- Type: ~feature, ~bug, ~customer, etc.
-- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
+- Subject: ~wiki, ~"Container Registry", ~ldap, ~api, ~frontend, etc.
- Team: ~Plan, ~Manage, ~Quality, etc.
- Stage: ~"devops:plan", ~"devops:create", etc.
- Release Scoping: ~Deliverable, ~Stretch, ~"Next Patch Release"
@@ -44,7 +44,7 @@ Subject labels are labels that define what area or feature of GitLab this issue
hits. They are not always necessary, but very convenient.
Examples of subject labels are ~wiki, ~ldap, ~api,
-~issues, ~"merge requests", ~labels, and ~"container registry".
+~issues, ~"merge requests", ~labels, and ~"Container Registry".
If you are an expert in a particular area, it makes it easier to find issues to
work on. You can also subscribe to those labels to receive an email each time an
@@ -92,20 +92,21 @@ Stage labels specify which [DevOps stage][devops-stages] the issue belongs to.
The current stage labels are:
-- ~"devops:manage"
-- ~"devops:plan"
-- ~"devops:create"
-- ~"devops:verify"
-- ~"devops:package"
-- ~"devops:release"
-- ~"devops:configure"
-- ~"devops:monitor"
-- ~"devops:secure"
-- ~"devops:defend"
-- ~"devops:enablement"
-
-These labels should be mutually exclusive. If an issue belongs to multiple
-stages, the most relevant should be used.
+- ~"devops::manage"
+- ~"devops::plan"
+- ~"devops::create"
+- ~"devops::verify"
+- ~"devops::package"
+- ~"devops::release"
+- ~"devops::configure"
+- ~"devops::monitor"
+- ~"devops::secure"
+- ~"devops::defend"
+- ~"devops::growth"
+- ~"devops::enablement"
+
+These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
+and thus are mutually exclusive.
They differ from the [Team labels](#team-labels) because teams may work on
issues outside their stage.
@@ -127,20 +128,18 @@ Group labels specify which [groups][structure-groups] the issue belongs to.
Examples include:
-- ~"group:control"
-- ~"group:editor"
+- ~"group::control"
+- ~"group::editor"
-These labels should be mutually exclusive. If an issue belongs to multiple
-groups, the most relevant should be used.
+These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
+and thus are mutually exclusive.
Groups are nested beneath a particular stage, so only one stage label and one group label
-should be applied to a single issue. You can find the groups listed in the
-[Product Categories pages][product-categories]. For example, ~"group:control" and
-~"group:framework" labels can be applied to issues related to the [Manage stage][manage-stage].
+can be applied to a single issue. You can find the groups listed in the
+[Product Categories pages][product-categories].
[structure-groups]: https://about.gitlab.com/company/team/structure/#groups
[product-categories]: https://about.gitlab.com/handbook/product/categories/
-[manage-stage]: https://about.gitlab.com/handbook/product/categories/#manage-stage
## Release Scoping labels
diff --git a/doc/development/database_debugging.md b/doc/development/database_debugging.md
index f00c5ccb9e9..68d33a9d8e0 100644
--- a/doc/development/database_debugging.md
+++ b/doc/development/database_debugging.md
@@ -56,7 +56,7 @@ bundle exec rails db RAILS_ENV=development
### `ActiveRecord::PendingMigrationError` with Spring
-When running specs with the [Spring preloader](./rake_tasks.md#speed-up-tests-rake-tasks-and-migrations),
+When running specs with the [Spring preloader](rake_tasks.md#speed-up-tests-rake-tasks-and-migrations),
the test database can get into a corrupted state. Trying to run the migration or
dropping/resetting the test database has no effect.
diff --git a/doc/development/distributed_tracing.md b/doc/development/distributed_tracing.md
index 038e3de10d7..bfce7488a8d 100644
--- a/doc/development/distributed_tracing.md
+++ b/doc/development/distributed_tracing.md
@@ -34,7 +34,7 @@ GITLAB_TRACING=opentracing://<driver>?<param_name>=<param_value>&<param_name_2>=
In this example, we have the following hypothetical values:
- `driver`: the driver. [GitLab supports
- `jaeger`](https://docs.gitlab.com/ee/user/project/operations/tracing.html). In future, other
+ `jaeger`](../user/project/operations/tracing.md). In future, other
tracing implementations may also be supported.
- `param_name`, `param_value`: these are driver specific configuration values. Configuration
parameters for Jaeger are documented [further on in this
diff --git a/doc/development/documentation/feature-change-workflow.md b/doc/development/documentation/feature-change-workflow.md
index 1f68b6a6a70..ca29353ecbe 100644
--- a/doc/development/documentation/feature-change-workflow.md
+++ b/doc/development/documentation/feature-change-workflow.md
@@ -86,7 +86,7 @@ Everyone is encouraged to draft the requirements in the issue, but a product man
do the following:
- When the issue is assigned a release milestone, review and update the Documentation details.
-- By the kickoff, finalizie the Documentation details.
+- By the kickoff, finalize the Documentation details.
### Developer and maintainer roles
@@ -117,7 +117,7 @@ Follow the process below unless otherwise agreed with the product manager and te
#### Reviews and merging
-All reviewers can help ensure accuracy, clarity, completeness, and adherence to the plans in the issue, as well as the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html).
+All reviewers can help ensure accuracy, clarity, completeness, and adherence to the plans in the issue, as well as the [Documentation Guidelines](index.md) and [Style Guide](styleguide.md).
- **Prior to merging**, documentation changes committed by the developer must be reviewed by:
@@ -136,7 +136,7 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
1. **The maintainer** who is assigned to merge the MR, to verify clarity, completeness, and quality, to the best of their ability.
- Upon merging, if a technical writer review has not been performed and there is not yet a linked issue for a follow-up review, the maintainer should [create an issue using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review), link it from the MR, and
- mention the original MR author in the new issue. Alternatively, the mainitainer can ask the MR author to create and link this issue before the MR is merged.
+ mention the original MR author in the new issue. Alternatively, the maintainer can ask the MR author to create and link this issue before the MR is merged.
- After merging, documentation changes are reviewed by:
@@ -157,14 +157,14 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
#### Collaboration
By default, the developer will work on documentation changes independently, but
-the developer, PM, or technicial writer can propose a broader collaboration for
+the developer, PM, or technical writer can propose a broader collaboration for
any given issue.
Additionally, technical writers are available for questions at any time.
#### Review
-- Techncial writers provide non-blocking reviews of all documentation changes,
+- Technical writers provide non-blocking reviews of all documentation changes,
before or after the change is merged. However, if the docs are ready in the MR while
there's time before the freeze, the technical writer's review can commence early, on request.
- The technical writer will confirm that the doc is clear, grammatically correct,
@@ -173,7 +173,7 @@ Additionally, technical writers are available for questions at any time.
the developer and code reviewer should have already made a good-faith effort to ensure:
- Clarity.
- Adherence to the plans and goals in the issue.
- - Location (make sure the docs are in the correct directorkes and has the correct name).
+ - Location (make sure the docs are in the correct directories and has the correct name).
- Syntax, typos, and broken links.
- Improvements to the content.
- Accordance with the [Documentation Style Guide](styleguide.md), and [Structure and Template](structure.md) doc.
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 4bf8401c0e8..c7fa40af930 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -74,9 +74,14 @@ Here are some links to get you up to speed with the current effort:
After a given documentation path is aligned across CE and EE, all merge requests
affecting that path must be submitted to CE, regardless of the content it has.
-This means that for EE-only features which are being added only to the EE codebase,
-you have to submit a separate merge request in CE that contains the docs.
-This might seem like a duplicate effort, but it's for the short term.
+This means that:
+
+* For **EE-only docs changes**, you only have to submit a CE MR.
+* For **EE-only features** that touch both the code and the docs, you have to submit
+an EE MR containing all changes, and a CE MR containing only the docs changes
+and without a changelog entry.
+
+This might seem like a duplicate effort, but it's only for the short term.
A list of the already aligned docs can be found in
[the epic description](https://gitlab.com/groups/gitlab-org/-/epics/199#ee-specific-lines-check).
@@ -466,7 +471,7 @@ If you want to know the in-depth details, here's what's really happening:
The following GitLab features are used among others:
- [Manual actions](../../ci/yaml/README.md#whenmanual)
-- [Multi project pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html)
+- [Multi project pipelines](../../ci/multi_project_pipeline_graphs.md)
- [Review Apps](../../ci/review_apps/index.md)
- [Artifacts](../../ci/yaml/README.md#artifacts)
- [Specific Runner](../../ci/runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects)
diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md
index 95b5fcd99a1..fe676efa94d 100644
--- a/doc/development/documentation/structure.md
+++ b/doc/development/documentation/structure.md
@@ -77,8 +77,8 @@ for use (e.g. variations on the main use case), but if that's not applicable, th
Examples of use cases on feature pages:
- CE and EE: [Issues](../../user/project/issues/index.md#use-cases)
- CE and EE: [Merge Requests](../../user/project/merge_requests/index.md)
-- EE-only: [Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html)
-- EE-only: [Jenkins integration](https://docs.gitlab.com/ee/integration/jenkins.html)
+- EE-only: [Geo](../../administration/geo/replication/index.md)
+- EE-only: [Jenkins integration](../../integration/jenkins.md)
## Requirements
@@ -121,8 +121,8 @@ but commented out to help encourage others to add to it in the future. -->
Notes:
-- (1): Apply the [tier badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) accordingly
-- (2): Apply the correct format for the [GitLab version introducing the feature](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers)
+- (1): Apply the [tier badges](styleguide.md#product-badges) accordingly
+- (2): Apply the correct format for the [GitLab version introducing the feature](styleguide.md#gitlab-versions-and-tiers)
```
## Help and feedback section
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index bc472bb5b0a..cca52706ddc 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -459,15 +459,6 @@ Resolving an EE template path that is relative to the CE view path will not work
= render_if_exists 'projects/button' # Will render `ee/app/views/projects/_button`
```
-You should not explicitly set render options like `partial` or provide a `locals` hash.
-The first argument should be a path string and the second can be a hash replacing `locals`.
-
-```ruby
-render partial: 'projects/button', locals: { project: project }
-# becomes
-render_if_exists 'projects/button', project: project
-```
-
#### Using `render_ce`
For `render` and `render_if_exists`, they search for the EE partial first,
@@ -557,40 +548,56 @@ due to `prepend`, but Grape is complex internally and we couldn't easily do
that, so we'll follow regular object-oriented practices that we define the
interface first here.
-For example, suppose we have a few more optional params for EE, given this CE
-API code:
+For example, suppose we have a few more optional params for EE. We can move the
+params out of the `Grape::API` class to a helper module, so we can `prepend` it
+before it would be used in the class.
```ruby
module API
- class MergeRequests < Grape::API
- # EE::API::MergeRequests would override the following helpers
- helpers do
- params :optional_params_ee do
+ class Projects < Grape::API
+ helpers Helpers::ProjectsHelpers
+ end
+end
+```
+
+Given this CE API `params`:
+
+```ruby
+module API
+ module Helpers
+ module ProjectsHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_project_params_ce do
+ # CE specific params go here...
end
- end
- params :optional_params do
- # CE specific params go here...
+ params :optional_project_params_ee do
+ end
- use :optional_params_ee
+ params :optional_project_params do
+ use :optional_project_params_ce
+ use :optional_project_params_ee
+ end
end
end
end
-API::MergeRequests.prepend(EE::API::MergeRequests)
+API::Helpers::ProjectsHelpers.prepend(EE::API::Helpers::ProjectsHelpers)
```
-And then we could override it in EE module:
+We could override it in EE module:
```ruby
module EE
module API
- module MergeRequests
- extend ActiveSupport::Concern
+ module Helpers
+ module ProjectsHelpers
+ extend ActiveSupport::Concern
- prepended do
- helpers do
- params :optional_params_ee do
+ prepended do
+ params :optional_project_params_ee do
# EE specific params go here...
end
end
@@ -600,9 +607,6 @@ module EE
end
```
-This way, the only difference between CE and EE for that API file would be
-`prepend EE::API::MergeRequests`.
-
#### EE helpers
To make it easy for an EE module to override the CE helpers, we need to define
@@ -902,7 +906,7 @@ import bundle from 'ee/protected_branches/protected_branches_bundle.js';
import bundle from 'ee_else_ce/protected_branches/protected_branches_bundle.js';
```
-See the frontend guide [performance section](./fe_guide/performance.md) for
+See the frontend guide [performance section](fe_guide/performance.md) for
information on managing page-specific javascript within EE.
@@ -933,7 +937,7 @@ export default {
```
#### For JS code that is EE only, like props, computed properties, methods, etc, we will keep the current approach
- - Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](https://docs.gitlab.com/ee/development/ee_features.html#javascript-code-in-assetsjavascripts) alias we already have for webpack.
+ - Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) alias we already have for webpack.
- This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the `ee/` folder and we need to create a CE counterpart of the mixin
##### Example:
@@ -960,7 +964,7 @@ import mixin from 'ee_else_ce/path/mixin';
### Non Vue Files
For regular JS files, the approach is similar.
-1. We will keep using the [`ee_else_ce`](https://docs.gitlab.com/ee/development/ee_features.html#javascript-code-in-assetsjavascripts) helper, this means that EE only code should be inside the `ee/` folder.
+1. We will keep using the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) helper, this means that EE only code should be inside the `ee/` folder.
1. An EE file should be created with the EE only code, and it should extend the CE counterpart.
1. For code inside functions that can't be extended, the code should be moved into a new file and we should use `ee_else_ce` helper:
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 0c9e7908713..8b0f4f02d19 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -2,7 +2,7 @@
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 https://docs.gitlab.com/ee/integration/elasticsearch.html#enabling-elasticsearch
+Information on how to enable ElasticSearch and perform the initial indexing is kept in ../integration/elasticsearch.md#enabling-elasticsearch
## Initial installation on OS X
@@ -16,7 +16,7 @@ and use `docker stop elastic56` and `docker start elastic56` to stop/start it.
### Installing on the host
-We currently only support Elasticsearch [5.6 to 6.x](https://docs.gitlab.com/ee/integration/elasticsearch.html#requirements)
+We currently only support Elasticsearch [5.6 to 6.x](../integration/elasticsearch.md#version-requirements)
Version 5.6 is available on homebrew and is the recommended version to use in order to test compatibility.
@@ -43,7 +43,7 @@ this adds `gitlab-elasticsearch-indexer` to `$GOPATH/bin`, please make sure that
- `gitlab:elastic:test:index_size`: Tells you how much space the current index is using, as well as how many documents are in the index.
- `gitlab:elastic:test:index_size_change`: Outputs index size, reindexes, and outputs index size again. Useful when testing improvements to indexing size.
-Additionally, if you need large repos or multiple forks for testing, please consider [following these instructions](https://docs.gitlab.com/ee/development/rake_tasks.html#extra-project-seed-options)
+Additionally, if you need large repos or multiple forks for testing, please consider [following these instructions](rake_tasks.md#extra-project-seed-options)
## How does it work?
diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md
index 77f064a21a9..e4225f2bc39 100644
--- a/doc/development/fe_guide/frontend_faq.md
+++ b/doc/development/fe_guide/frontend_faq.md
@@ -25,3 +25,17 @@ document.body.dataset.page
```
Find here the [source code setting the attribute](https://gitlab.com/gitlab-org/gitlab-ce/blob/cc5095edfce2b4d4083a4fb1cdc7c0a1898b9921/app/views/layouts/application.html.haml#L4).
+
+### `modal_copy_button` vs `clipboard_button`
+
+The `clipboard_button` uses the `copy_to_clipboard.js` behaviour, which is
+initialized on page load, so if there are vue-based clipboard buttons that
+don't exist at page load (such as ones in a `GlModal`), they do not have the
+click handlers associated with the clipboard package.
+
+`modal_copy_button` was added that manages an instance of the
+[`clipboard` plugin](https://www.npmjs.com/package/clipboard) specific to
+the instance of that component, which means that clipboard events are
+bound on mounting and destroyed when the button is, mitigating the above
+issue. It also has bindings to a particular container or modal ID
+available, to work with the focus trap created by our GlModal.
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 060cd8baf7f..b50159c2b75 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -95,6 +95,7 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
#### Modules, Imports, and Exports
1. Use ES module syntax to import modules
+
```javascript
// bad
const SomeClass = require('some_class');
@@ -168,6 +169,7 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
Do not use them anymore and feel free to remove them when refactoring legacy code.
1. Avoid adding to the global namespace.
+
```javascript
// bad
window.MyClass = class { /* ... */ };
@@ -176,7 +178,8 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
export default class MyClass { /* ... */ }
```
-1. Side effects are forbidden in any script which contains exports
+1. Side effects are forbidden in any script which contains export
+
```javascript
// bad
export default class MyClass { /* ... */ }
@@ -449,6 +452,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
#### Props
1. Props should be declared as an object
+
```javascript
// bad
props: ['foo']
@@ -544,14 +548,24 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
<component @click="eventHandler"/>
```
-1. Shorthand `:` is preferable over `v-bind`
+2. Shorthand `:` is preferable over `v-bind`
```javascript
// bad
<component v-bind:class="btn"/>
// good
- <component :class="btsn"/>
+ <component :class="btn"/>
+ ```
+
+3. Shorthand `#` is preferable over `v-slot`
+
+ ```javascript
+ // bad
+ <template v-slot:header></template>
+
+ // good
+ <template #header></template>
```
#### Closing tags
diff --git a/doc/development/fe_guide/style_guide_scss.md b/doc/development/fe_guide/style_guide_scss.md
index 36880dd746d..b25dce65ffe 100644
--- a/doc/development/fe_guide/style_guide_scss.md
+++ b/doc/development/fe_guide/style_guide_scss.md
@@ -9,20 +9,44 @@ easy to maintain, and performant for the end-user.
As part of the effort for [cleaning up our CSS and moving our components into GitLab-UI](https://gitlab.com/groups/gitlab-org/-/epics/950)
led by the [GitLab UI WG](https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/20623) we prefer the use of utility classes over adding new CSS. However, complex CSS can be addressed by adding component classes.
-We have a few internal utility classes in [`common.scss`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/stylesheets/framework/common.scss)
-and we use [Bootstrap's Utility Classes](https://getbootstrap.com/docs/4.3/utilities/)
+#### Where are utility classes defined?
+
+- [Bootstrap's Utility Classes](https://getbootstrap.com/docs/4.3/utilities/)
+- [`common.scss`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/stylesheets/framework/common.scss) (old)
+- [`utilities.scss`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/stylesheets/utilities.scss) (new)
+
+#### Where should I put new utility classes?
New utility classes should be added to [`utilities.scss`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/stylesheets/utilities.scss). Existing classes include:
-**Background color**: `.bg-variant-shade` e.g. `.bg-warning-400`
-**Text color**: `.text-variant-shade` e.g. `.text-success-500`
+| Name | Pattern | Example |
+|------|---------|---------|
+| Background color | `.bg-{variant}-{shade}` | `.bg-warning-400` |
+| Text color | `.text-{variant}-{shade}` | `.text-success-500` |
+| Font size | `.text-{size}` | `.text-2` |
+
+- `{variant}` is one of 'primary', 'secondary', 'success', 'warning', 'error'
+- `{shade}` is on of the shades listed on [colors](https://design.gitlab.com/foundations/colors/)
+- `{size}` is a number from 1-6 from our [Type scale](https://design.gitlab.com/foundations/typography)
+
+#### When should I create component classes?
+
+We recommend a "utility-first" approach.
+
+1. Start with utility classes.
+2. If composing utility classes into a component class removes code duplication and encapsulates a clear responsibility, do it.
+
+This encourages an organic growth of component classes and prevents the creation of one-off unreusable classes. Also, the kind of classes that emerge from "utility-first" tend to be design-centered (e.g. `.button`, `.alert`, `.card`) rather than domain-centered (e.g. `.security-report-widget`, `.commit-header-icon`).
+
+Examples of component classes that were created using "utility-first" include:
-- variant is one of 'primary', 'secondary', 'success', 'warning', 'error'
-- shade is on of the shades listed on [colors](https://design.gitlab.com/foundations/colors/)
+- [`.circle-icon-container`](https://gitlab.com/gitlab-org/gitlab-ce/blob/579fa8b8ec7eb38d40c96521f517c9dab8c3b97a/app/assets/stylesheets/framework/icons.scss#L85)
+- [`.d-flex-center`](https://gitlab.com/gitlab-org/gitlab-ce/blob/900083d89cd6af391d26ab7922b3f64fa2839bef/app/assets/stylesheets/framework/common.scss#L425)
-**Font size**: `.text-size` e.g. `.text-2`
+Inspiration:
-- **size** is number from 1-6 from our [Type scale](https://design.gitlab.com/foundations/typography)
+- https://tailwindcss.com/docs/utility-first
+- https://tailwindcss.com/docs/extracting-components
### Naming
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 437ce9abc7d..8c6a73c6824 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -234,7 +234,7 @@ One should apply to be a Vue.js expert by opening an MR when the Merge Request's
- Deep understanding of Vue and Vuex reactivy
- Vue and Vuex code are structured according to both official and our guidelines
- Full understanding of testing a Vue and Vuex application
-- Vuex code follows the [documented pattern](./vuex.md#actions-pattern-request-and-receive-namespaces)
+- Vuex code follows the [documented pattern](vuex.md#actions-pattern-request-and-receive-namespaces)
- Knowledge about the existing Vue and Vuex applications and existing reusable components
[vue-docs]: http://vuejs.org/guide/index.html
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 82b09cc0224..13f0c5cc33e 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -20,7 +20,7 @@ dynamic (querying the DB etc.).
Once defined in `lib/feature.rb`, you will be able to activate a
feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature)
-For GitLab.com, team members have access to feature flags through chatops. Only
+For GitLab.com, [team members have access to feature flags through Chatops](chatops_on_gitlabcom.md). Only
percentage gates are supported at this time. Setting a feature to be used 50% of
the time, you should execute `/chatops run feature set my_feature_flag 50`.
@@ -28,7 +28,7 @@ the time, you should execute `/chatops run feature set my_feature_flag 50`.
This document only covers feature flags used in the development of GitLab
itself. Feature flags in deployed user applications can be found at
-[Feature Flags](https://docs.gitlab.com/ee/user/project/operations/feature_flags.html)
+[Feature Flags](../user/project/operations/feature_flags.md)
## Developing with feature flags
@@ -111,7 +111,7 @@ Whenever a feature flag is present, make sure to test _both_ states of the
feature flag.
See the
-[testing guide](testing_guide/best_practices.html#feature-flags-in-tests)
+[testing guide](testing_guide/best_practices.md#feature-flags-in-tests)
for information and examples on how to stub feature flags in tests.
## Enabling a feature flag (in development)
diff --git a/doc/development/geo.md b/doc/development/geo.md
index c8e6a86eb52..6e59fab34c7 100644
--- a/doc/development/geo.md
+++ b/doc/development/geo.md
@@ -10,6 +10,7 @@ the diagram below and are described in more detail within this document.
## Replication layer
Geo handles replication for different components:
+
- [Database](#database-replication): includes the entire application, except cache and jobs.
- [Git repositories](#repository-replication): includes both projects and wikis.
- [Uploaded blobs](#uploads-replication): includes anything from images attached on issues
@@ -90,7 +91,7 @@ projects that need updating. Those projects can be:
timestamp that is more recent than the `last_repository_successful_sync_at`
timestamp in the `Geo::ProjectRegistry` model.
- Manual: The admin can manually flag a repository to resync in the
- [Geo admin panel](https://docs.gitlab.com/ee/user/admin_area/geo_nodes.html).
+ [Geo admin panel](../user/admin_area/geo_nodes.md).
When we fail to fetch a repository on the secondary `RETRIES_BEFORE_REDOWNLOAD`
times, Geo does a so-called _redownload_. It will do a clean clone
@@ -209,20 +210,38 @@ bundle exec rake geo:db:migrate
### Foreign Data Wrapper
-The use of [FDW](#fdw) was introduced in GitLab 10.1.
+> Introduced in GitLab 10.1.
+
+Foreign Data Wrapper ([FDW](#fdw)) is used by the [Geo Log Cursor](#geo-log-cursor) and improves
+the performance of many synchronization operations.
-This is useful for the [Geo Log Cursor](#geo-log-cursor) and improves
-the performance of some synchronization operations.
+FDW is a PostgreSQL extension ([`postgres_fdw`](https://www.postgresql.org/docs/current/postgres-fdw.html)) that is enabled within
+the Geo Tracking Database (on a **secondary** node), which allows it
+to connect to the readonly database replica and perform queries and filter
+data from both instances.
While FDW is available in older versions of PostgreSQL, we needed to
raise the minimum required version to 9.6 as this includes many
performance improvements to the FDW implementation.
+This persistent connection is configured as an FDW server
+named `gitlab_secondary`. This configuration exists within the database's user
+context only. To access the `gitlab_secondary`, GitLab needs to use the
+same database user that had previously been configured.
+
+The Geo Tracking Database accesses the readonly database replica via FDW as a regular user,
+limited by its own restrictions. The credentials are configured as a
+`USER MAPPING` associated with the `SERVER` mapped previously
+(`gitlab_secondary`).
+
+FDW configuration and credentials definition are managed automatically by the
+Omnibus GitLab `gitlab-ctl reconfigure` command.
+
#### Refeshing the Foreign Tables
-Whenever the database schema changes on the **primary** node, the
-**secondary** node will need to refresh its foreign tables by running
-the following:
+Whenever a new Geo node is configured or the database schema changes on the
+**primary** node, you must refresh the foreign tables on the **secondary** node
+by running the following:
```sh
bundle exec rake geo:db:refresh_foreign_tables
@@ -243,6 +262,53 @@ STATEMENT: SELECT a.attname, format_type(a.atttypid, a.atttypmod)
ORDER BY a.attnum
```
+#### Accessing data from a Foreign Table
+
+At the SQL level, all you have to do is `SELECT` data from `gitlab_secondary.*`.
+
+Here's an example of how to access all projects from the Geo Tracking Database's FDW:
+
+```sql
+SELECT * FROM gitlab_secondary.projects;
+```
+
+As a more real-world example, this is how you filter for unarchived projects
+on the Tracking Database:
+
+```sql
+SELECT project_registry.*
+ FROM project_registry
+ JOIN gitlab_secondary.projects
+ ON (project_registry.project_id = gitlab_secondary.projects.id
+ AND gitlab_secondary.projects.archived IS FALSE)
+```
+
+At the ActiveRecord level, we have additional Models that represent the
+foreign tables. They must be mapped in a slightly different way, and they are read-only.
+
+Check the existing FDW models in `ee/app/models/geo/fdw` for reference.
+
+From a developer's perspective, it's no different than creating a model that
+represents a Database View.
+
+With the examples above, you can access the projects with:
+
+```ruby
+Geo::Fdw::Project.all
+```
+
+and to access the `ProjectRegistry` filtering by unarchived projects:
+
+```ruby
+# We have to use Arel here:
+project_registry_table = Geo::ProjectRegistry.arel_table
+fdw_project_table = Geo::Fdw::Project.arel_table
+
+project_registry_table.join(fdw_project_table)
+ .on(project_registry_table[:project_id].eq(fdw_project_table[:id]))
+ .where((fdw_project_table[:archived]).eq(true)) # if you append `.to_sql` you can check generated query
+```
+
## Finders
Geo uses [Finders](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/app/finders),
@@ -299,7 +365,7 @@ basically hashes all Git refs together and stores that hash in the
The **secondary** node does the same to calculate the hash of its
clone, and compares the hash with the value the **primary** node
calculated. If there is a mismatch, Geo will mark this as a mismatch
-and the administrator can see this in the [Geo admin panel](https://docs.gitlab.com/ee/user/admin_area/geo_nodes.html).
+and the administrator can see this in the [Geo admin panel](../user/admin_area/geo_nodes.md).
## Glossary
diff --git a/doc/development/git_object_deduplication.md b/doc/development/git_object_deduplication.md
index 81c5f69c7b8..b512d7611d3 100644
--- a/doc/development/git_object_deduplication.md
+++ b/doc/development/git_object_deduplication.md
@@ -10,10 +10,10 @@ GitLab implements Git object deduplication.
## Enabling Git object deduplication via feature flags
-As of GitLab 11.9, Git object deduplication in GitLab is in beta. In this
-document, you can read about the caveats of enabling the feature. Also,
-note that Git object deduplication is limited to forks of public
-projects on hashed repository storage.
+As of GitLab 12.0, Git object deduplication in GitLab is still behind a
+feature flag. In this document, you can read about the effects of
+enabling the feature. Also, note that Git object deduplication is
+limited to forks of public projects on hashed repository storage.
You can enable deduplication globally by setting the `object_pools`
feature flag to `true`:
@@ -51,6 +51,15 @@ configuration. Objects in A that are not in B will remain in A. For this
to work, it is of course critical that **no objects ever get deleted from
B** because A might need them.
+DANGER: **Danger:**
+Do not run `git prune` or `git gc` in pool repositories! This can
+cause data loss in "real" repositories that depend on the pool in
+question.
+
+The danger lies in `git prune`, and `git gc` calls `git prune`. The
+problem is that `git prune`, when running in a pool repository, cannot
+reliable decide if an object is no longer needed.
+
### Git alternates in GitLab: pool repositories
GitLab organizes this object borrowing by creating special **pool
@@ -80,43 +89,10 @@ across a collection of GitLab project repositories at the Git level:
The effectiveness of Git object deduplication in GitLab depends on the
amount of overlap between the pool repository and each of its
-participants. As of GitLab 11.9, we have a somewhat optimistic system.
-The only data that will be deduplicated is the data in the source
-project repository at the time the pool repository is created. That is,
-the data in the source project at the time of the first fork *after* the
-deduplication feature has been enabled.
-
-When we enable the object deduplication feature for
-gitlab.com/gitlab-org/gitlab-ce, which is about 1GB at the time of
-writing, all new forks of that project would be 1GB smaller than they
-would have been without Git object deduplication. So even in its current
-optimistic form, we expect Git object deduplication in GitLab to make a
-difference.
-
-However, if a lot of Git objects get added to the project repositories
-in a pool after the pool repository was created these new Git objects
-will currently (GitLab 11.9) not get deduplicated. Over time, the
-deduplication factor of the pool will get worse and worse.
-
-As an extreme example, if we create an empty repository A, and fork that
-to repository B, behind the scenes we get an object pool P with no
-objects in it at all. If we then push 1GB of Git data to A, and push the
-same Git data to B, it will not get deduplicated, because that data was
-not in A at the time P was created.
-
-This also matters in less extreme examples. Consider a pool P with
-source project A and 500 active forks B1, B2,...,B500. Suppose,
-optimistically, that the forks are fully deduplicated at the start of
-our scenario. Now some time passes and 200MB of new Git data gets added
-to project A. Because of the forking workflow, this data makes also its way
-into the forks B1, ..., B500. That means we would now have 100GB of Git
-data sitting around (500 \* 200MB) across the forks, that could have
-been deduplicated. But because of the way we do deduplication this new
-data will not be deduplicated.
-
-> TODO Add periodic maintenance of object pools to prevent gradual loss
-> of deduplication over time.
-> https://gitlab.com/groups/gitlab-org/-/epics/524
+participants. Each time garbage collection runs on the source project,
+Git objects from the source project will get migrated to the pool
+repository. One by one, as garbage collection runs, other member
+projects will benefit from the new objects that got added to the pool.
## SQL model
@@ -136,6 +112,9 @@ are as follows:
- a `PoolRepository` has exactly one "source `Project`"
(`pool.source_project`)
+> TODO Fix invalid SQL data for pools created prior to GitLab 11.11
+> https://gitlab.com/gitlab-org/gitaly/issues/1653.
+
### Assumptions
- All repositories in a pool must use [hashed
@@ -146,10 +125,6 @@ are as follows:
The Git alternates mechanism relies on direct disk access across
multiple repositories, and we can only assume direct disk access to
be possible within a Gitaly storage shard.
-- All project repositories in a pool must have "Public" visibility in
- GitLab at the time they join. There are gotchas around visibility of
- Git objects across alternates links. This restriction is a defense
- against accidentally leaking private Git data.
- The only two ways to remove a member project from a pool are (1) to
delete the project or (2) to move the project to another Gitaly
storage shard.
@@ -187,17 +162,14 @@ are as follows:
### Consequences
- If a normal Project participating in a pool gets moved to another
- Gitaly storage shard, its "belongs to PoolRepository" relation must
+ Gitaly storage shard, its "belongs to PoolRepository" relation will
be broken. Because of the way moving repositories between shard is
implemented, we will automatically get a fresh self-contained copy
of the project's repository on the new storage shard.
- If the source project of a pool gets moved to another Gitaly storage
- shard or is deleted, we may have to break the "PoolRepository has
- one source Project" relation?
-
-> TODO What happens, or should happen, if a source project changes
-> visibility, is deleted, or moves to another storage shard?
-> https://gitlab.com/gitlab-org/gitaly/issues/1488
+ shard or is deleted the "source project" relation is not broken.
+ However, as of GitLab 12.0 a pool will not fetch from a source
+ unless the source is on the same Gitaly shard.
## Consistency between the SQL pool relation and Gitaly
@@ -209,16 +181,8 @@ repository and a pool.
### Pool existence
If GitLab thinks a pool repository exists (i.e. it exists according to
-SQL), but it does not on the Gitaly server, then certain RPC calls that
-take the object pool as an argument will fail.
-
-> TODO What happens if SQL says the pool repo exists but Gitaly says it
-> does not? https://gitlab.com/gitlab-org/gitaly/issues/1533
-
-If GitLab thinks a pool does not exist, while it does exist on disk,
-that has no direct consequences on its own. However, if other
-repositories on disk borrow objects from this unknown pool repository
-then we risk data loss, see below.
+SQL), but it does not on the Gitaly server, then it will be created on
+the fly by Gitaly.
### Pool relation existence
@@ -226,26 +190,19 @@ There are three different things that can go wrong here.
#### 1. SQL says repo A belongs to pool P but Gitaly says A has no alternate objects
-In this case, we miss out on disk space savings but all RPC's on A itself
-will function fine. As long as Git can find all its objects, it does not
-matter exactly where those objects are.
+In this case, we miss out on disk space savings but all RPC's on A
+itself will function fine. The next time garbage collection runs on A,
+the alternates connection gets established in Gitaly. This is done by
+`Projects::GitDeduplicationService` in gitlab-rails.
#### 2. SQL says repo A belongs to pool P1 but Gitaly says A has alternate objects in pool P2
-If we are not careful, this situation can lead to data loss. During some
-operations (repository maintenance), GitLab will try to re-link A to its
-pool P1. If this clobbers the existing link to P2, then A will loose Git
-objects and become invalid.
-
-Also, keep in mind that if GitLab's database got messed up, it may not
-even know that P2 exists.
-
-> TODO Ensure that Gitaly will not clobber existing, unexpected
-> alternates links. https://gitlab.com/gitlab-org/gitaly/issues/1534
+In this case `Projects::GitDeduplicationService` will throw an exception.
#### 3. SQL says repo A does not belong to any pool but Gitaly says A belongs to P
-This has the same data loss possibility as scenario 2 above.
+In this case `Projects::GitDeduplicationService` will try to
+"re-duplicate" the repository A using the DisconnectGitAlternates RPC.
## Git object deduplication and GitLab Geo
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index cf78b792ffd..4dad8815fcb 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -40,7 +40,7 @@ of possible security breaches in our code:
- SQL injections
Remember to run
-[SAST](https://docs.gitlab.com/ee/user/application_security/sast/index)
+[SAST](../../user/application_security/sast/index.md)
**[ULTIMATE]** on your project (or at least the [gosec
analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/gosec)),
and to follow our [Security
@@ -94,9 +94,9 @@ become available, you will be able to share job templates like this
Dependencies should be kept to the minimum. The introduction of a new
dependency should be argued in the merge request, as per our [Approval
Guidelines](../code_review.md#approval-guidelines). Both [License
-Management](https://docs.gitlab.com/ee/user/project/merge_requests/license_management.html)
+Management](../../user/project/merge_requests/license_management.md)
**[ULTIMATE]** and [Dependency
-Scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index)
+Scanning](../../user/application_security/dependency_scanning/index.md)
**[ULTIMATE]** should be activated on all projects to ensure new dependencies
security status and license compatibility.
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 38425674567..9fb8ea542d9 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -174,7 +174,7 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
# => When size == 2: 'There are 2 mice.'
```
- Avoid using `%d` or count variables in sigular strings. This allows more natural translation in some languages.
+ Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
- In JavaScript:
@@ -212,7 +212,7 @@ Namespaces should be PascalCase.
```
Note: The namespace should be removed from the translation. See the [translation
-guidelines for more details](./translation.md#namespaced-strings).
+guidelines for more details](translation.md#namespaced-strings).
### Dates / times
@@ -332,7 +332,7 @@ Errors in `locale/zh_HK/gitlab.po`:
Syntax error in msgstr
Syntax error in message_line
There should be only whitespace until the end of line after the double quote character of a message text.
- Parseing result before error: '{:msgid=>["", "You are going to remove %{project_name_with_namespace}.\\n", "Removed project CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
+ Parsing result before error: '{:msgid=>["", "You are going to remove %{project_name_with_namespace}.\\n", "Removed project CANNOT be restored!\\n", "Are you ABSOLUTELY sure?"]}'
SimplePoParser filtered backtrace: SimplePoParser::ParserError
Errors in `locale/zh_TW/gitlab.po`:
1 pipeline
diff --git a/doc/development/i18n/merging_translations.md b/doc/development/i18n/merging_translations.md
index 85284d8c714..eadf1cd74c5 100644
--- a/doc/development/i18n/merging_translations.md
+++ b/doc/development/i18n/merging_translations.md
@@ -32,7 +32,7 @@ clicking `Pause sync` on the [Crowdin integration settings
page](https://translate.gitlab.com/project/gitlab-ee/settings#integration).
When all failures are resolved, the translations need to be double
-checked once more as discussed in [confidential issue](https://docs.gitlab.com/ee/user/project/issues/confidential_issues.html) `https://gitlab.com/gitlab-org/gitlab-ce/issues/37850`.
+checked once more as discussed in [confidential issue](../../user/project/issues/confidential_issues.md) `https://gitlab.com/gitlab-org/gitlab-ce/issues/37850`.
## Merging translations
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index eb492c9818b..35c5b155594 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -27,7 +27,7 @@ are very appreciative of the work done by translators and proofreaders!
- Czech
- Proofreaders needed.
- Danish
- - Proofreaders needed.
+ - Saederup92 - [GitLab](https://gitlab.com/Saederup92), [Crowdin](https://crowdin.com/profile/Saederup92)
- Dutch
- Emily Hendle - [GitLab](https://gitlab.com/pundachan), [Crowdin](https://crowdin.com/profile/pandachan)
- Esperanto
@@ -130,7 +130,7 @@ are very appreciative of the work done by translators and proofreaders!
your previous translations by [GitLab team members](https://about.gitlab.com/team/)
or [Core team members](https://about.gitlab.com/core-team/) who are fluent in
the language or current proofreaders.
- - When a request is made for the first proofreader for a lanuguage and there are no [GitLab team members](https://about.gitlab.com/team/)
+ - When a request is made for the first proofreader for a language and there are no [GitLab team members](https://about.gitlab.com/team/)
or [Core team members](https://about.gitlab.com/core-team/) who speak the language, we will request links to previous translation work in other communities or projects.
diff --git a/doc/development/i18n/translation.md b/doc/development/i18n/translation.md
index 99c0fe6db1d..62be3786549 100644
--- a/doc/development/i18n/translation.md
+++ b/doc/development/i18n/translation.md
@@ -89,9 +89,7 @@ To propose additions to the glossary please
### Inclusive language in French
-In French, we should follow the guidelines from [ecriture-inclusive.fr]. For
-instance:
+In French, the "écriture inclusive" is now over (see on [Legifrance](https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000036068906&categorieLien=id)).
+So, to include both genders, write “Utilisateurs et utilisatrices” instead of “Utilisateur·rice·s”.
+When space is missing, the male gender should be used alone.
-- Utilisateur•rice•s
-
-[ecriture-inclusive.fr]: http://www.ecriture-inclusive.fr/
diff --git a/doc/development/img/architecture_simplified.png b/doc/development/img/architecture_simplified.png
new file mode 100644
index 00000000000..1698c167c5e
--- /dev/null
+++ b/doc/development/img/architecture_simplified.png
Binary files differ
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
index f7f48b03651..fd067b80c16 100644
--- a/doc/development/import_export.md
+++ b/doc/development/import_export.md
@@ -27,7 +27,7 @@ Read through the current performance problems using the Import/Export below.
### OOM errors
-Out of memory (OOM) errors are normally caused by the [Sidekiq Memory Killer](https://docs.gitlab.com/ee/administration/operations/sidekiq_memory_killer.html):
+Out of memory (OOM) errors are normally caused by the [Sidekiq Memory Killer](../administration/operations/sidekiq_memory_killer.md):
```bash
SIDEKIQ_MEMORY_KILLER_MAX_RSS = 2GB in GitLab.com
diff --git a/doc/development/licensed_feature_availability.md b/doc/development/licensed_feature_availability.md
index 1657d73e0c9..6f3dd59b2c3 100644
--- a/doc/development/licensed_feature_availability.md
+++ b/doc/development/licensed_feature_availability.md
@@ -7,8 +7,8 @@ on-premise or GitLab.com plans and features.
## Restricting features scoped by namespaces or projects
GitLab.com plans are persisted on user groups and namespaces, therefore, if you're adding a
-feature such as [Related issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html) or
-[Service desk](https://docs.gitlab.com/ee/user/project/service_desk.html),
+feature such as [Related issues](../user/project/issues/related_issues.md) or
+[Service desk](../user/project/service_desk.md),
it should be restricted on namespace scope.
1. Add the feature symbol on `EES_FEATURES`, `EEP_FEATURES` or `EEU_FEATURES` constants in
@@ -22,8 +22,8 @@ project.feature_available?(:feature_symbol)
## Restricting global features (instance)
-However, for features such as [Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html) and
-[Load balancing](https://docs.gitlab.com/ee/administration/database_load_balancing.html), which cannot be restricted
+However, for features such as [Geo](../administration/geo/replication/index.md) and
+[Load balancing](../administration/database_load_balancing.md), which cannot be restricted
to only a subset of projects or namespaces, the check will be made directly in
the instance license.
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index 0e71cd47481..0db90d2872f 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -88,9 +88,11 @@ Definitions
GitLab means GitLab Inc. and its affiliates and subsidiaries.
-## Requesting Approval for Licenses
+## Requesting Approval for Licenses or any other Intellectual Property
-Libraries that are not listed in the [Acceptable Licenses][Acceptable-Licenses] or [Unacceptable Licenses][Unacceptable-Licenses] list can be submitted to the legal team for review. Please email `legal@gitlab.com` with the details. After a decision has been made, the original requestor is responsible for updating this document.
+Libraries that are not already approved and listed on the [Acceptable Licenses][Acceptable-Licenses] list or that may be listed on the [Unacceptable Licenses][Unacceptable-Licenses] list may be submitted to the legal team for review and use on a case-by-case basis. Please email `legal@gitlab.com` with the details of how the software will be used, whether or not it will be modified, and how it will be distributed (if at all). After a decision has been made, the original requestor is responsible for updating this document, if applicable. Not all approvals will be approved for universal use and may continue to remain on the Unacceptable License list.
+
+All inquiries relating to patents should be directed to the Legal team.
## Notes
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 0c326eeb851..9b26f691b55 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -186,7 +186,11 @@ end
When adding a foreign-key constraint to either an existing or new
column remember to also add a index on the column.
-This is _required_ for all foreign-keys.
+This is **required** for all foreign-keys, e.g., to support efficient cascading
+deleting: when a lot of rows in a table get deleted, the referenced records need
+to be deleted too. The database has to look for corresponding records in the
+referenced table. Without an index, this will result in a sequential scan on the
+table which can take a long time.
Here's an example where we add a new column with a foreign key
constraint. Note it includes `index: true` to create an index for it.
diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md
index 8420a504ec4..81a29170129 100644
--- a/doc/development/new_fe_guide/development/accessibility.md
+++ b/doc/development/new_fe_guide/development/accessibility.md
@@ -21,6 +21,7 @@ On the other hand, if an icon is crucial to understand the context we should do
## Form inputs
In forms we should use the `for` attribute in the label statement:
+
```
<div>
<label for="name">Fill in your name:</label>
diff --git a/doc/development/new_fe_guide/development/components.md b/doc/development/new_fe_guide/development/components.md
index 8ae58d30c35..963ce53423b 100644
--- a/doc/development/new_fe_guide/development/components.md
+++ b/doc/development/new_fe_guide/development/components.md
@@ -17,5 +17,5 @@ D3 is very popular across many projects outside of GitLab:
Within GitLab, D3 has been used for the following notable features
-- [Prometheus graphs](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html)
+- [Prometheus graphs](../../../user/project/integrations/prometheus.md)
- Contribution calendars
diff --git a/doc/development/new_fe_guide/tips.md b/doc/development/new_fe_guide/tips.md
index 889a5aab2b7..4564f678ec0 100644
--- a/doc/development/new_fe_guide/tips.md
+++ b/doc/development/new_fe_guide/tips.md
@@ -10,16 +10,16 @@ yarn clean
## Creating feature flags in development
-The process for creating a feature flag is the same as [enabling a feature flag in development](https://docs.gitlab.com/ee/development/feature_flags.html#enabling-a-feature-flag-in-development).
+The process for creating a feature flag is the same as [enabling a feature flag in development](../feature_flags.md#enabling-a-feature-flag-in-development).
Your feature flag can now be:
-- [made available to the frontend](https://docs.gitlab.com/ee/development/feature_flags.html#frontend) via the `gon`
-- queried in [tests](https://docs.gitlab.com/ee/development/feature_flags.html#specs)
+- [made available to the frontend](../feature_flags.md#frontend) via the `gon`
+- queried in [tests](../feature_flags.md#specs)
- queried in HAML templates and ruby files via the `Feature.enabled?(:my_shiny_new_feature_flag)` method
### More on feature flags
-- [Deleting a feature flag](https://docs.gitlab.com/ee/api/features.html#delete-a-feature)
-- [Manage feature flags](https://docs.gitlab.com/ee/development/feature_flags.html)
-- [Feature flags API](https://docs.gitlab.com/ee/api/features.html)
+- [Deleting a feature flag](../../api/features.md#delete-a-feature)
+- [Manage feature flags](../feature_flags.md)
+- [Feature flags API](../../api/features.md)
diff --git a/doc/development/packages.md b/doc/development/packages.md
index a3b891d7834..ab0c5f9904d 100644
--- a/doc/development/packages.md
+++ b/doc/development/packages.md
@@ -1,8 +1,8 @@
# Packages **[PREMIUM]**
-This document will guide you through adding another [package management system](https://docs.gitlab.com/ee/administration/packages.html) support to GitLab.
+This document will guide you through adding another [package management system](../administration/packages.md) support to GitLab.
-See already supported package types in [Packages documentation](https://docs.gitlab.com/ee/administration/packages.html)
+See already supported package types in [Packages documentation](../administration/packages.md)
Since GitLab packages' UI is pretty generic, it is possible to add new
package system support by solely backend changes. This guide is superficial and does
@@ -46,7 +46,7 @@ Group-level and instance-level endpoints are good to have but are optional.
NOTE: **Note:**
To avoid name conflict for instance-level endpoints we use
-[the package naming convention](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html#package-naming-convention)
+[the package naming convention](../user/project/packages/npm_registry.md#package-naming-convention)
## Configuration
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 27fc3231218..28a12572961 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -31,7 +31,7 @@ project.
#### Seeding issues for Insights charts **[ULTIMATE]**
You can seed issues specifically for working with the
-[Insights charts](https://docs.gitlab.com/ee/user/group/insights/index.html) with the
+[Insights charts](../user/group/insights/index.md) with the
`gitlab:seed:insights:issues` task:
```shell
@@ -108,11 +108,13 @@ To make sure that indices still fit. You could find great details in:
In order to run the test you can use the following commands:
-- `rake spec` to run the rspec suite
-- `rake karma` to run the karma test suite
-- `rake gitlab:test` to run all the tests
+- `bin/rake spec` to run the rspec suite
+- `bin/rake spec:unit` to run the only the unit tests
+- `bin/rake spec:integration` to run the only the integration tests
+- `bin/rake spec:system` to run the only the system tests
+- `bin/rake karma` to run the karma test suite
-Note: `rake spec` takes significant time to pass.
+Note: `bin/rake spec` takes significant time to pass.
Instead of running full test suite locally you can save a lot of time by running
a single test or directory related to your changes. After you submit merge request
CI will run full test suite for you. Green CI status in the merge request means
@@ -121,6 +123,9 @@ full test suite is passed.
Note: You can't run `rspec .` since this will try to run all the `_spec.rb`
files it can find, also the ones in `/tmp`
+Note: You can pass RSpec command line options to the `spec:unit`,
+`spec:integration`, and `spec:system` tasks, e.g. `bin/rake "spec:unit[--tag ~geo --dry-run]"`.
+
To run a single test file you can use:
- `bin/rspec spec/controllers/commit_controller_spec.rb` for a rspec test
diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md
index 8e4e07c4319..84028b1b342 100644
--- a/doc/development/rolling_out_changes_using_feature_flags.md
+++ b/doc/development/rolling_out_changes_using_feature_flags.md
@@ -102,7 +102,7 @@ GitLab's feature library (using
[Flipper](https://github.com/jnunemaker/flipper), and covered in the [Feature
Flags](feature_flags.md) guide) supports rolling out changes to a percentage of
users. This in turn can be controlled using [GitLab
-chatops](https://docs.gitlab.com/ee/ci/chatops/).
+chatops](../ci/chatops/README.md).
For an up to date list of feature flag commands please see [the source
code](https://gitlab.com/gitlab-com/chatops/blob/master/lib/chatops/commands/feature.rb).
@@ -200,10 +200,9 @@ isn't gated by a License or Plan.
### Undefined feature flags default to "on"
-An important side-effect of the [implicit feature
-flags][#implicit-feature-flags] mentioned above is that unless the feature is
-explicitly disabled or limited to a percentage of users, the feature flag check
-will default to `true`.
+An important side-effect of the [implicit feature flags](#implicit-feature-flags)
+mentioned above is that unless the feature is explicitly disabled or limited to a
+percentage of users, the feature flag check will default to `true`.
As an example, if you were to ship the backend half of a feature behind a flag,
you'd want to explicitly disable that flag until the frontend half is also ready
diff --git a/doc/development/routing.md b/doc/development/routing.md
new file mode 100644
index 00000000000..e9c0ad8d4e8
--- /dev/null
+++ b/doc/development/routing.md
@@ -0,0 +1,63 @@
+# Routing
+
+The GitLab backend is written primarily with Rails so it uses [Rails
+routing](https://guides.rubyonrails.org/routing.html). Beside Rails best
+practices, there are few rules unique to the GitLab application. To
+support subgroups, GitLab project and group routes use the wildcard
+character to match project and group routes. For example, we might have
+a path such as:
+
+ /gitlab-com/customer-success/north-america/west/customerA
+
+However, paths can be ambiguous. Consider the following example:
+
+ /gitlab-com/edit
+
+It's ambiguous whether there is a subgroup named `edit` or whether
+this is a special endpoint to edit the `gitlab-com` group.
+
+To eliminate the ambiguity and to make the backend easier to maintain,
+we introduced the `/-/` scope. The purpose of it is to separate group or
+project paths from the rest of the routes. Also it helps to reduce the
+number of [reserved names](../user/reserved_names.md).
+
+## Global routes
+
+We have a number of global routes. For example:
+
+ /-/health
+ /-/metrics
+
+## Group routes
+
+Every group route must be under the `/-/` scope.
+
+Examples:
+
+ gitlab-org/-/edit
+ gitlab-org/-/activity
+ gitlab-org/-/security/dashboard
+ gitlab-org/serverless/-/activity
+
+To achieve that, use the `scope '-'` method.
+
+## Project routes
+
+Every project route must be under the `/-/` scope, except cases where a Git
+client or other software requires something different.
+
+Examples:
+
+ gitlab-org/gitlab-ce/-/activity
+ gitlab-org/gitlab-ce/-/jobs/123
+ gitlab-org/gitlab-ce/-/settings/repository
+ gitlab-org/serverless/runtimes/-/settings/repository
+
+Currently, only some project routes are placed under the `/-/` scope. However,
+you can help us migrate more of them! To migrate project routes:
+
+1. Modify existing routes by adding `-` scope.
+1. Add redirects for legacy routes by using `Gitlab::Routing.redirect_legacy_paths`.
+1. Create a technical debt issue to remove deprecated routes in later releases.
+
+To get started, see an [example merge request](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28435).
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 63ec9755462..82439c94c5a 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -2,19 +2,29 @@
## Test Design
-Testing at GitLab is a first class citizen, not an afterthought. It's important we consider the design of our tests
-as we do the design of our features.
+Testing at GitLab is a first class citizen, not an afterthought. It's important we consider the design of our tests
+as we do the design of our features.
-When implementing a feature, we think about developing the right capabilities the right way, which helps us
-narrow our scope to a manageable level. When implementing tests for a feature, we must think about developing
-the right tests, but then cover _all_ the important ways the test may fail, which can quickly widen our scope to
+When implementing a feature, we think about developing the right capabilities the right way, which helps us
+narrow our scope to a manageable level. When implementing tests for a feature, we must think about developing
+the right tests, but then cover _all_ the important ways the test may fail, which can quickly widen our scope to
a level that is difficult to manage.
-Test heuristics can help solve this problem. They concisely address many of the common ways bugs
-manifest themselves within our code. When designing our tests, take time to review known test heuristics to inform
-our test design. We can find some helpful heuristics documented in the Handbook in the
+Test heuristics can help solve this problem. They concisely address many of the common ways bugs
+manifest themselves within our code. When designing our tests, take time to review known test heuristics to inform
+our test design. We can find some helpful heuristics documented in the Handbook in the
[Test Design](https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/test-design/) section.
+## Run tests against MySQL
+
+By default, tests are only run againts PostgreSQL, but you can run them on
+demand against MySQL by following one of the following conventions:
+
+| Convention | Valid example |
+|:----------------------|:-----------------------------|
+| Include `mysql` in your branch name | `enhance-mysql-support` |
+| Include `[run mysql]` in your commit message | `Fix MySQL support<br><br>[run mysql]` |
+
## Test speed
GitLab has a massive test suite that, without [parallelization], can take hours
@@ -184,11 +194,11 @@ instead of 30+ seconds in case of a regular `spec_helper`.
### `let` variables
GitLab's RSpec suite has made extensive use of `let`(along with it strict, non-lazy
-version `let!`) variables to reduce duplication. However, this sometimes [comes at the cost of clarity][lets-not],
+version `let!`) variables to reduce duplication. However, this sometimes [comes at the cost of clarity][lets-not],
so we need to set some guidelines for their use going forward:
- `let!` variables are preferable to instance variables. `let` variables
- are preferable to `let!` variables. Local variables are preferable to
+ are preferable to `let!` variables. Local variables are preferable to
`let` variables.
- Use `let` to reduce duplication throughout an entire spec file.
- Don't use `let` to define variables used by a single test; define them as
@@ -199,8 +209,8 @@ so we need to set some guidelines for their use going forward:
- Try to avoid overriding the definition of one `let` variable with another.
- Don't define a `let` variable that's only used by the definition of another.
Use a helper method instead.
-- `let!` variables should be used only in case if strict evaluation with defined
- order is required, otherwise `let` will suffice. Remember that `let` is lazy and won't
+- `let!` variables should be used only in case if strict evaluation with defined
+ order is required, otherwise `let` will suffice. Remember that `let` is lazy and won't
be evaluated until it is referenced.
[lets-not]: https://robots.thoughtbot.com/lets-not
diff --git a/qa/docs/BEST_PRACTICES.md b/doc/development/testing_guide/end_to_end/best_practices.md
index 3a2640607e4..89500ef9a90 100644
--- a/qa/docs/BEST_PRACTICES.md
+++ b/doc/development/testing_guide/end_to_end/best_practices.md
@@ -4,7 +4,7 @@ The majority of the end-to-end tests require some state to be built in the appli
A good example is a user being logged in as a pre-condition for testing the feature.
-But if the login feature is already covered with end-to-end tests through the GUI, there is no reason to perform such an expensive task to test the functionality of creating a project, or importing a repo, even if this features depend on a user being logged in. Let's see an example to make things clear.
+But if the login feature is already covered with end-to-end tests through the GUI, there is no reason to perform such an expensive task to test the functionality of creating a project, or importing a repo, even if these features depend on a user being logged in. Let's see an example to make things clear.
Let's say that, on average, the process to perform a successful login through the GUI takes 2 seconds.
@@ -33,6 +33,6 @@ Finally, interacting with the application only by its GUI generates a higher rat
**The takeaways here are:**
- Building state through the GUI is time consuming and it's not sustainable as the test suite grows.
-- When depending only on the GUI to create the application's state and tests fail due to front-end issues, we can't rely on the test failures rate, and we generates a higher rate of test flakiness.
+- When depending only on the GUI to create the application's state and tests fail due to front-end issues, we can't rely on the test failures rate, and we generate a higher rate of test flakiness.
-Now that we are aware of all of it, [let's go create some tests](./WRITING_TESTS_FROM_SCRATCH.md).
+Now that we are aware of all of it, [let's go create some tests](quick_start_guide.md).
diff --git a/doc/development/testing_guide/end_to_end/dynamic_element_validation.md b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md
new file mode 100644
index 00000000000..f7b3ca8bc89
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md
@@ -0,0 +1,113 @@
+# Dynamic Element Validation
+
+We devised a solution to solve common test automation problems such as the dreaded `NoSuchElementException`.
+
+Other problems that dynamic element validations solve are...
+
+- When we perform an action with the mouse, we expect something to occur.
+- When our test is navigating to (or from) a page, we ensure that we are on the page we expect before
+test continuation.
+
+## How it works
+
+We interpret user actions on the page to have some sort of effect. These actions are
+
+- [Navigation](#navigation)
+- [Clicks](#clicks)
+
+### Navigation
+
+When a page is navigated to, there are elements that will always appear on the page unconditionally.
+
+Dynamic element validation is instituted when using
+
+```ruby
+Runtime::Browser.visit(:gitlab, Some::Page)
+```
+
+### Clicks
+
+When we perform a click within our tests, we expect something to occur. That something could be a component to now
+appear on the webpage, or the test to navigate away from the page entirely.
+
+Dynamic element validation is instituted when using
+
+```ruby
+click_element :my_element, Some::Page
+```
+
+### Required Elements
+
+#### Definition
+
+First it is important to define what a "required element" is.
+
+Simply put, a required element is a visible HTML element that appears on a UI component without any user input.
+
+"Visible" can be defined as
+
+- Not having any CSS preventing its display. E.g.: `display: none` or `width: 0px; height: 0px;`
+- Being able to be interacted with by the user
+
+"UI component" can be defined as
+
+- Anything the user sees
+- A button, a text field
+- A layer that sits atop the page
+
+#### Application
+
+Requiring elements is very easy. By adding `required: true` as a parameter to an `element`, you've now made it
+a requirement that the element appear on the page upon navigation.
+
+## Examples
+
+Given ...
+
+```ruby
+class MyPage < Page::Base
+ view 'app/views/view.html.haml' do
+ element :my_element, required: true
+ element :another_element, required: true
+ element :conditional_element
+ end
+
+ def open_layer
+ click_element :my_element, Layer::MyLayer
+ end
+end
+
+class Layer < Page::Component
+ view 'app/views/mylayer/layer.html.haml' do
+ element :message_content, required: true
+ end
+end
+```
+
+### Navigating
+
+Given the [source](#examples) ...
+
+```ruby
+Runtime::Browser.visit(:gitlab, Page::MyPage)
+
+execute_stuff
+```
+
+will invoke GitLab QA to scan `MyPage` for `my_element` and `another_element` to be on the page before continuing to
+`execute_stuff`
+
+### Clicking
+
+Given the [source](#examples) ...
+
+```ruby
+def open_layer
+ click_element :my_element, Layer::MyLayer
+end
+```
+
+will invoke GitLab QA to ensure that `message_content` appears on
+the Layer upon clicking `my_element`.
+
+This will imply that the Layer is indeed rendered before we continue our test.
diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end/index.md
index fc7aaedca29..afd81ff00b2 100644
--- a/doc/development/testing_guide/end_to_end_tests.md
+++ b/doc/development/testing_guide/end_to_end/index.md
@@ -65,7 +65,7 @@ Below you can read more about how to use it and how does it work.
Currently, we are using _multi-project pipeline_-like approach to run QA
pipelines.
-![QA on merge requests CI/CD architecture](img/qa_on_merge_requests_cicd_architecture.png)
+![QA on merge requests CI/CD architecture](../img/qa_on_merge_requests_cicd_architecture.png)
<details>
<summary>Show mermaid source</summary>
@@ -136,6 +136,12 @@ Once you decided where to put [test environment orchestration scenarios] and
the [GitLab QA orchestrator README][gitlab-qa-readme], and [the already existing
instance-level scenarios][instance-level scenarios].
+Continued reading:
+
+- [Quick Start Guide](quick_start_guide.md)
+- [Style Guide](style_guide.md)
+- [Best Practices](best_practices.md)
+
## Where can I ask for help?
You can ask question in the `#quality` channel on Slack (GitLab internal) or
@@ -149,7 +155,7 @@ you can find an issue you would like to work on in
[gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md
[quality-nightly-pipelines]: https://gitlab.com/gitlab-org/quality/nightly/pipelines
[quality-staging-pipelines]: https://gitlab.com/gitlab-org/quality/staging/pipelines
-[review-apps]: ./review_apps.md
+[review-apps]: ../review_apps.md
[gitlab-qa-architecture]: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/architecture.md
[gitlab-qa-issues]: https://gitlab.com/gitlab-org/gitlab-qa/issues?label_name%5B%5D=new+scenario
[gitlab-ce-issues]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name[]=QA&label_name[]=test
diff --git a/qa/qa/page/README.md b/doc/development/testing_guide/end_to_end/page_objects.md
index d0de33892c4..73e1fd862c1 100644
--- a/qa/qa/page/README.md
+++ b/doc/development/testing_guide/end_to_end/page_objects.md
@@ -82,15 +82,17 @@ module Page
end
# ...
+ end
end
end
```
-The `view` DSL method declares the filename of the view where an
-`element` is implemented.
+### Defining Elements
+
+The `view` DSL method will correspond to the rails View, partial, or vue component that renders the elements.
The `element` DSL method in turn declares an element for which a corresponding
-`qa-element-name-dasherized` CSS class need to be added to the view file.
+`qa-element-name-dasherized` CSS class will need to be added to the view file.
You can also define a value (String or Regexp) to match to the actual view
code but **this is deprecated** in favor of the above method for two reasons:
@@ -115,6 +117,37 @@ view 'app/views/my/view.html.haml' do
end
```
+### Adding Elements to a View
+
+Given the following elements...
+
+```ruby
+view 'app/views/my/view.html.haml' do
+ element :login_field
+ element :password_field
+ element :sign_in_button
+end
+```
+
+To add these elements to the view, you must change the rails View, partial, or vue component by adding a `qa-element-descriptor` class
+for each element defined.
+
+In our case, `qa-login-field`, `qa-password-field` and `qa-sign-in-button`
+
+**app/views/my/view.html.haml**
+
+```haml
+= f.text_field :login, class: "form-control top qa-login-field", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
+= f.password_field :password, class: "form-control bottom qa-password-field", required: true, title: "This field is required."
+= f.submit "Sign in", class: "btn btn-success qa-sign-in-button"
+```
+
+Things to note:
+
+- The CSS class must be `kebab-cased` (separated with hyphens "`-`")
+- If the element appears on the page unconditionally, add `required: true` to the element. See
+[Dynamic element validation](dynamic_element_validation.md)
+
## Running the test locally
During development, you can run the `qa:selectors` test by running
diff --git a/doc/development/testing_guide/end_to_end/quick_start_guide.md b/doc/development/testing_guide/end_to_end/quick_start_guide.md
new file mode 100644
index 00000000000..afe76acf9c9
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/quick_start_guide.md
@@ -0,0 +1,585 @@
+# Writing end-to-end tests step-by-step
+
+In this tutorial, you will find different examples, and the steps involved, in the creation of end-to-end (_e2e_) tests for GitLab CE and GitLab EE, using GitLab QA.
+
+> When referring to end-to-end tests in this document, this means testing a specific feature end-to-end, such as a user logging in, the creation of a project, the management of labels, breaking down epics into sub-epics and issues, etc.
+
+## Important information before we start writing tests
+
+It's important to understand that end-to-end tests of isolated features, such as the ones described in the above note, doesn't mean that everything needs to happen through the GUI.
+
+If you don't exactly understand what we mean by **not everything needs to happen through the GUI,** please make sure you've read the [best practices](best_practices.md) before moving on.
+
+## This document covers the following items:
+
+- [0.](#0-are-end-to-end-tests-needed) Identifying if end-to-end tests are really needed
+- [1.](#1-identifying-the-devops-stage) Identifying the [DevOps stage](https://about.gitlab.com/stages-devops-lifecycle/) of the feature that you are going to cover with end-to-end tests
+- [2.](#2-test-skeleton) Creating the skeleton of the test file (`*_spec.rb`)
+- [3.](#3-test-cases-mvc) The [MVC](https://about.gitlab.com/handbook/values/#minimum-viable-change-mvc) of the test cases' logic
+- [4.](#4-extracting-duplicated-code) Extracting duplicated code into methods
+- [5.](#5-tests-pre-conditions-using-resources-and-page-objects) Tests' pre-conditions (`before :context` and `before`) using resources and [Page Objects]
+- [6.](#6-optimization) Optimizing the test suite
+- [7.](#7-resources) Using and implementing resources
+- [8.](#8-page-objects) Moving element definitions and methods to [Page Objects]
+
+### 0. Are end-to-end tests needed?
+
+At GitLab we respect the [test pyramid](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/testing_guide/testing_levels.md), and so, we recommend you check the code coverage of a specific feature before writing end-to-end tests, for both [CE](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby/#_AllFiles) and [EE](https://gitlab-org.gitlab.io/gitlab-ee/coverage-ruby/#_AllFiles) projects.
+
+Sometimes you may notice that there is already good coverage in other test levels, and we can stay confident that if we break a feature, we will still have quick feedback about it, even without having end-to-end tests.
+
+If after this analysis you still think that end-to-end tests are needed, keep reading.
+
+### 1. Identifying the DevOps stage
+
+The GitLab QA end-to-end tests are organized by the different [stages in the DevOps lifecycle](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/specs/features/browser_ui), and so, if you are creating tests for issue creation, for instance, you would locate the spec files under the `qa/qa/specs/features/browser_ui/2_plan/` directory since issue creation is part of the Plan stage.
+
+ In another case of a test for listing merged merge requests (MRs), the test should go under the `qa/qa/specs/features/browser_ui/3_create/` directory since merge requests are a feature from the Create stage.
+
+> There may be sub-directories inside the stages directories, for different features. For example: `.../browser_ui/2_plan/ee_epics/` and `.../browser_ui/2_plan/issues/`.
+
+Now, let's say we want to create tests for the [scoped labels](https://about.gitlab.com/2019/04/22/gitlab-11-10-released/#scoped-labels) feature, available on GitLab EE Premium (this feature is part of the Plan stage.)
+
+> Because these tests are for a feature available only on GitLab EE, we need to create them in the [EE repository](https://gitlab.com/gitlab-org/gitlab-ee).
+
+Since [there is no specific directory for this feature](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/qa/qa/specs/features/browser_ui/2_plan), we should create a sub-directory for it.
+
+Under `.../browser_ui/2_plan/`, let's create a sub-directory called `ee_scoped_labels/`.
+
+> Notice that since this feature is only available for GitLab EE we prefix the sub-directory with `ee_`.
+
+### 2. Test skeleton
+
+Inside the newly created sub-directory, let's create a file describing the test suite (e.g. `editing_scoped_labels_spec.rb`.)
+
+#### The `context` and `describe` blocks
+
+Specs have an outer `context` that indicates the DevOps stage. The next level is the `describe` block, that briefly states the subject of the test suite. See the following example:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ end
+ end
+end
+```
+
+#### The `it` blocks
+
+Every test suite is composed of at least one `it` block, and a good way to start writing end-to-end tests is by writing test cases descriptions as `it` blocks. These might help you to think of different test scenarios. Take a look at the following example:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ it 'replaces an existing label if it has the same key' do
+ end
+
+ it 'keeps both scoped labels when adding a label with a different key' do
+ end
+ end
+ end
+end
+```
+
+### 3. Test cases MVC
+
+For the [MVC](https://about.gitlab.com/handbook/values/#minimum-viable-change-mvc) of our test cases, let's say that we already have the application in the state needed for the tests, and then let's focus on the logic of the test cases only.
+
+To evolve the test cases drafted on step 2, let's imagine that the user is already logged into a GitLab EE instance, they already have at least a Premium license in use, there is already a project created, there is already an issue opened in the project, the issue already has a scoped label (e.g. `animal::fox`), there are other scoped labels (for the same scope and for a different scope (e.g. `animal::dolphin` and `plant::orchid`), and finally, the user is already on the issue's page. Let's also suppose that for every test case the application is in a clean state, meaning that one test case won't affect another.
+
+> Note: there are different approaches to creating an application state for end-to-end tests. Some of them are very time consuming and subject to failures, such as when using the GUI for all the pre-conditions of the tests. On the other hand, other approaches are more efficient, such as using the public APIs. The latter is more efficient since it doesn't depend on the GUI. We won't focus on this part yet, but it's good to keep it in mind.
+
+Let's now focus on the first test case.
+
+```ruby
+it 'replaces an existing label if it has the same key' do
+ # This implementation is only for tutorial purposes. We normally encapsulate elements in Page Objects (which we cover on section 8).
+ page.find('.block.labels .edit-link').click
+ page.find('.dropdown-menu-labels .dropdown-input-field').send_keys ['animal::dolphin', :enter]
+ page.find('#content-body').click
+ page.refresh
+
+ labels_block = page.find('.qa-labels-block')
+
+ expect(labels_block).to have_content('animal::dolphin')
+ expect(labels_block).not_to have_content('animal::fox')
+ expect(page).to have_content('added animal::dolphin label and removed animal::fox')
+end
+```
+
+> Notice that the test itself is simple. The most challenging part is the creation of the application state, which will be covered later.
+
+> The exemplified test case's MVC is not enough for the change to be merged, but it helps to build up the test logic. The reason is that we do not want to use locators directly in the tests, and tests **must** use [Page Objects] before they can be merged. This way we better separate the responsibilities, where the Page Objects encapsulate elements and methods that allow us to interact with pages, while the spec files describe the test cases in more business-related language.
+
+Below are the steps that the test covers:
+
+1. The test finds the 'Edit' link for the labels and clicks on it.
+2. Then it fills in the 'Assign labels' input field with the value 'animal::dolphin' and press enters.
+3. Then it clicks in the content body to apply the label and refreshes the page.
+4. Finally, the expectations check that the previous scoped label was removed and that the new one was added.
+
+Let's now see how the second test case would look.
+
+```ruby
+it 'keeps both scoped labels when adding a label with a different key' do
+ # This implementation is only for tutorial purposes. We normally encapsulate elements in Page Objects (which we cover on section 8).
+ page.find('.block.labels .edit-link').click
+ page.find('.dropdown-menu-labels .dropdown-input-field').send_keys ['plant::orchid', :enter]
+ page.find('#content-body').click
+ page.refresh
+
+ labels_block = page.find('.qa-labels-block')
+
+ expect(labels_block).to have_content('animal::fox')
+ expect(labels_block).to have_content('plant::orchid')
+ expect(page).to have_content('added animal::fox')
+ expect(page).to have_content('added plant::orchid')
+end
+```
+
+> Note that elements are always located using CSS selectors, and a good practice is to add test-specific selectors (this is called adding testability to the application and we will talk more about it later.) For example, the `labels_block` element uses the selector `.qa-labels-block`, which was added specifically for testing purposes.
+
+Below are the steps that the test covers:
+
+1. The test finds the 'Edit' link for the labels and clicks on it.
+2. Then it fills in the 'Assign labels' input field with the value 'plant::orchid' and press enters.
+3. Then it clicks in the content body to apply the label and refreshes the page.
+4. Finally, the expectations check that both scoped labels are present.
+
+> Similar to the previous test, this one is also very straightforward, but there is some code duplication. Let's address it.
+
+### 4. Extracting duplicated code
+
+If we refactor the tests created on step 3 we could come up with something like this:
+
+```ruby
+before do
+ ...
+
+ @initial_label = 'animal::fox'
+ @new_label_same_scope = 'animal::dolphin'
+ @new_label_different_scope = 'plant::orchid'
+
+ ...
+end
+
+it 'replaces an existing label if it has the same key' do
+ select_label_and_refresh @new_label_same_scope
+
+ labels_block = page.find('.qa-labels-block')
+
+ expect(labels_block).to have_content(@new_label_same_scope)
+ expect(labels_block).not_to have_content(@initial_label)
+ expect(page).to have_content("added #{@new_label_same_scope}")
+ expect(page).to have_content("and removed #{@initial_label}")
+end
+
+it 'keeps both scoped label when adding a label with a different key' do
+ select_label_and_refresh @new_label_different_scope
+
+ labels_block = page.find('.qa-labels-block')
+
+ expect(labels_blocks).to have_content(@new_label_different_scope)
+ expect(labels_blocks).to have_content(@initial_label)
+ expect(page).to have_content("added #{@new_label_different_scope}")
+ expect(page).to have_content("added #{@initial_label}")
+end
+
+def select_label_and_refresh(label)
+ page.find('.block.labels .edit-link').click
+ page.find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]
+ page.find('#content-body').click
+ page.refresh
+end
+```
+
+First, we remove the duplication of strings by defining the global variables `@initial_label`, `@new_label_same_scope` and `@new_label_different_scope` in the `before` block, and by using them in the expectations.
+
+Then, by creating a reusable `select_label_and_refresh` method, we remove the code duplication of this action, and later we can move this method to a Page Object class that will be created for easier maintenance purposes.
+
+> Notice that the reusable method is created at the bottom of the file. The reason for that is that reading the code should be similar to reading a newspaper, where high-level information is at the top, like the title and summary of the news, while low level, or more specific information, is at the bottom (this helps readability).
+
+### 5. Tests' pre-conditions using resources and Page Objects
+
+In this section, we will address the previously mentioned subject of creating the application state for the tests, using the `before :context` and `before` blocks, together with resources and Page Objects.
+
+#### `before :context`
+
+A pre-condition for the entire test suite is defined in the `before :context` block.
+
+> For our test suite, due to the need of the tests being completely independent of each other, we won't use the `before :context` block. The `before :context` block would make the tests dependent on each other because the first test changes the label of the issue, and the second one depends on the `'animal::fox'` label being set.
+
+> **Tip:** In case of a test suite with only one `it` block it's ok to use only the `before` block (see below) with all the test's pre-conditions.
+
+#### `before`
+
+As the pre-conditions for our test suite, the things that needs to happen before each test starts are:
+
+- The user logging in;
+- A premium license already being set;
+- A project being created with an issue and labels already set;
+- The issue page being opened with only one scoped label applied to the it.
+
+> When running end-to-end tests as part of the GitLab's continuous integration process [a license is already set as an environment variable](https://gitlab.com/gitlab-org/gitlab-ee/blob/1a60d926740db10e3b5724713285780a4f470531/qa/qa/ee/strategy.rb#L20). For running tests locally you can set up such license by following the document [what tests can be run?](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-remote-grid-environment-variables), based on the [supported GitLab environment variables](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables).
+
+#### Implementation
+
+In the following code we will focus only on the test suite's pre-conditions:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ @initial_label = 'animal::fox'
+ @new_label_same_scope = 'animal::dolphin'
+ @new_label_different_scope = 'plant::orchid'
+
+ issue = Resource::Issue.fabricate_via_api! do |issue|
+ issue.title = 'Issue to test the scoped labels'
+ issue.labels = @initial_label
+ end
+
+ [@new_label_same_scope, @new_label_different_scope].each do |label|
+ Resource::Label.fabricate_via_api! do |l|
+ l.project = issue.project.id
+ l.title = label
+ end
+ end
+
+ issue.visit!
+ end
+
+ it 'replaces an existing label if it has the same key' do
+ ...
+ end
+
+ it 'keeps both scoped labels when adding a label with a different key' do
+ ...
+ end
+
+ def select_label_and_refresh(label)
+ ...
+ end
+ end
+ end
+end
+```
+
+In the `before` block we create all the application state needed for the tests to run. We do that by using the `Runtime::Browser.visit` method to go to the login page, by performing a `sign_in_using_credentials` from the `Login` Page Object, by fabricating resources via APIs (`issue`, and `Resource::Label`), and by using the `issue.visit!` to visit the issue page.
+
+> A project is created in the background by creating the `issue` resource.
+
+> When creating the [Resources], notice that when calling the `fabricate_via_api` method, we pass some attribute:values, like `title`, and `labels` for the `issue` resource; and `project` and `title` for the `label` resource.
+
+> What's important to understand here is that by creating the application state mostly using the public APIs we save a lot of time in the test suite setup stage.
+
+> Soon we will cover the use of the already existing resources' methods and the creation of your own `fabricate_via_api` methods for resources where this is still not available, but first, let's optimize our implementation.
+
+### 6. Optimization
+
+As already mentioned in the [best practices](best_practices.md) document, end-to-end tests are very costly in terms of execution time, and it's our responsibility as software engineers to ensure that we optimize them as much as possible.
+
+> Note that end-to-end tests are slow to run and so they can have several actions and assertions in a single test, which helps us get feedback from the tests sooner. In comparison, unit tests are much faster to run and can exercise every little piece of the application in isolation, and so they usually have only one assertion per test.
+
+Some improvements that we could make in our test suite to optimize its time to run are:
+
+1. Having a single test case (an `it` block) that exercises both scenarios to avoid "wasting" time in the tests' pre-conditions, instead of having two different test cases.
+2. Making the selection of labels more performant by allowing for the selection of more than one label in the same reusable method.
+
+Let's look at a suggestion that addresses the above points, one by one:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ before do
+ ...
+ end
+
+ it 'correctly applies scoped labels depending on if they are from the same or a different scope' do
+ select_labels_and_refresh [@new_label_same_scope, @new_label_different_scope]
+
+ labels_block = page.all('.qa-labels-block')
+
+ expect(labels_block).to have_content(@new_label_same_scope)
+ expect(labels_block).to have_content(@new_label_different_scope)
+ expect(labels_block).not_to have_content(@initial_label)
+ expect(page).to have_content("added #{@initial_label}")
+ expect(page).to have_content("added #{@new_label_same_scope} #{@new_label_different_scope} labels and removed #{@initial_label}")
+ end
+
+ def select_labels_and_refresh(labels)
+ find('.block.labels .edit-link').click
+ labels.each do |label|
+ find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]
+ end
+ find('#content-body').click
+ refresh
+ end
+ end
+ end
+ end
+```
+
+To address point 1, we changed the test implementation from two `it` blocks into a single one that exercises both scenarios. Now the new test description is: `'correctly applies the scoped labels depending if they are from the same or a different scope'`. It's a long description, but it describes well what the test does.
+
+> Notice that the implementation of the new and unique `it` block had to change a little bit. Below we describe in details what it does.
+
+1. It selects two scoped labels simultaneously, one from the same scope of the one already applied in the issue during the setup phase (in the `before` block), and another one from a different scope.
+2. It asserts that the correct labels are visible in the `labels_block`, and that the labels were correctly added and removed;
+3. Finally, the `select_label_and_refresh` method is changed to `select_labels_and_refresh`, which accepts an array of labels instead of a single label, and it iterates on them for faster label selection (this is what is used in step 1 explained above.)
+
+### 7. Resources
+
+**Note:** When writing this document, some code that is now merged to master was not implemented yet, but we left them here for the readers to understand the whole process of end-to-end test creation.
+
+You can think of [Resources] as anything that can be created on GitLab CE or EE, either through the GUI, the API, or the CLI.
+
+With that in mind, resources can be a project, an epic, an issue, a label, a commit, etc.
+
+As you saw in the tests' pre-conditions and the optimization sections, we're already creating some of these resources, and we are doing that by calling the `fabricate_via_api!` method.
+
+> We could be using the `fabricate!` method instead, which would use the `fabricate_via_api!` method if it exists, and fallback to GUI fabrication otherwise, but we recommend being explicit to make it clear what the test does. Also, we always recommend fabricating resources via API since this makes tests faster and more reliable.
+
+For our test suite example, the resources that we need to create don't have the necessary code for the `fabricate_via_api!` method to correctly work (e.g., the issue and label resources), so we will have to create them.
+
+#### Implementation
+
+In the following we describe the changes needed in each of the resource files mentioned above.
+
+**Issue resource**
+
+Now, let's make it possible to create an issue resource through the API.
+
+First, in the [issue resource](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb), let's expose its labels attribute.
+
+Add the following `attribute :labels` right below the [`attribute :title`](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb#L15).
+
+> This line is needed to allow for labels to be automatically added to an issue when fabricating it via API.
+
+Next, add the following code right below the [`fabricate!`](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb#L27) method.
+
+```ruby
+def api_get_path
+ "/projects/#{project.id}/issues/#{id}"
+end
+
+def api_post_path
+ "/projects/#{project.id}/issues"
+end
+
+def api_post_body
+ {
+ title: title,
+ labels: [labels]
+ }
+end
+```
+
+By defining the `api_get_path` method, we allow the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to get a single issue.
+
+> This `GET` path can be found in the [public API documentation](https://docs.gitlab.com/ee/api/issues.html#single-issue).
+
+By defining the `api_post_path` method, we allow the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to create a new issue in a specific project.
+
+> This `POST` path can be found in the [public API documentation](https://docs.gitlab.com/ee/api/issues.html#new-issue).
+
+By defining the `api_post_body` method, we allow the [`ApiFabricator.api_post`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/api_fabricator.rb#L68) method to know which data to send when making the `POST` request.
+
+> Notice that we pass both `title` and `labels` attributes in the `api_post_body`, where `labels` receives an array of labels, and [`title` is required](https://docs.gitlab.com/ee/api/issues.html#new-issue).
+
+**Label resource**
+
+Finally, let's make it possible to create label resources through the API.
+
+Add the following code right below the [`fabricate!`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/label.rb#L36) method.
+
+```ruby
+def resource_web_url(resource)
+ super
+rescue ResourceURLMissingError
+ # this particular resource does not expose a web_url property
+end
+
+def api_get_path
+ raise NotImplementedError, "The Labels API doesn't expose a single-resource endpoint so this method cannot be properly implemented."
+end
+
+def api_post_path
+ "/projects/#{project}/labels"
+end
+
+def api_post_body
+ {
+ name: @title,
+ color: @color
+ }
+end
+```
+
+By defining the `resource_web_url(resource)` method, we override the one from the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb#L44) module. We do that to avoid failing the test due to this particular resource not exposing a `web_url` property.
+
+By defining the `api_get_path` method, we **would** allow for the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to get a single label, but since there's no path available for that in the publich API, we raise a `NotImplementedError` instead.
+
+By defining the `api_post_path` method, we allow for the [`ApiFabricator `](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to create a new label in a specific project.
+
+By defining the `api_post_body` method, we we allow for the [`ApiFabricator.api_post`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/api_fabricator.rb#L68) method to know which data to send when making the `POST` request.
+
+> Notice that we pass both `name` and `color` attributes in the `api_post_body` since [those are required](https://docs.gitlab.com/ee/api/labels.html#create-a-new-label).
+
+### 8. Page Objects
+
+Page Objects are used in end-to-end tests for maintenance reasons, where a page's elements and methods are defined to be reused in any test.
+
+> Page Objects are auto-loaded in the `qa/qa.rb` file and available in all the test files (`*_spec.rb`).
+
+Take a look at the [Page Objects] documentation.
+
+Now, let's go back to our example.
+
+As you may have noticed, we are defining elements with CSS selectors and the `select_labels_and_refresh` method directly in the test file, and this is an anti-pattern since we need to better separate the responsibilities.
+
+To address this issue, we will move the implementation to Page Objects, and the test suite will only focus on the business rules that we are testing.
+
+#### Updates in the test file
+
+As in a test-driven development approach, let's start changing the test file even before the Page Object implementation is in place.
+
+Replace the code of the `it` block in the test file by the following:
+
+```ruby
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ before do
+ ...
+ end
+
+ it 'correctly applies scoped labels depending on if they are from the same or a different scope' do
+ Page::Project::Issue::Show.perform do |issue_page|
+ issue_page.select_labels_and_refresh [@new_label_same_scope, @new_label_different_scope]
+
+ expect(page).to have_content("added #{@initial_label}")
+ expect(page).to have_content("added #{@new_label_same_scope} #{@new_label_different_scope} labels and removed #{@initial_label}")
+ expect(issue_page.text_of_labels_block).to have_content(@new_label_same_scope)
+ expect(issue_page.text_of_labels_block).to have_content(@new_label_different_scope)
+ expect(issue_page.text_of_labels_block).not_to have_content(@initial_label)
+ end
+ end
+ end
+ end
+end
+```
+
+Notice that `select_labels_and_refresh` is now a method from the issue Page Object (which is not yet implemented), and that we verify the labels' text by using `text_of_labels_block`, instead of via the `labels_block` element. The `text_of_labels_block` method will also be implemented in the issue Page Object.
+
+Let's now update the Issue Page Object.
+
+#### Updates in the Issue Page Object
+
+> Page Objects are located in the `qa/qa/page/` directory, and its sub-directories.
+
+The file we will have to change is the [Issue Page Object](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/page/project/issue/show.rb).
+
+First, add the following code right below the definition of an already implemented view:
+
+```ruby
+view 'app/views/shared/issuable/_sidebar.html.haml' do
+ element :labels_block
+ element :edit_link_labels
+ element :dropdown_menu_labels
+end
+
+view 'app/helpers/dropdowns_helper.rb' do
+ element :dropdown_input_field
+end
+```
+
+Similarly to what we did before, let's first change the Page Object even without the elements being defined in the view (`_sidebar.html.haml`) and the `dropdowns_helper.rb` files, and later we will update them by adding the appropriate CSS selectors.
+
+Now, let's implement the methods `select_labels_and_refresh` and `text_of_labels_block`.
+
+Somewhere between the definition of the views and the private methods, add the following snippet of code:
+
+```ruby
+def select_labels_and_refresh(labels)
+ click_element(:edit_link_labels)
+ labels.each do |label|
+ within_element(:dropdown_menu_labels, text: label) do
+ send_keys_to_element(:dropdown_input_field, [label, :enter])
+ end
+ end
+ click_body
+ labels.each do |label|
+ has_element?(:labels_block, text: label)
+ end
+ refresh
+end
+
+def text_of_labels_block
+ find_element(:labels_block)
+end
+```
+
+##### Details of `select_labels_and_refresh`
+
+Notice that we have not only moved the `select_labels_and_refresh` method, but we have also changed its implementation to:
+1. Click the `:edit_link_labels` element previously defined, instead of using `find('.block.labels .edit-link').click`
+2. Use `within_element(:dropdown_menu_labels, text: label)`, and inside of it, we call `send_keys_to_element(:dropdown_input_field, [label, :enter])`, which is a method that we will implement in the `QA::Page::Base` class to replace `find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]`
+3. Use `click_body` after iterating on each label, instead of using `find('#content-body').click`
+4. Iterate on every label again, and then we use `has_element?(:labels_block, text: label)` after clicking the page body (which applies the labels), and before refreshing the page, to avoid test flakiness due to refreshing too fast.
+
+##### Details of `text_of_labels_block`
+
+The `text_of_labels_block` method is a simple method that returns the `:labels_block` element (`find_element(:labels_block)`).
+
+#### Updates in the view (*.html.haml) and `dropdowns_helper.rb` files
+
+Now let's change the view and the `dropdowns_helper` files to add the selectors that relate to the Page Object.
+
+In the [app/views/shared/issuable/_sidebar.html.haml](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/views/shared/issuable/_sidebar.html.haml) file, on [line 105 ](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L105), add an extra class `qa-edit-link-labels`.
+
+The code should look like this: `= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link qa-edit-link-labels float-right'`.
+
+In the same file, on [line 121](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L121), add an extra class `.qa-dropdown-menu-labels`.
+
+The code should look like this: `.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.qa-dropdown-menu-labels.dropdown-menu-selectable`.
+
+In the [`dropdowns_helper.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/helpers/dropdowns_helper.rb) file, on [line 94](https://gitlab.com/gitlab-org/gitlab-ee/blob/99e51a374f2c20bee0989cac802e4b5621f72714/app/helpers/dropdowns_helper.rb#L94), add an extra class `qa-dropdown-input-field`.
+
+The code should look like this: `filter_output = search_field_tag search_id, nil, class: "dropdown-input-field qa-dropdown-input-field", placeholder: placeholder, autocomplete: 'off'`.
+
+> Classes starting with `qa-` are used for testing purposes only, and by defining such classes in the elements we add **testability** in the application.
+
+> When defining a class like `qa-labels-block`, it is transformed into `:labels_block` for usage in the Page Objects. So, `qa-edit-link-labels` is tranformed into `:edit_link_labels`, `qa-dropdown-menu-labels` is transformed into `:dropdown_menu_labels`, and `qa-dropdown-input-field` is transformed into `:dropdown_input_field`. Also, we use a [sanity test](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/page#how-did-we-solve-fragile-tests-problem) to check that defined elements have their respective `qa-` selectors in the specified views.
+
+> We did not define the `qa-labels-block` class in the `app/views/shared/issuable/_sidebar.html.haml` file because it was already there to be used.
+
+#### Updates in the `QA::Page::Base` class
+
+The last thing that we have to do is to update `QA::Page::Base` class to add the `send_keys_to_element` method on it.
+
+Add the following snippet of code somewhere where class methods are defined:
+
+```ruby
+def send_keys_to_element(name, keys)
+ find_element(name).send_keys(keys)
+end
+```
+
+This method receives an element (`name`) and the `keys` that it will send to that element, and the keys are an array that can receive strings, or "special" keys, like `:enter`.
+
+As you might remember, in the Issue Page Object we call this method like this: `send_keys_to_element(:dropdown_input_field, [label, :enter])`.
+
+___
+
+With that, you should be able to start writing end-to-end tests yourself. *Congratulations!*
+
+[Page Objects]: page_objects.md
+[Resources]: resources.md
diff --git a/qa/qa/resource/README.md b/doc/development/testing_guide/end_to_end/resources.md
index 2c8859b6599..1e32db4f633 100644
--- a/qa/qa/resource/README.md
+++ b/doc/development/testing_guide/end_to_end/resources.md
@@ -5,11 +5,11 @@ be created via the API or the CLI.
## How to properly implement a resource class?
-All resource classes should inherit from [`Resource::Base`](./base.rb).
+All resource classes should inherit from `Resource::Base`.
There is only one mandatory method to implement to define a resource class.
This is the `#fabricate!` method, which is used to build the resource via the
-browser UI. Note that you should only use [Page objects](../page/README.md) to
+browser UI. Note that you should only use [Page objects](page_objects.md) to
interact with a Web page in this method.
Here is an imaginary example:
@@ -74,7 +74,7 @@ module QA
end
```
-The [`Project` resource](./project.rb) is a good real example of Browser
+The `Project` resource is a good real example of Browser
UI and API implementations.
#### Resource attributes
diff --git a/doc/development/testing_guide/end_to_end/style_guide.md b/doc/development/testing_guide/end_to_end/style_guide.md
new file mode 100644
index 00000000000..0272e1810f2
--- /dev/null
+++ b/doc/development/testing_guide/end_to_end/style_guide.md
@@ -0,0 +1,99 @@
+# Style guide for writing end-to-end tests
+
+This document describes the conventions used at GitLab for writing End-to-end (E2E) tests using the GitLab QA project.
+
+## `click_` versus `go_to_`
+
+### When to use `click_`?
+
+When clicking in a single link to navigate, use `click_`.
+
+E.g.:
+
+```ruby
+def click_ci_cd_pipelines
+ within_sidebar do
+ click_element :link_pipelines
+ end
+end
+```
+
+From a testing perspective, if we want to check that clicking a link, or a button (a single interaction) is working as intended, we would want the test to read as:
+
+- Click a certain element
+- Verify the action took place
+
+### When to use `go_to_`?
+
+When interacting with multiple elements to go to a page, use `go_to_`.
+
+E.g.:
+
+```ruby
+def go_to_operations_environments
+ hover_operations do
+ within_submenu do
+ click_element(:operations_environments_link)
+ end
+ end
+end
+```
+
+`go_to_` fits the definition of interacting with multiple elements very well given it's more of a meta-navigation action that includes multiple interactions.
+
+Notice that in the above example, before clicking the `:operations_environments_link`, another element is hovered over.
+
+> We can create these methods as helpers to abstract multi-step navigation.
+
+### Element naming convention
+
+When adding new elements to a page, it's important that we have a uniform element naming convention.
+
+We follow a simple formula roughly based on hungarian notation.
+
+*Formula*: `element :<descriptor>_<type>`
+
+- `descriptor`: The natural-language description of what the element is. On the login page, this could be `username`, or `password`.
+- `type`: A physical control on the page that can be seen by a user.
+ - `_button`
+ - `_link`
+ - `_tab`
+ - `_dropdown`
+ - `_field`
+ - `_checkbox`
+ - `_radio`
+ - `_content`
+
+*Note: This list is a work in progress. This list will eventually be the end-all enumeration of all available types.
+ I.e., any element that does not end with something in this list is bad form.*
+
+#### Examples
+
+**Good**
+
+```ruby
+view '...' do
+ element :edit_button
+ element :notes_tab
+ element :squash_checkbox
+ element :username_field
+ element :issue_title_content
+end
+```
+
+**Bad**
+
+```ruby
+view '...' do
+ # `_confirmation` should be `_field`. what sort of confirmation? a checkbox confirmation? no real way to disambiguate.
+ # an appropriate replacement would be `element :password_confirmation_field`
+ element :password_confirmation
+
+ # `clone_options` is too vague. If it's a dropdown menu, it should be `clone_dropdown`.
+ # If it's a checkbox, it should be `clone_checkbox`
+ element :clone_options
+
+ # how is this url being displayed? is it a textbox? a simple span?
+ element :ssh_clone_url
+end
+```
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 58d6f08954d..4c9d1684c00 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -67,9 +67,6 @@ Remember that the performance of each test depends on the environment.
GitLab uses the [Karma][karma] test runner with [Jasmine] as its test
framework for our JavaScript unit and integration tests.
-We generate HTML and JSON fixtures from backend views and controllers
-using RSpec (see `spec/javascripts/fixtures/*.rb` for examples).
-Fixtures are served during testing by the [jasmine-jquery][jasmine-jquery] plugin.
JavaScript tests live in `spec/javascripts/`, matching the folder structure
of `app/assets/javascripts/`: `app/assets/javascripts/behaviors/autosize.js`
@@ -421,7 +418,7 @@ See this [section][vue-test].
For running the frontend tests, you need the following commands:
-- `rake karma:fixtures` (re-)generates fixtures.
+- `rake karma:fixtures` (re-)generates [fixtures](#frontend-test-fixtures).
- `yarn test` executes the tests.
As long as the fixtures don't change, `yarn test` is sufficient (and saves you some time).
@@ -469,6 +466,48 @@ yarn karma -f 'spec/javascripts/ide/**/file_spec.js'
Information on setting up and running RSpec integration tests with
[Capybara] can be found in the [Testing Best Practices](best_practices.md).
+## Frontend test fixtures
+
+Code that is added to HAML templates (in `app/views/`) or makes Ajax requests to the backend has tests that require HTML or JSON from the backend.
+Fixtures for these tests are located at:
+
+- `spec/javascripts/fixtures/`, for running tests in CE.
+- `ee/spec/javascripts/fixtures/`, for running tests in EE.
+
+Fixture files in:
+
+- The Karma test suite are served by [jasmine-jquery](https://github.com/velesin/jasmine-jquery).
+- Jest use `spec/frontend/helpers/fixtures.js`.
+
+The following are examples of tests that work for both Karma and Jest:
+
+```javascript
+it('makes a request', () => {
+ const responseBody = getJSONFixture('some/fixture.json'); // loads spec/javascripts/fixtures/some/fixture.json
+ axiosMock.onGet(endpoint).reply(200, responseBody);
+
+ myButton.click();
+
+ // ...
+});
+
+it('uses some HTML element', () => {
+ loadFixtures('some/page.html'); // loads spec/javascripts/fixtures/some/page.html and adds it to the DOM
+
+ const element = document.getElementById('#my-id');
+
+ // ...
+});
+```
+
+HTML and JSON fixtures are generated from backend views and controllers using RSpec (see `spec/javascripts/fixtures/*.rb`).
+
+For each fixture, the content of the `response` variable is stored in the output file.
+This variable gets automagically set if the test is marked as `type: :request` or `type: :controller`.
+Fixtures are regenerated using the `bin/rake karma:fixtures` command but you can also generate them individually,
+for example `bin/rspec spec/javascripts/fixtures/merge_requests.rb`.
+When creating a new fixture, it often makes sense to take a look at the corresponding tests for the endpoint in `(ee/)spec/controllers/` or `(ee/)spec/requests/`.
+
## Gotchas
### RSpec errors due to JavaScript
@@ -501,7 +540,6 @@ end
```
[jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html
-[jasmine-jquery]: https://github.com/velesin/jasmine-jquery
[karma]: http://karma-runner.github.io/
[vue-test]: https://docs.gitlab.com/ce/development/fe_guide/vue.html#testing-vue-components
[rspec]: https://github.com/rspec/rspec-rails#feature-specs
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index ecad9ba48a3..93ee2a6371a 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -71,7 +71,7 @@ Everything you should know about how to test Rake tasks.
---
-## [End-to-end tests](end_to_end_tests.md)
+## [End-to-end tests](end_to_end/index.md)
Everything you should know about how to run end-to-end tests using
[GitLab QA][gitlab-qa] testing framework.
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index f34793c11f4..b5fde92a23d 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -113,7 +113,7 @@ You can also manually start the `review-qa-all`: it runs the full QA suite.
On every [pipeline][gitlab-pipeline] in the `qa` stage, the
`review-performance` job is automatically started: this job does basic
browser performance testing using a
-[Sitespeed.io Container](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
+[Sitespeed.io Container](../../user/project/merge_requests/browser_performance_testing.md).
## How to:
@@ -203,7 +203,7 @@ find a way to limit it to only us.**
[automated_cleanup.rb]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/review_apps/automated_cleanup.rb
[Auto-DevOps.gitlab-ci.yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
[gitlab-ci-yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml
-[gitlab-k8s-integration]: https://docs.gitlab.com/ee/user/project/clusters/index.html
+[gitlab-k8s-integration]: ../../user/project/clusters/index.md
[password-bug]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53621
---
diff --git a/doc/development/testing_guide/smoke.md b/doc/development/testing_guide/smoke.md
index 30d861d7d68..c9d3238fbe9 100644
--- a/doc/development/testing_guide/smoke.md
+++ b/doc/development/testing_guide/smoke.md
@@ -17,7 +17,7 @@ Currently, our suite consists of this basic functionality coverage:
Smoke tests have the `:smoke` RSpec metadata.
-See [End-to-end Testing](./end_to_end_tests.md) for more details about
+See [End-to-end Testing](end_to_end/index.md) for more details about
end-to-end tests.
---
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index 1fa6e38ea5a..e1ce4d3b7d1 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -4,12 +4,14 @@
_This diagram demonstrates the relative priority of each test type we use. `e2e` stands for end-to-end._
-As of 2019-04-16, we have the following distribution of tests per level:
+As of 2019-05-01, we have the following distribution of tests per level:
-- 67 black-box tests at the system level (aka end-to-end or QA tests) in CE, 98 in EE. This represents 0.3% of all the CE tests (0.3% in EE).
-- 5,457 white-box tests at the system level (aka system or feature tests) in CE, 6,585 in EE. This represents 24.6% of all the CE tests (20.3% in EE).
-- 8,298 integration tests in CE, 10,633 in EE: 0.3% of all the CE tests (0.3% in EE). This represents 37.2% of all the CE tests (32.8% in EE).
-- 8,403 unit tests in CE, 15,090 in EE: 0.3% of all the CE tests (0.3% in EE). This represents 37.8% of all the CE tests (46.6% in EE).
+| Test level | Community Edition | Enterprise Edition | Community + Enterprise Edition |
+| --------- | ---------- | -------------- | ----- |
+| Black-box tests at the system level (aka end-to-end or QA tests) | 68 (0.14%) | 31 (0.2%) | 99 (0.17%) |
+| White-box tests at the system level (aka system or feature tests) | 5,471 (11.9%) | 969 (7.4%) | 6440 (10.9%) |
+| Integration tests | 8,333 (18.2%) | 2,244 (17.2%) | 10,577 (17.9%) |
+| Unit tests | 32,031 (69.7%) | 9,778 (75.1%) | 41,809 (71%) |
## Unit tests
@@ -176,7 +178,7 @@ Every new feature should come with a [test plan].
| ---------- | -------------- | ----- |
| `qa/qa/specs/features/` | [Capybara] + [RSpec] + Custom QA framework | Tests should be placed under their corresponding [Product category] |
-> See [end-to-end tests](end_to_end_tests.md) for more information.
+> See [end-to-end tests](end_to_end/index.md) for more information.
Note that `qa/spec` contains unit tests of the QA framework itself, not to be
confused with the application's [unit tests](#unit-tests) or
diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md
index 01a0044f096..bfbb7be70e3 100644
--- a/doc/development/understanding_explain_plans.md
+++ b/doc/development/understanding_explain_plans.md
@@ -80,8 +80,9 @@ Planning time: 2.861 ms
Execution time: 3428.596 ms
```
-For more information, refer to the official [EXPLAIN
-documentation](https://www.postgresql.org/docs/current/static/sql-explain.html).
+For more information, refer to the official
+[`EXPLAIN` documentation](https://www.postgresql.org/docs/current/sql-explain.html)
+and [using `EXPLAIN` guide](https://www.postgresql.org/docs/current/using-explain.html).
## Nodes
@@ -653,9 +654,39 @@ and related tools such as:
- <https://explain.depesz.com/>
- <http://tatiyants.com/postgres-query-plan-visualization/>
-GitLab employees can also use our chatops solution, available in Slack using the
-`/chatops` slash command. You can use chatops to get a query plan by running the
-following:
+
+## Producing query plans
+
+There are a few ways to get the output of a query plan. Of course you
+can directly run the `EXPLAIN` query in the `psql` console, or you can
+follow one of the other options below.
+
+### Rails console
+
+Using the [`activerecord-explain-analyze`](https://github.com/6/activerecord-explain-analyze)
+you can directly generate the query plan from the Rails console:
+
+```ruby
+pry(main)> require 'activerecord-explain-analyze'
+=> true
+pry(main)> Project.where('build_timeout > ?', 3600).explain(analyze: true)
+ Project Load (1.9ms) SELECT "projects".* FROM "projects" WHERE (build_timeout > 3600)
+ ↳ (pry):12
+=> EXPLAIN for: SELECT "projects".* FROM "projects" WHERE (build_timeout > 3600)
+Seq Scan on public.projects (cost=0.00..2.17 rows=1 width=742) (actual time=0.040..0.041 rows=0 loops=1)
+ Output: id, name, path, description, created_at, updated_at, creator_id, namespace_id, ...
+ Filter: (projects.build_timeout > 3600)
+ Rows Removed by Filter: 14
+ Buffers: shared hit=2
+Planning time: 0.411 ms
+Execution time: 0.113 ms
+```
+
+### Chatops
+
+[GitLab employees can also use our chatops solution, available in Slack using the
+`/chatops` slash command](chatops_on_gitlabcom.md).
+You can use chatops to get a query plan by running the following:
```
/chatops run explain SELECT COUNT(*) FROM projects WHERE visibility_level IN (0, 20)
@@ -674,3 +705,9 @@ For more information about the available options, run:
```
/chatops run explain --help
```
+
+## Further reading
+
+A more extensive guide on understanding query plans can be found in
+the [presentation](https://www.dalibo.org/_media/understanding_explain.pdf)
+from [Dalibo.org](https://www.dalibo.org/en/).
diff --git a/doc/git_hooks/git_hooks.md b/doc/git_hooks/git_hooks.md
index 9b8ad1578a0..b251e58410a 100644
--- a/doc/git_hooks/git_hooks.md
+++ b/doc/git_hooks/git_hooks.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/push_rules/push_rules.html'
+redirect_to: '../push_rules/push_rules.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/push_rules/push_rules.html)
+This document was moved to [another location](../push_rules/push_rules.md)
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
index 28ba20bec09..00ac6d6b650 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -15,7 +15,7 @@ This documentation is split into the following groups:
The following are guides to basic GitLab functionality:
-- [Create and add your SSH Keys](create-your-ssh-keys.md), for enabling Git over SSH.
+- [Create and add your SSH public key](create-your-ssh-keys.md), for enabling Git over SSH.
- [Create a project](create-project.md), to start using GitLab.
- [Create a group](../user/group/index.md#create-a-new-group), to combine and administer projects together.
- [Create a branch](create-branch.md), to make changes to files stored in a project's repository.
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 785e2ffb650..a9ae4fb23f9 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -16,7 +16,7 @@ To create a project in GitLab:
- [Import a project](../user/project/import/index.md) from a different repository,
if enabled on your GitLab instance. Contact your GitLab admin if this
is unavailable.
- - Run [CI/CD pipelines for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html). **[PREMIUM]**
+ - Run [CI/CD pipelines for external repositories](../ci/ci_cd_for_external_repos/index.md). **[PREMIUM]**
## Blank projects
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index 8fecdc6948e..aac73d4c9c5 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -1,22 +1,22 @@
-# How to create your SSH keys
+# Create and add your SSH public key
-This topic describes how to create SSH keys. You do this to use Git over SSH instead of Git over HTTP.
+This topic describes how to:
-## Creating your SSH keys
+- Create an SSH key pair to use with GitLab.
+- Add the SSH public key to your GitLab account.
-1. Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate your SSH key pair.
-1. Log in to GitLab.
-1. In the upper-right corner, click your avatar and select **Settings**.
-1. On the **User Settings** menu, select **SSH keys**.
-1. Paste the **public** key generated in the first step in the **Key**
- text field.
-1. Optionally, give it a descriptive title so that you can recognize it in the
- event you add multiple keys.
-1. Finally, click the **Add key** button to add it to GitLab. You will be able to see
- its fingerprint, title, and creation date.
+You do this to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).
- ![SSH key single page](img/profile_settings_ssh_keys_single_key.png)
+## Creating your SSH key pair
+
+1. Go to your [command line](start-using-git.md).
+1. Follow the [instructions](../ssh/README.md#generating-a-new-ssh-key-pair) to generate your SSH key pair.
+
+## Adding your SSH public key to GitLab
+
+To add the SSH public key to GitLab,
+see [Adding an SSH key to your GitLab account](../ssh/README.md#adding-an-ssh-key-to-your-gitlab-account).
NOTE: **Note:**
Once you add a key, you cannot edit it. If the paste
-didn't work, you need to remove the offending key and re-add it.
+[didn't work](../ssh/README.md#testing-that-everything-is-set-up-correctly), you need to remove the key and re-add it.
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
deleted file mode 100644
index 8014f1d5301..00000000000
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-geo/configuration_source.md b/doc/gitlab-geo/configuration_source.md
index f1aab86fadc..b46a2caea4a 100644
--- a/doc/gitlab-geo/configuration_source.md
+++ b/doc/gitlab-geo/configuration_source.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../administration/geo/replication/configuration_source.md'
+redirect_to: '../administration/geo/replication/configuration.md'
---
-This document was moved to [another location](../administration/geo/replication/configuration_source.md).
+This document was moved to [another location](../administration/geo/replication/configuration.md).
diff --git a/doc/gitlab-geo/database_source.md b/doc/gitlab-geo/database_source.md
index 3392d0f02c0..b4156dc4ec6 100644
--- a/doc/gitlab-geo/database_source.md
+++ b/doc/gitlab-geo/database_source.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../administration/geo/replication/database_source.md'
+redirect_to: '../administration/geo/replication/database.md'
---
-This document was moved to [another location](../administration/geo/replication/database_source.md).
+This document was moved to [another location](../administration/geo/replication/database.md).
diff --git a/doc/install/README.md b/doc/install/README.md
index 53778f7f0d3..9cc21412898 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -1,6 +1,7 @@
---
comments: false
description: Read through the GitLab installation methods.
+type: index
---
# Installation **[CORE ONLY]**
@@ -81,7 +82,7 @@ the above methods, provided the cloud provider supports it.
- [Install on AWS](aws/index.md): Install Omnibus GitLab on AWS using the community AMIs that GitLab provides.
- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md): Install Omnibus GitLab on a VM in GCP.
- [Install GitLab on Azure](azure/index.md): Install Omnibus GitLab from Azure Marketplace.
-- [Install GitLab on OpenShift](openshift_and_gitlab/index.md): Install GitLab using the Docker image on OpenShift.
+- [Install GitLab on OpenShift](https://docs.gitlab.com/charts/installation/cloud/openshift.html): Install GitLab on OpenShift by using GitLab's Helm charts.
- [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/): Install GitLab on Mesosphere DC/OS via the [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/).
- [Install GitLab on DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): Install Omnibus GitLab on DigitalOcean.
- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md):
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index 0000e03f1d7..73eaf758923 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Installing GitLab HA on Amazon Web Services (AWS)
This page offers a walkthrough of a common HA (Highly Available) configuration
@@ -383,7 +387,7 @@ after the instance is created.
CAUTION: **Caution:**
We **do not** recommend using the AWS Elastic File System (EFS), as it can result
-in [significantly degraded performance](../../administration/high_availability/nfs.html#avoid-using-awss-elastic-file-system-efs).
+in [significantly degraded performance](../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs).
### Configure security group
@@ -649,12 +653,24 @@ Have a read through these other resources and feel free to
[open an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/new)
to request additional material:
-- [GitLab High Availability](https://docs.gitlab.com/ee/administration/high_availability/):
+- [GitLab High Availability](../../administration/high_availability/README.md):
GitLab supports several different types of clustering and high-availability.
-- [Geo replication](https://docs.gitlab.com/ee/administration/geo/replication/):
+- [Geo replication](../../administration/geo/replication/index.md):
Geo is the solution for widely distributed development teams.
- [Omnibus GitLab](https://docs.gitlab.com/omnibus/) - Everything you need to know
about administering your GitLab instance.
-- [Upload a license](https://docs.gitlab.com/ee/user/admin_area/license.html):
+- [Upload a license](../../user/admin_area/license.md):
Activate all GitLab Enterprise Edition functionality with a license.
- [Pricing](https://about.gitlab.com/pricing): Pricing for the different tiers.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index fa5be1d30f9..b1f79893baf 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -1,14 +1,10 @@
---
-description: 'Learn how to spin up a
-pre-configured GitLab VM on Microsoft Azure and have your very own private GitLab instance up and running in around 30 minutes.'
+description: 'Learn how to spin up a pre-configured GitLab VM on Microsoft Azure.'
+type: howto
---
# Install GitLab on Microsoft Azure
-> _This article was originally written by Dave Wentzel and [published on the GitLab Blog][Original-Blog-Post]._
->
-> _Ported to the GitLab documentation and updated on 2017-08-24 by [Ian Scorer](https://gitlab.com/iscorer)._
-
Azure is Microsoft's business cloud and GitLab is a pre-configured offering on the Azure Marketplace.
Hopefully, you aren't surprised to hear that Microsoft and Azure have embraced open source software
like Ubuntu, Red Hat Enterprise Linux, and of course - GitLab! This means that you can spin up a
@@ -444,3 +440,15 @@ Check out our other [Technical Articles][GitLab-Technical-Articles] or browse th
[SSH]: https://en.wikipedia.org/wiki/Secure_Shell
[PuTTY]: http://www.putty.org/
[Using-SSH-In-Putty]: https://mediatemple.net/community/products/dv/204404604/using-ssh-in-putty-
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index e89846107b6..cbb3b766b4e 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Database MySQL
NOTE: **Note:**
@@ -301,3 +305,15 @@ Details can be found in the [PostgreSQL][postgres-text-type] and
[postgres-text-type]: http://www.postgresql.org/docs/9.2/static/datatype-character.html
[ce-38152]: https://gitlab.com/gitlab-org/gitlab-ce/issues/38152
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/digitaloceandocker.md b/doc/install/digitaloceandocker.md
index d67695d75b4..63bb941ad47 100644
--- a/doc/install/digitaloceandocker.md
+++ b/doc/install/digitaloceandocker.md
@@ -1,8 +1,11 @@
+---
+type: howto
+---
+
# Digital Ocean and Docker Machine test environment
-CAUTION: **Caution:**
-This guide is for quickly testing different versions of GitLab and not recommended for ease of
-future upgrades or keeping the data you create.
+This guide is for quickly testing different versions of GitLab and not
+recommended for ease of future upgrades or keeping the data you create.
## Initial setup
@@ -28,7 +31,7 @@ locally on either macOS or Linux.
NOTE: **Note:**
The rest of the steps are identical for macOS and Linux.
-### Create new docker host
+## Create new docker host
1. Login to Digital Ocean.
1. Generate a new API token at <https://cloud.digitalocean.com/settings/api/tokens>.
@@ -60,9 +63,9 @@ The rest of the steps are identical for macOS and Linux.
Resource: <https://docs.docker.com/machine/drivers/digital-ocean/>.
-### Creating GitLab test instance
+## Creating GitLab test instance
-#### Connect your shell to the new machine
+### Connect your shell to the new machine
In this example we'll create a GitLab EE 8.10.8 instance.
@@ -74,7 +77,7 @@ eval "$(docker-machine env gitlab-test-env-do)"
You can add this to your `~/.bash_profile` file to ensure the `docker` client uses the `gitlab-test-env-do` docker host
-#### Create new GitLab container
+### Create new GitLab container
- HTTP port: `8888`
- SSH port: `2222`
@@ -83,7 +86,7 @@ You can add this to your `~/.bash_profile` file to ensure the `docker` client us
- Container name: `gitlab-test-8.10`
- GitLab version: **EE** `8.10.8-ee.0`
-##### Set up container settings
+#### Set up container settings
```sh
export SSH_PORT=2222
@@ -92,7 +95,7 @@ export VERSION=8.10.8-ee.0
export NAME=gitlab-test-8.10
```
-##### Create container
+#### Create container
```sh
docker run --detach \
@@ -103,9 +106,9 @@ docker run --detach \
gitlab/gitlab-ee:$VERSION
```
-#### Connect to the GitLab container
+### Connect to the GitLab container
-##### Retrieve the docker host IP
+#### Retrieve the docker host IP
```sh
docker-machine ip gitlab-test-env-do
@@ -114,7 +117,7 @@ docker-machine ip gitlab-test-env-do
Browse to: <http://192.168.151.134:8888/>.
-##### Execute interactive shell/edit configuration
+#### Execute interactive shell/edit configuration
```sh
docker exec -it $NAME /bin/bash
@@ -126,8 +129,20 @@ root@192:/# vi /etc/gitlab/gitlab.rb
root@192:/# gitlab-ctl reconfigure
```
-#### Resources
+### Resources
- <https://docs.gitlab.com/omnibus/docker/>.
- <https://docs.docker.com/machine/get-started/>.
- <https://docs.docker.com/machine/reference/ip/>.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/docker.md b/doc/install/docker.md
index 4568a949b0a..06da65189ba 100644
--- a/doc/install/docker.md
+++ b/doc/install/docker.md
@@ -1,3 +1,7 @@
+---
+type: index
+---
+
# Install GitLab with Docker
[Docker](https://www.docker.com) and container technology have been revolutionizing the software world for the past few years. They combine the performance and efficiency of native execution with the abstraction, security, and immutability of virtualization.
@@ -16,4 +20,4 @@ A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) to these image
## Cloud native images
-GitLab is also working towards a [cloud native set of containers](https://gitlab.com/charts/helm.gitlab.io#docker-container-images), with a single image for each component service. We intend for these images to eventually replace the [Omnibus GitLab based images](#omnibus-gitlab-based-images).
+GitLab is also working towards a [cloud native set of containers](https://docs.gitlab.com/charts/), with a single image for each component service. We intend for these images to eventually replace the [Omnibus GitLab based images](#omnibus-gitlab-based-images).
diff --git a/doc/install/google-protobuf.md b/doc/install/google-protobuf.md
index a531b4519b3..434817c48cb 100644
--- a/doc/install/google-protobuf.md
+++ b/doc/install/google-protobuf.md
@@ -1,26 +1,5 @@
-# Installing a locally compiled google-protobuf gem
+---
+redirect_to: 'installation.md#google-protobuf-loaderror-libx86_64-linux-gnulibcso6-version-glibc_214-not-found'
+---
-First we must find the exact version of google-protobuf that your
-GitLab installation requires.
-
- cd /home/git/gitlab
-
- # Only one of the following two commands will print something. It
- # will look like: * google-protobuf (3.2.0)
- bundle list | grep google-protobuf
- bundle check | grep google-protobuf
-
-Below we use `3.2.0` as an example. Replace it with the version number
-you found above.
-
- cd /home/git/gitlab
- sudo -u git -H gem install google-protobuf --version 3.2.0 --platform ruby
-
-Finally, you can test whether google-protobuf loads correctly. The
-following should print 'OK'.
-
- sudo -u git -H bundle exec ruby -rgoogle/protobuf -e 'puts :OK'
-
-If the `gem install` command fails you may need to install developer
-tools. On Debian: `apt-get install build-essential libgmp-dev`, on
-Centos/RedHat `yum groupinstall 'Development Tools'`.
+This document was moved to [another location](installation.md#google-protobuf-loaderror-libx86_64-linux-gnulibcso6-version-glibc_214-not-found).
diff --git a/doc/install/google_cloud_platform/img/gcp_landing.png b/doc/install/google_cloud_platform/img/gcp_landing.png
deleted file mode 100644
index 92a9873728c..00000000000
--- a/doc/install/google_cloud_platform/img/gcp_landing.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md
index 242ad70ae82..77c61acbfd4 100644
--- a/doc/install/google_cloud_platform/index.md
+++ b/doc/install/google_cloud_platform/index.md
@@ -1,14 +1,13 @@
---
description: 'Learn how to install a GitLab instance on Google Cloud Platform.'
+type: howto
---
# Installing GitLab on Google Cloud Platform
-![GCP landing page](img/gcp_landing.png)
+This guide will help you install GitLab on a [Google Cloud Platform (GCP)][gcp] instance.
-Getting started with GitLab on a [Google Cloud Platform (GCP)][gcp] instance is quick and easy.
-
-NOTE: **Note:**
+NOTE: **Alternative installation method:**
Google provides a whitepaper for [deploying production-ready GitLab on
Google Kubernetes Engine](https://cloud.google.com/solutions/deploying-production-ready-gitlab-on-gke),
including all steps and external resource configuration. These are an alternative to using a GCP VM, and use
@@ -26,10 +25,9 @@ Once you have performed those two steps, you can [create a VM](#creating-the-vm)
## Creating the VM
-To deploy GitLab on GCP you need to follow five simple steps:
-
-1. Go to <https://console.cloud.google.com/compute/instances> and login with your Google credentials.
+To deploy GitLab on GCP you first need to create a virtual machine:
+1. Go to <https://console.cloud.google.com/compute/instances> and log in with your Google credentials.
1. Click on **Create**
![Search for GitLab](img/launch_vm.png)
@@ -142,3 +140,15 @@ Kerberos, etc. Here are some documents you might be interested in reading:
[ssh]: https://cloud.google.com/compute/docs/instances/connecting-to-instance "Connecting to Linux Instances"
[omni-smtp]: https://docs.gitlab.com/omnibus/settings/smtp.html#smtp-settings "Omnibus GitLab SMTP settings"
[omni-ssl]: https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https "Omnibus GitLab enable HTTPS"
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 6b24dc8abdc..eb484dde545 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -1,5 +1,28 @@
+---
+type: howto
+---
+
# Installation from source
+This is the official installation guide to set up a production GitLab server
+using the source files. To set up a **development installation** or for many
+other installation options, see the [main installation page](index.md).
+It was created for and tested on **Debian/Ubuntu** operating systems.
+Read [requirements.md](requirements.md) for hardware and operating system requirements.
+If you want to install on RHEL/CentOS, we recommend using the
+[Omnibus packages](https://about.gitlab.com/downloads/).
+
+This guide is long because it covers many cases and includes all commands you
+need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880).
+The following steps have been known to work. **Use caution when you deviate**
+from this guide. Make sure you don't violate any assumptions GitLab makes about
+its environment. For example, many people run into permission problems because
+they changed the location of directories or run services as the wrong user.
+
+If you find a bug/error in this guide, **submit a merge request**
+following the
+[contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md).
+
## Consider the Omnibus package installation
Since an installation from source is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
@@ -12,26 +35,42 @@ After this termination Runit will detect Sidekiq is not running and will start i
Since installations from source don't use Runit for process supervision, Sidekiq
can't be terminated and its memory usage will grow over time.
-## Select Version to Install
+## Select version to install
Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install (e.g., `11-7-stable`).
You can select the branch in the version dropdown in the top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear, check the [GitLab blog](https://about.gitlab.com/blog/) for installation guide links by version.
-## Important Notes
+## GitLab directory structure
-This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880).
+This is the main directory structure you will end up with following the instructions
+of this page:
-This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Read [requirements.md](requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS, we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/).
+```
+|-- home
+| |-- git
+| |-- .ssh
+| |-- gitlab
+| |-- gitlab-shell
+| |-- repositories
+```
-This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options, see [the installation section of the README](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation).
+- `/home/git/.ssh` - Contains OpenSSH settings. Specifically the `authorized_keys`
+ file managed by gitlab-shell.
+- `/home/git/gitlab` - GitLab core software.
+- `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH
+ cloning and other functionality.
+- `/home/git/repositories` - Bare repositories for all projects organized by
+ namespace. This is where the git repositories which are pushed/pulled are
+ maintained for all projects. **This area contains critical data for projects.
+ [Keep a backup](../raketasks/backup_restore.md).**
-The following steps have been known to work. **Use caution when you deviate** from this guide. Make sure you don't violate any assumptions GitLab makes about its environment. For example, many people run into permission problems because they changed the location of directories or run services as the wrong user.
+NOTE: **Note:**
+The default locations for repositories can be configured in `config/gitlab.yml`
+of GitLab and `config.yml` of gitlab-shell.
-If you find a bug/error in this guide, **submit a merge request**
-following the
-[contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md).
+For a more in-depth overview, see the [GitLab architecture doc](../development/architecture.md).
## Overview
@@ -211,11 +250,11 @@ page](https://golang.org/dl).
# Remove former Go installation folder
sudo rm -rf /usr/local/go
-curl --remote-name --progress https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
-echo 'fa1b0e45d3b647c252f51f5e1204aba049cde4af177ef9f2181f43004f901035 go1.10.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
- sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz
+curl --remote-name --progress https://dl.google.com/go/go1.11.10.linux-amd64.tar.gz
+echo 'aefaa228b68641e266d1f23f1d95dba33f17552ba132878b65bb798ffa37e6d0 go1.11.10.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.11.10.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
-rm go1.10.3.linux-amd64.tar.gz
+rm go1.11.10.linux-amd64.tar.gz
```
## 4. Node
@@ -324,9 +363,7 @@ Redis 2.8 with:
sudo apt-get install redis-server
```
-If you are using Debian 7 or Ubuntu 12.04, follow the special documentation
-on [an alternate Redis installation](redis.md). Once done, follow the rest of
-the guide here.
+Once done, you can configure Redis:
```sh
# Configure redis to use sockets
@@ -930,8 +967,50 @@ and correctly [configured Nginx](#site-configuration).
### google-protobuf "LoadError: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.14' not found"
This can happen on some platforms for some versions of the
-google-protobuf gem. The workaround is to [install a source-only
-version of this gem](google-protobuf.md).
+`google-protobuf` gem. The workaround is to install a source-only
+version of this gem.
+
+First, you must find the exact version of `google-protobuf` that your
+GitLab installation requires:
+
+```sh
+cd /home/git/gitlab
+
+# Only one of the following two commands will print something. It
+# will look like: * google-protobuf (3.2.0)
+bundle list | grep google-protobuf
+bundle check | grep google-protobuf
+```
+
+Below, `3.2.0` is used as an example. Replace it with the version number
+you found above:
+
+```sh
+cd /home/git/gitlab
+sudo -u git -H gem install google-protobuf --version 3.2.0 --platform ruby
+```
+
+Finally, you can test whether `google-protobuf` loads correctly. The
+following should print 'OK'.
+
+```sh
+sudo -u git -H bundle exec ruby -rgoogle/protobuf -e 'puts :OK'
+```
+
+If the `gem install` command fails, you may need to install the developer
+tools of your OS.
+
+On Debian/Ubuntu:
+
+```sh
+sudo apt-get install build-essential libgmp-dev
+```
+
+On RedHat/CentOS:
+
+```sh
+sudo yum groupinstall 'Development Tools'
+```
[RVM]: https://rvm.io/ "RVM Homepage"
[rbenv]: https://github.com/sstephenson/rbenv "rbenv on GitHub"
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index 7312bf2d4f7..43655767002 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -1,41 +1,5 @@
---
-description: 'Read through the different methods to deploy GitLab on Kubernetes.'
+redirect_to: https://docs.gitlab.com/charts/
---
-# Installing GitLab on Kubernetes
-
-NOTE: **Kubernetes experience required:**
-Our Helm charts are recommended for those who are familiar with Kubernetes.
-If you're not sure if Kubernetes is for you, our
-[Omnibus GitLab packages](../README.md#installing-gitlab-using-the-omnibus-gitlab-package-recommended)
-are mature, scalable, support [high availability](../../administration/high_availability/README.md)
-and are used today on GitLab.com.
-It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html).
-
-The easiest method to deploy GitLab on [Kubernetes](https://kubernetes.io/) is
-to take advantage of GitLab's Helm charts. [Helm](https://github.com/kubernetes/helm/blob/master/README.md)
-is a package management tool for Kubernetes, allowing apps to be easily managed via their
-Charts. A [Chart](https://github.com/kubernetes/charts) is a detailed description
-of the application including how it should be deployed, upgraded, and configured.
-
-## GitLab Chart
-
-This chart contains all the required components to get started, and can scale to
-large deployments. It offers a number of benefits, among others:
-
-- Horizontal scaling of individual components.
-- No requirement for shared storage to scale.
-- Containers do not need `root` permissions.
-- Automatic SSL with Let's Encrypt.
-- An unprivileged GitLab Runner.
-
-Learn more about the [GitLab chart](https://docs.gitlab.com/charts/).
-
-## GitLab Runner Chart
-
-If you already have a GitLab instance running, inside or outside of Kubernetes,
-and you'd like to leverage the Runner's
-[Kubernetes capabilities](https://docs.gitlab.com/runner/executors/kubernetes.html),
-it can be deployed with the GitLab Runner chart.
-
-Learn more about the [GitLab Runner chart](https://docs.gitlab.com/runner/install/kubernetes.html).
+This document was moved to [another location](https://docs.gitlab.com/charts/).
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 77bd9e9f7a9..18981c43464 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -1,8 +1,5 @@
---
-author: Achilleas Pipinellis
-author_gitlab: axil
-level: intermediary
-article_type: tutorial
+type: howto
date: 2016-06-28
---
diff --git a/doc/install/pivotal/index.md b/doc/install/pivotal/index.md
index 896e01ad975..f068572f1e9 100644
--- a/doc/install/pivotal/index.md
+++ b/doc/install/pivotal/index.md
@@ -1,45 +1,12 @@
# GitLab Pivotal Tile **[PREMIUM ONLY]**
-> Introduced in [GitLab Premium][eep] 8.2.
-
-Easily deploy GitLab as a pre-configured appliance using Ops Manager (BOSH) for
-[Pivotal Cloud Foundry][pcf].
-
-## Overview
-
-Enterprise admins want their development toolkit to be more customizable, more
-integrated, and more secure. With Pivotal Cloud Foundry, GitLab is installed and
-scales easily in a highly available environment.
-
-The upgrades are pain-free and well tested. All it takes is upload the new tile
-and click a button to begin the upgrade process.
-
-## Use cases
-
-- You want a highly available deployment with minimal effort. Scale horizontally
- as your user base grows.
-
-## Features
-
-The GitLab Pivotal Tile is based on [GitLab Premium][eep] and includes nearly all of its features. The features in Premium but _not_ supported on the Tile are:
-
-* PostgreSQL
-* Pages
-* Geo
-* Registry
-* Mattermost
-* Subgroups
-* Elasticsearch
-* Service Desk
-* OAuth & Kerberos Authentication
-
-## Installing GitLab with Pivotal
-
-The product information and installation documentation is hosted on Pivotal's
-website:
-
-- [Product page](https://network.pivotal.io/products/p-gitlab/)
-- [Documentation](https://docs.pivotal.io/partners/gitlab/index.html)
-
-[eep]: https://about.gitlab.com/pricing/
-[pcf]: https://pivotal.io/platform
+CAUTION: **Discontinued:**
+As of September 13, 2017, the GitLab Enterprise Plus for Pivotal Cloud Foundry
+tile on Pivotal Network has reached its End of Availability (“EoA”) and is no
+longer available for download or sale through Pivotal. Current customers with
+active subscriptions will continue to receive support from GitLab through their
+subscription term. Pivotal and GitLab are collaborating on creating a new
+Kubernetes-based tile for the Pivotal Container Service. Please contact GitLab
+support with any questions regarding GitLab Enterprise Plus for Pivotal Cloud Foundry.
+
+Original article: <https://docs.pivotal.io/partners/gitlab/index.html>.
diff --git a/doc/install/redis.md b/doc/install/redis.md
index 4075e6283d0..cff5c2f2611 100644
--- a/doc/install/redis.md
+++ b/doc/install/redis.md
@@ -1,60 +1,5 @@
-# Install Redis on old distributions
+---
+redirect_to: installation.md#7-redis
+---
-GitLab requires at least Redis 2.8. The following guide is for Debian 7 and
-Ubuntu 12.04. If you are using Debian 8 or Ubuntu 14.04 and up, follow the
-[installation guide](installation.md).
-
-## Install Redis 2.8 in Debian 7
-
-Redis 2.8 is included in the Debian Wheezy [backports] repository.
-
-1. Edit `/etc/apt/sources.list` and add the following line:
-
- ```
- deb http://http.debian.net/debian wheezy-backports main
- ```
-
-1. Update the repositories:
-
- ```
- sudo apt-get update
- ```
-
-1. Install `redis-server`:
-
- ```
- sudo apt-get -t wheezy-backports install redis-server
- ```
-
-1. Follow the rest of the [installation guide](installation.md).
-
-## Install Redis 2.8 in Ubuntu 12.04
-
-We will [use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server)
-to install a recent version of Redis.
-
-1. Install the PPA repository:
-
- ```
- sudo add-apt-repository ppa:chris-lea/redis-server
- ```
-
- Your system will now fetch the PPA's key. This enables your Ubuntu system to
- verify that the packages in the PPA have not been interfered with since they
- were built.
-
-1. Update the repositories:
-
- ```
- sudo apt-get update
- ```
-
-1. Install `redis-server`:
-
- ```
- sudo apt-get install redis-server
- ```
-
-1. Follow the rest of the [installation guide](installation.md).
-
-[backports]: http://backports.debian.org/Instructions/ "Debian backports website"
+This document was moved to [another location](installation.md#7-redis).
diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md
index 5f129fd3bd1..96b7d0f3648 100644
--- a/doc/install/relative_url.md
+++ b/doc/install/relative_url.md
@@ -1,18 +1,19 @@
+---
+type: reference
+---
+
# Install GitLab under a relative URL
-NOTE: **Note:**
+While it is recommended to install GitLab on its own (sub)domain, sometimes
+this is not possible due to a variety of reasons. In that case, GitLab can also
+be installed under a relative URL, for example `https://example.com/gitlab`.
+
This document describes how to run GitLab under a relative URL for installations
from source. If you are using an Omnibus package,
[the steps are different][omnibus-rel]. Use this guide along with the
[installation guide](installation.md) if you are installing GitLab for the
first time.
----
-
-While it is recommended to install GitLab on its own (sub)domain, sometimes
-this is not possible due to a variety of reasons. In that case, GitLab can also
-be installed under a relative URL, for example `https://example.com/gitlab`.
-
There is no limit to how deeply nested the relative URL can be. For example you
could serve GitLab under `/foo/bar/gitlab/git` without any issues.
@@ -20,8 +21,6 @@ Note that by changing the URL on an existing GitLab installation, all remote
URLs will change, so you'll have to manually edit them in any local repository
that points to your GitLab instance.
----
-
The TL;DR list of configuration files that you need to change in order to
serve GitLab under a relative URL is:
@@ -126,3 +125,15 @@ To disable the relative URL:
[omnibus-rel]: http://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to set up relative URL in Omnibus GitLab"
[restart gitlab]: ../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index f6a52205a0e..ee3d17704a2 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -1,5 +1,12 @@
+---
+type: reference
+---
+
# Requirements
+This page includes useful information on the supported Operating Systems as well
+as the hardware requirements that are needed to install and use GitLab.
+
## Operating Systems
### Supported Unix distributions
@@ -12,7 +19,7 @@
- Scientific Linux (please use the CentOS packages and instructions)
- Oracle Linux (please use the CentOS packages and instructions)
-For the installations options please see [the installation page on the GitLab website](https://about.gitlab.com/installation/).
+For the installations options, see [the main installation page](README.md).
### Unsupported Unix distributions
@@ -105,9 +112,9 @@ features of GitLab work with MySQL/MariaDB:
1. MySQL support for subgroups was [dropped with GitLab 9.3][post].
See [issue #30472][30472] for more information.
-1. Geo does [not support MySQL](https://docs.gitlab.com/ee/administration/geo/replication/database.html). This means no supported Disaster Recovery solution if using MySQL. **[PREMIUM ONLY]**
+1. Geo does [not support MySQL](../administration/geo/replication/database.md). This means no supported Disaster Recovery solution if using MySQL. **[PREMIUM ONLY]**
1. [Zero downtime migrations](../update/README.md#upgrading-without-downtime) do not work with MySQL.
-1. [Database load balancing](https://docs.gitlab.com/ee/administration/database_load_balancing.html) is
+1. [Database load balancing](../administration/database_load_balancing.md) is
supported only for PostgreSQL. **[PREMIUM ONLY]**
1. GitLab [optimizes the loading of dashboard events](https://gitlab.com/gitlab-org/gitlab-ce/issues/31806) using [PostgreSQL LATERAL JOINs](https://blog.heapanalytics.com/postgresqls-powerful-new-join-type-lateral/).
1. In general, SQL optimized for PostgreSQL may run much slower in MySQL due to
@@ -143,14 +150,14 @@ On some systems you may need to install an additional package (e.g.
#### Additional requirements for GitLab Geo
-If you are using [GitLab Geo](https://docs.gitlab.com/ee/development/geo.html):
+If you are using [GitLab Geo](../development/geo.md):
- We strongly recommend running Omnibus-managed instances as they are actively
developed and tested. We aim to be compatible with most external (not managed
by Omnibus) databases (for example, AWS RDS) but we do not guarantee
compatibility.
- The
- [tracking database](https://docs.gitlab.com/ee/development/geo.html#geo-tracking-database)
+ [tracking database](../development/geo.md#using-the-tracking-database)
requires the
[postgres_fdw](https://www.postgresql.org/docs/9.6/static/postgres-fdw.html)
extension.
@@ -161,14 +168,14 @@ CREATE EXTENSION postgres_fdw;
## Unicorn Workers
-It's possible to increase the amount of unicorn workers and this will usually help to reduce the response time of the applications and increase the ability to handle parallel requests.
-
For most instances we recommend using: CPU cores + 1 = unicorn workers.
So for a machine with 2 cores, 3 unicorn workers is ideal.
For all machines that have 2GB and up we recommend a minimum of three unicorn workers.
If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive swapping.
+As long as you have enough available CPU and memory capacity, it's okay to increase the number of unicorn workers and this will usually help to reduce the response time of the applications and increase the ability to handle parallel requests.
+
To change the Unicorn workers when you have the Omnibus package (which defaults to the recommendation above) please see [the Unicorn settings in the Omnibus GitLab documentation](https://docs.gitlab.com/omnibus/settings/unicorn.html).
## Redis and Sidekiq
@@ -201,12 +208,23 @@ you decide to run GitLab Runner and the GitLab Rails application on the same
machine.
It is also not safe to install everything on a single machine, because of the
-[security reasons](https://docs.gitlab.com/runner/security/)
-- especially when you plan to use shell executor with GitLab
+[security reasons](https://docs.gitlab.com/runner/security/), especially when you plan to use shell executor with GitLab
Runner.
We recommend using a separate machine for each GitLab Runner, if you plan to
use the CI features.
+The GitLab Runner server requirements depend on:
+
+- The type of [executor](https://docs.gitlab.com/runner/executors/) you configured on GitLab Runner.
+- Resources required to run build jobs.
+- Job concurrency settings.
+
+Since the nature of the jobs varies for each use case, you will need to experiment by adjusting the job concurrency to get the optimum setting.
+
+For reference, GitLab.com's [auto-scaling shared runner](../user/gitlab_com/index.md#shared-runners) is configured so that a **single job** will run in a **single instance** with:
+
+- 1vCPU.
+- 3.75GB of RAM.
## Supported web browsers
@@ -225,3 +243,15 @@ Each time a new browser version is released, we begin supporting that version an
NOTE: **Note:** We do not support running GitLab with JavaScript disabled in the browser and have no plans of supporting that
in the future because we have features such as Issue Boards which require JavaScript extensively.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/structure.md b/doc/install/structure.md
index 8fc6ab4ab2f..87ef11c60fe 100644
--- a/doc/install/structure.md
+++ b/doc/install/structure.md
@@ -1,19 +1,5 @@
-# GitLab directory structure
+---
+redirect_to: installation.md#gitlab-directory-structure
+---
-This is the directory structure you will end up with following the instructions in the Installation Guide.
-
- |-- home
- | |-- git
- | |-- .ssh
- | |-- gitlab
- | |-- gitlab-shell
- | |-- repositories
-
-- `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell.
-- `/home/git/gitlab` - GitLab core software.
-- `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH cloning and other functionality.
-- `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md).**
-
-*Note: the default locations for repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.*
-
-To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md).
+This page was moved to [another location](#installation.md#gitlab-directory-structure).
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 15176ede733..6064c417900 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -8,8 +8,8 @@ This document describes how to set up Elasticsearch with GitLab. Once enabled,
you'll have the benefit of fast search response times and the advantage of two
special searches:
-- [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html)
-- [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html)
+- [Advanced Global Search](../user/search/advanced_global_search.md)
+- [Advanced Syntax Search](../user/search/advanced_search_syntax.md)
## Version Requirements
<!-- Please remember to update ee/lib/system_check/app/elasticsearch_check.rb if this changes -->
@@ -192,9 +192,6 @@ Performing asynchronous indexing, as this will describe, will generate a lot of
Make sure to prepare for this task by either [Horizontally Scaling](../administration/high_availability/README.md#basic-scaling)
or creating [extra sidekiq processes](../administration/operations/extra_sidekiq_processes.md)
-NOTE: **Note**:
-After indexing the repositories asynchronously, you **MUST** index the database to be able to search.
-
Configure Elasticsearch's host and port in **Admin > Settings > Integrations**. Then create empty indexes using one of the following commands:
```sh
@@ -217,78 +214,49 @@ curl --request PUT localhost:9200/gitlab-production/_settings --data '{
} }'
```
-Then enable Elasticsearch indexing and run repository indexing tasks:
+Then enable Elasticsearch indexing and run project indexing tasks:
```sh
# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories_async
+sudo gitlab-rake gitlab:elastic:index_projects
# Installations from source
-bundle exec rake gitlab:elastic:index_repositories_async RAILS_ENV=production
+bundle exec rake gitlab:elastic:index_projects RAILS_ENV=production
```
-This enqueues a number of Sidekiq jobs to index your existing repositories.
-You can view the jobs in the admin panel (they are placed in the `elastic_batch_project_indexer`)
+This enqueues a Sidekiq job for each project that needs to be indexed.
+You can view the jobs in the admin panel (they are placed in the `elastic_indexer`
queue), or you can query indexing status using a rake task:
```sh
# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories_status
+sudo gitlab-rake gitlab:elastic:index_projects_status
# Installations from source
-bundle exec rake gitlab:elastic:index_repositories_status RAILS_ENV=production
+bundle exec rake gitlab:elastic:index_projects_status RAILS_ENV=production
Indexing is 65.55% complete (6555/10000 projects)
```
-By default, one job is created for every 300 projects. For large numbers of
-projects, you may wish to increase the batch size, by setting the `BATCH`
-environment variable.
-
-You can also run the initial indexing synchronously - this is most useful if
-you have a small number of projects or need finer-grained control over indexing
-than Sidekiq permits:
+If you want to limit the index to a range of projects you can provide the
+`ID_FROM` and `ID_TO` parameters:
```sh
# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories
+sudo gitlab-rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000
# Installations from source
-bundle exec rake gitlab:elastic:index_repositories RAILS_ENV=production
-```
-
-It might take a while depending on how big your Git repositories are.
-
-If you want to run several tasks in parallel (probably in separate terminal
-windows) you can provide the `ID_FROM` and `ID_TO` parameters:
-
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories ID_FROM=1001 ID_TO=2000
-
-# Installations from source
-bundle exec rake gitlab:elastic:index_repositories ID_FROM=1001 ID_TO=2000 RAILS_ENV=production
+bundle exec rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000 RAILS_ENV=production
```
Where `ID_FROM` and `ID_TO` are project IDs. Both parameters are optional.
-As an example, if you have 3,000 repositories and you want to run three separate indexing tasks, you might run:
+The above examples will index all projects starting with ID `1001` up to (and including) ID `2000`.
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories ID_TO=1000
-sudo gitlab-rake gitlab:elastic:index_repositories ID_FROM=1001 ID_TO=2000
-sudo gitlab-rake gitlab:elastic:index_repositories ID_FROM=2001
-
-# Installations from source
-bundle exec rake gitlab:elastic:index_repositories RAILS_ENV=production ID_TO=1000
-bundle exec rake gitlab:elastic:index_repositories RAILS_ENV=production ID_FROM=1001 ID_TO=2000
-bundle exec rake gitlab:elastic:index_repositories RAILS_ENV=production ID_FROM=2001
-```
-
-Sometimes your repository index process `gitlab:elastic:index_repositories` or
-`gitlab:elastic:index_repositories_async` can get interrupted. This may happen
-for many reasons, but it's always safe to run the indexing job again - it will
-skip those repositories that have already been indexed.
+TIP: **Troubleshooting:**
+Sometimes the project indexing jobs queued by `gitlab:elastic:index_projects`
+can get interrupted. This may happen for many reasons, but it's always safe
+to run the indexing task again - it will skip those repositories that have
+already been indexed.
As the indexer stores the last commit SHA of every indexed repository in the
database, you can run the indexer with the special parameter `UPDATE_INDEX` and
@@ -297,10 +265,10 @@ that repository is indexed, it can be useful in case if your index is outdated:
```sh
# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_repositories UPDATE_INDEX=true ID_TO=1000
+sudo gitlab-rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000
# Installations from source
-bundle exec rake gitlab:elastic:index_repositories UPDATE_INDEX=true ID_TO=1000 RAILS_ENV=production
+bundle exec rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000 RAILS_ENV=production
```
You can also use the `gitlab:elastic:clear_index_status` Rake task to force the
@@ -320,16 +288,6 @@ bundle exec rake gitlab:elastic:index_wikis RAILS_ENV=production
The wiki indexer also supports the `ID_FROM` and `ID_TO` parameters if you want
to limit a project set.
-Index all database entities (Keep in mind it can take a while, so consider using `screen` or `tmux`):
-
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_database
-
-# Installations from source
-bundle exec rake gitlab:elastic:index_database RAILS_ENV=production
-```
-
Enable replication and refreshing again after indexing (only if you previously disabled it):
```bash
@@ -376,25 +334,15 @@ There are several rake tasks available to you via the command line:
- This is a wrapper task. It does the following:
- `sudo gitlab-rake gitlab:elastic:create_empty_index`
- `sudo gitlab-rake gitlab:elastic:clear_index_status`
+ - `sudo gitlab-rake gitlab:elastic:index_projects`
- `sudo gitlab-rake gitlab:elastic:index_wikis`
- - `sudo gitlab-rake gitlab:elastic:index_database`
- - `sudo gitlab-rake gitlab:elastic:index_repositories`
-- [sudo gitlab-rake gitlab:elastic:index_repositories_async](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - This iterates over all projects and places them in batches. It then sends these batches to the background via sidekiq jobs to be indexed.
-- [sudo gitlab-rake gitlab:elastic:index_repositories_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - `sudo gitlab-rake gitlab:elastic:index_snippets`
+- [sudo gitlab-rake gitlab:elastic:index_projects](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - This iterates over all projects and queues sidekiq jobs to index them in the background.
+- [sudo gitlab-rake gitlab:elastic:index_projects_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- This determines the overall status of the indexing. It is done by counting the total number of indexed projects, dividing by a count of the total number of projects, then multiplying by 100.
-- [sudo gitlab-rake gitlab:elastic:index_repositories](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - This iterates over all projects and places them in batches. It then performs indexing on said batches synchronously.
- [sudo gitlab-rake gitlab:elastic:index_wikis](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Iterates over every project, determines if said project contains wiki data, and then indexes the blobs (content) of said wiki data.
-- [sudo gitlab-rake gitlab:elastic:index_database](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - This is a [rake multitask](https://www.rubydoc.info/github/ruby/rake/Rake/MultiTask). It does the following:
- - `sudo gitlab-rake gitlab:elastic:index_projects`
- - `sudo gitlab-rake gitlab:elastic:index_issues`
- - `sudo gitlab-rake gitlab:elastic:index_merge_requests`
- - `sudo gitlab-rake gitlab:elastic:index_snippets`
- - `sudo gitlab-rake gitlab:elastic:index_notes`
- - `sudo gitlab-rake gitlab:elastic:index_milestones`
- [sudo gitlab-rake gitlab:elastic:create_empty_index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- This generates an empty index on the Elasticsearch side.
- [sudo gitlab-rake gitlab:elastic:clear_index_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
@@ -403,20 +351,8 @@ There are several rake tasks available to you via the command line:
- This removes the GitLab index on the Elasticsearch instance.
- [sudo gitlab-rake gitlab:elastic:recreate_index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Does the same thing as `sudo gitlab-rake gitlab:elastic:create_empty_index`
-- [sudo gitlab-rake gitlab:elastic:add_feature_visibility_levels_to_project](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Adds visibility information to the indices for projects.
-- [sudo gitlab-rake gitlab:elastic:index_projects](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes projects data.
-- [sudo gitlab-rake gitlab:elastic:index_issues](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes issues data.
-- [sudo gitlab-rake gitlab:elastic:index_merge_requests](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes merge requests data.
- [sudo gitlab-rake gitlab:elastic:index_snippets](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Performs an Elasticsearch import that indexes the snippets data.
-- [sudo gitlab-rake gitlab:elastic:index_notes](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes the notes data.
-- [sudo gitlab-rake gitlab:elastic:index_milestones](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - Performs an Elasticsearch import that indexes the milestones data.
### Environment Variables
@@ -424,40 +360,16 @@ In addition to the rake tasks, there are some environment variables that can be
| Environment Variable | Data Type | What it does |
| -------------------- |:---------:| ---------------------------------------------------------------------------- |
-| `BATCH` | Integer | Modifies the size of the indexing batch (default 300 projects). |
| `UPDATE_INDEX` | Boolean | Tells the indexer to overwrite any existing index data (true/false). |
| `ID_TO` | Integer | Tells the indexer to only index projects less than or equal to the value. |
| `ID_FROM` | Integer | Tells the indexer to only index projects greater than or equal to the value. |
-### Batching
-
-The ability to apply batching makes the indexer run more efficiently. The default
-size of a batch is 300 projects, which may or may not be ideal for your setup.
-Depending on the resources available to your GitLab instance (sidekiq) and your
-Elasticsearch instance (RAM, CPU), you may be able to increase or decrease the
-batch size for more efficiency.
-
-- The larger the batch size is, the less sidekiq jobs and indexing requests get created.
-- The larger the batch size is, the more time and RAM it takes to process.
-- The smaller the batch size, the more sidekiq jobs, and indexing requests get created.
-- The smaller the batch size, the more CPU gets utilized.
-
-Finding the ideal size can be tricky, and will vary from GitLab instance to GitLab instance.
-Generally speaking, if the default is not ideal for you, try reducing it to somewhere in
-the 50-150 range (for bigger sized repos) or 450-600 range (for many small-sized repos).
-
-Example use:
-
-```sh
-sudo gitlab-rake gitlab:elastic:index_repositories_async BATCH=50
-```
-
### Indexing a specific project
Because the `ID_TO` and `ID_FROM` environment variables use the `or equal to` comparison, you can index only one project by using both these variables with the same project ID number:
```sh
-root@git:~# sudo gitlab-rake gitlab:elastic:index_repositories ID_TO=5 ID_FROM=5
+root@git:~# sudo gitlab-rake gitlab:elastic:index_projects ID_TO=5 ID_FROM=5
Indexing project repositories...I, [2019-03-04T21:27:03.083410 #3384] INFO -- : Indexing GitLab User / test (ID=33)...
I, [2019-03-04T21:27:05.215266 #3384] INFO -- : Indexing GitLab User / test (ID=33) is done!
```
@@ -554,7 +466,7 @@ Here are some common pitfalls and how to overcome them:
- **The indexing process is taking a very long time**
- The more data present in your GitLab instance, the longer the indexing process takes. You might want to try adjusting the BATCH sizes for asynchronous indexing to help speed up the process.
+ The more data present in your GitLab instance, the longer the indexing process takes.
- **No new data is added to the Elasticsearch index when I push code**
diff --git a/doc/integration/github.md b/doc/integration/github.md
index e145afbdd5e..5b01dd9feb7 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -21,10 +21,10 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
- Application name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- Homepage URL: The URL of your GitLab installation. For example, `https://gitlab.example.com`.
- Application description: Fill this in if you wish.
- - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth/github/callback`. Please make sure the port is included if your GitLab instance is not configured on default port.
+ - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth`. Please make sure the port is included if your GitLab instance is not configured on default port.
![Register OAuth App](img/github_register_app.png)
- NOTE: Be sure to append `/users/auth/github/callback` to the end of the callback URL
+ NOTE: Be sure to append `/users/auth` to the end of the callback URL
to prevent a [OAuth2 convert
redirect](http://tetraph.com/covert_redirect/) vulnerability.
diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md
index f60333aa07c..afe1268555a 100644
--- a/doc/integration/jenkins.md
+++ b/doc/integration/jenkins.md
@@ -19,6 +19,9 @@ GitLab's Jenkins integration allows you to trigger a Jenkins build when you
push code to a repository, or when a merge request is created. Additionally,
it shows the pipeline status on merge requests widgets and on the project's home page.
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For a video overview, see [Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF5Y).
+
## Use cases
- Suppose you are new to GitLab, and want to keep using Jenkins until you prepare
@@ -30,6 +33,8 @@ running with Jenkins, but view the results in your project's repository in GitLa
therefore, you opt for keep using Jenkins to build your apps. Show the results of your
pipelines directly in GitLab.
+For a real use case, read the blog post [Continuous integration: From Jenkins to GitLab using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/).
+
## Requirements
* [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
diff --git a/doc/integration/salesforce.md b/doc/integration/salesforce.md
index 8a99641a256..1ef43cfcece 100644
--- a/doc/integration/salesforce.md
+++ b/doc/integration/salesforce.md
@@ -1,27 +1,28 @@
-# SalesForce OmniAuth Provider
+# Salesforce OmniAuth Provider
-You can integrate your GitLab instance with [SalesForce](https://www.salesforce.com/) to enable users to login to your GitLab instance with their SalesForce account.
+You can integrate your GitLab instance with [Salesforce](https://www.salesforce.com/) to enable users to log in to your GitLab instance with their Salesforce account.
-## Create SalesForce Application
+## Create a Salesforce Connected App
-To enable SalesForce OmniAuth provider, you must use SalesForce's credentials for your GitLab instance.
-To get the credentials (a pair of Client ID and Client Secret), you must register an application on SalesForces.
+To enable Salesforce OmniAuth provider, you must use Salesforce's credentials for your GitLab instance.
+To get the credentials (a pair of Client ID and Client Secret), you must [create a Connected App](https://help.salesforce.com/articleView?id=connected_app_create.htm&type=5) on Salesforce.
-1. Sign in to [SalesForce](https://www.salesforce.com/).
+1. Sign in to [Salesforce](https://login.salesforce.com/).
-1. Navigate to **Platform Tools/Apps/App Manager** and click on **New Connected App**.
+1. In Setup, enter `App Manager` in the Quick Find box, click **App Manager**, then click **New Connected App**.
1. Fill in the application details into the following fields:
- **Connected App Name** and **API Name**: Set to any value but consider something like `<Organization>'s GitLab`, `<Your Name>'s GitLab`, or something else that is descriptive.
+ - **Contact Email**: Enter the contact email for Salesforce to use when contacting you or your support team.
- **Description**: Description for the application.
- ![SalesForce App Details](img/salesforce_app_details.png)
+ ![Salesforce App Details](img/salesforce_app_details.png)
1. Select **API (Enable OAuth Settings)** and click on **Enable OAuth Settings**.
1. Fill in the application details into the following fields:
- - **Callback URL**: The call callback URL. For example, `https://gitlab.example.com/users/auth/salesforce/callback`.
+ - **Callback URL**: The callback URL of your GitLab installation. For example, `https://gitlab.example.com/users/auth/salesforce/callback`.
- **Selected OAuth Scopes**: Move **Access your basic information (id, profile, email, address, phone)** and **Allow access to your unique identifier (openid)** to the right column.
- ![SalesForce Oauth App Details](img/salesforce_oauth_app_details.png)
+ ![Salesforce Oauth App Details](img/salesforce_oauth_app_details.png)
1. Click **Save**.
1. On your GitLab server, open the configuration file.
@@ -63,17 +64,16 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
app_secret: 'SALESFORCE_CLIENT_SECRET'
}
```
-1. Change `SALESFORCE_CLIENT_ID` to the Consumer Key from the SalesForce connected application page.
-1. Change `SALESFORCE_CLIENT_SECRET` to the Consumer Secret from the SalesForce connected application page.
- ![SalesForce App Secret Details](img/salesforce_app_secret_details.png)
+1. Change `SALESFORCE_CLIENT_ID` to the Consumer Key from the Salesforce connected application page.
+1. Change `SALESFORCE_CLIENT_SECRET` to the Consumer Secret from the Salesforce connected application page.
+ ![Salesforce App Secret Details](img/salesforce_app_secret_details.png)
1. Save the configuration file.
-1. [Reconfigure GitLab]( ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure ) or [restart GitLab]( ../administration/restart_gitlab.md#installations-from-source ) for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. [Reconfigure GitLab]( ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure ) or [restart GitLab]( ../administration/restart_gitlab.md#installations-from-source ) for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
-On the sign in page, there should now be a SalesForce icon below the regular sign in form.
-Click the icon to begin the authentication process. SalesForce will ask the user to sign in and authorize the GitLab application.
+On the sign in page, there should now be a Salesforce icon below the regular sign in form.
+Click the icon to begin the authentication process. Salesforce will ask the user to sign in and authorize the GitLab application.
If everything goes well, the user will be returned to GitLab and will be signed in.
NOTE: **Note:**
-GitLab requires the email address of each new user. Once the user is logged in using SalesForce, GitLab will redirect the user to the profile page where they will have to provide the email and verify the email.
+GitLab requires the email address of each new user. Once the user is logged in using Salesforce, GitLab will redirect the user to the profile page where they will have to provide the email and verify the email. \ No newline at end of file
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 15d9d8c9c74..22e07594d6f 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -1,6 +1,6 @@
# SAML OmniAuth Provider
-> This topic is for SAML on self-managed GitLab instances. For SAML on GitLab.com, see [SAML SSO for GitLab.com Groups](https://docs.gitlab.com/ee/user/group/saml_sso/index.html).
+> This topic is for SAML on self-managed GitLab instances. For SAML on GitLab.com, see [SAML SSO for GitLab.com Groups](../user/group/saml_sso/index.md).
NOTE: **Note:**
You need to [enable OmniAuth](omniauth.md) in order to use this.
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index cd755089be8..71ea2e25533 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -20,8 +20,8 @@ Taking the trigger term as `project-name`, the commands are:
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
| `/project-name run <job name> <arguments>` | Execute [ChatOps](../ci/chatops/README.md) job `<job name>` on `master` |
-Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for
-your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage).
+Note that if you are using the [GitLab Slack application](../user/project/integrations/gitlab_slack_application.md) for
+your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](../user/project/integrations/gitlab_slack_application.md#usage).
## Issue commands
diff --git a/doc/license/README.md b/doc/license/README.md
index b9281fd5299..fd110a39b61 100644
--- a/doc/license/README.md
+++ b/doc/license/README.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/admin_area/license.html'
+redirect_to: '../user/admin_area/license.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/admin_area/license.html).
+This document was moved to [another location](../user/admin_area/license.md).
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 4c4b423f40f..7659b743311 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Migrate GitLab CI to GitLab CE or EE
Beginning with version 8.0 of GitLab Community Edition (CE) and Enterprise
@@ -333,7 +337,9 @@ restoration](../raketasks/backup_restore.md) guide.
## Troubleshooting
### show:secrets problem (Omnibus-only)
+
If you see errors like this:
+
```
Missing `secret_key_base` or `db_key_base` for 'production' environment. The secrets will be generated and stored in `config/secrets.yml`
rake aborted!
@@ -344,6 +350,7 @@ This can happen if you are updating from versions prior to 7.13 straight to 8.0.
The fix for this is to update to Omnibus 7.14 first and then update it to 8.0.
### Permission denied when accessing /var/opt/gitlab/gitlab-ci/builds
+
To fix that issue you have to change builds/ folder permission before doing final backup:
```
sudo chown -R gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
@@ -355,8 +362,10 @@ sudo chown git:git /var/opt/gitlab/gitlab-ci/builds
```
### Problems when importing CI database to GitLab
+
If you were migrating CI database from MySQL to PostgreSQL manually you can see errors during import about missing sequences:
-```
+
+```sql
ALTER SEQUENCE
ERROR: relation "ci_builds_id_seq" does not exist
ERROR: relation "ci_commits_id_seq" does not exist
@@ -373,9 +382,9 @@ CREATE TABLE
To fix that you need to apply this SQL statement before doing final backup:
-```sql
-## Omnibus GitLab
+Omnibus GitLab installations:
+```sql
gitlab-ci-rails dbconsole <<EOF
-- ALTER TABLES - DROP DEFAULTS
ALTER TABLE ONLY ci_application_settings ALTER COLUMN id DROP DEFAULT;
@@ -428,9 +437,11 @@ ALTER TABLE ONLY ci_triggers ALTER COLUMN id SET DEFAULT nextval('ci_triggers_id
ALTER TABLE ONLY ci_variables ALTER COLUMN id SET DEFAULT nextval('ci_variables_id_seq'::regclass);
ALTER TABLE ONLY ci_web_hooks ALTER COLUMN id SET DEFAULT nextval('ci_web_hooks_id_seq'::regclass);
EOF
+```
-## Source installations
+Source installations:
+```
cd /home/gitlab_ci/gitlab-ci
sudo -u gitlab_ci -H bundle exec rails dbconsole production <<EOF
... COPY SQL STATEMENTS FROM ABOVE ...
diff --git a/doc/policy/maintenance.md b/doc/policy/maintenance.md
index 1d656574acd..72bace3d282 100644
--- a/doc/policy/maintenance.md
+++ b/doc/policy/maintenance.md
@@ -1,8 +1,15 @@
-# GitLab Maintenance Policy
+---
+type: concepts
+---
+
+# GitLab Release and Maintenance Policy
+
+GitLab has strict policies governing version naming, as well as release pace for major, minor,
+patch and security releases. New releases are usually announced on the [GitLab blog](https://about.gitlab.com/blog/categories/releases/).
## Versioning
-GitLab follows the [Semantic Versioning](http://semver.org/) for its releases:
+GitLab uses [Semantic Versioning](http://semver.org/) for its releases:
`(Major).(Minor).(Patch)` in a [pragmatic way](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e).
For example, for GitLab version 10.5.7:
@@ -15,9 +22,9 @@ Any part of the version number can increment into multiple digits, for example,
The following table describes the version types and their release cadence:
-| Version type | Description | Cadence |
-|:-------------|:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| Major | For significant changes, or when any backward-incompatible changes are introduced to the public API. | Yearly. The next major release is GitLab 12.0 on June 22, 2019. Subsequent major releases will be scheduled for May 22 each year, by default. | |
+| Version type | Description | Cadence |
+|:-------------|:------------|:--------|
+| Major | For significant changes, or when any backward-incompatible changes are introduced to the public API. | Yearly. The next major release is GitLab 12.0 on June 22, 2019. Subsequent major releases will be scheduled for May 22 each year, by default. |
| Minor | For when new backward-compatible functionality is introduced to the public API, a minor feature is introduced, or when a set of smaller features is rolled out. | Monthly on the 22nd. |
| Patch | For backward-compatible bug fixes that fix incorrect behavior. See [Patch releases](#patch-releases). | As needed. |
@@ -75,10 +82,10 @@ that could change behavior in the next major release.
Please see the table below for some examples:
| Latest stable version | Your version | Recommended upgrade path | Note |
-| -------------- | ------------ | ------------------------ | ---------------- |
-| 9.4.5 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.4.5` | `8.17.7` is the last version in version `8` |
-| 10.1.4 | 8.13.4 | `8.13.4 -> 8.17.7 -> 9.5.10 -> 10.1.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9` |
-| 11.3.4 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.5.10` -> `10.8.7` -> `11.3.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9`, `10.8.7` is the last version in version `10` |
+| --------------------- | ------------ | ------------------------ | ---- |
+| 9.4.5 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.4.5` | `8.17.7` is the last version in version `8` |
+| 10.1.4 | 8.13.4 | `8.13.4 -> 8.17.7 -> 9.5.10 -> 10.1.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9` |
+| 11.3.4 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.5.10` -> `10.8.7` -> `11.3.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9`, `10.8.7` is the last version in version `10` |
More information about the release procedures can be found in our
[release documentation](https://gitlab.com/gitlab-org/release/docs). You may also want to read our
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 8601551e3bd..b1637181855 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -1,13 +1,12 @@
-# Public access
-
-GitLab allows [Owners](../user/permissions.md) to change a projects' visibility in order to be accessed
-**publicly** or **internally**.
+---
+type: reference
+---
-Projects with either of these visibility levels will be listed in the
-public access directory (`/public` under your GitLab instance).
-Here is the [GitLab.com example](https://gitlab.com/public).
+# Public access
-Internal projects will only be available to authenticated users.
+GitLab allows [Owners](../user/permissions.md) to set a projects' visibility as **public**, **internal**
+or **private**. These visibility levels affect who can see the project in the
+public access directory (`/public` under your GitLab instance), like at [https://gitlab.com/public]().
## Visibility of projects
@@ -15,26 +14,26 @@ Internal projects will only be available to authenticated users.
Public projects can be cloned **without any** authentication.
-They will also be listed on the public access directory (`/public`).
+They will be listed in the public access directory (`/public`) for all users.
-**Any logged in user** will have [Guest](../user/permissions.md)
-permissions on the repository.
+**Any logged in user** will have [Guest permissions](../user/permissions.md)
+on the repository.
### Internal projects
Internal projects can be cloned by any logged in user.
-They will also be listed on the public access directory (`/public`) for logged
+They will also be listed in the public access directory (`/public`), but only for logged
in users.
-Any logged in user will have [Guest](../user/permissions.md) permissions
+Any logged in user will have [Guest permissions](../user/permissions.md)
on the repository.
### Private projects
-Private projects can only be cloned and viewed by project members, and
-they will only appear to project members on the public access directory
-(`https://gitlab.example.com/public`).
+Private projects can only be cloned and viewed by project members.
+
+They will appear in the public access directory (`/public`) for project members only.
### How to change project visibility
@@ -43,10 +42,10 @@ they will only appear to project members on the public access directory
## Visibility of groups
->**Note:**
-[Starting with][3323] GitLab 8.6, the group visibility has changed and can be
-configured the same way as projects. In previous versions, a group's page was
-always visible to all users.
+NOTE: **Note:**
+[Starting with](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3323) GitLab 8.6,
+the group visibility has changed and can be configured the same way as projects.
+In previous versions, a group's page was always visible to all users.
Like with projects, the visibility of a group can be set to dictate whether
anonymous users, all signed in users, or only explicit group members can view
@@ -54,8 +53,6 @@ it. The restriction for visibility levels on the application setting level also
applies to groups, so if that's set to internal, the explore page will be empty
for anonymous users. The group page now has a visibility level icon.
-[3323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3323
-
## Visibility of users
The public page of a user, located at `/username`, is always visible whether
@@ -76,3 +73,15 @@ snippet:
This is useful to prevent people exposing their repositories to public
by accident. The restricted visibility settings do not apply to admin users.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/push_rules/push_rules.md b/doc/push_rules/push_rules.md
index 7654023f266..b2d626a0a74 100644
--- a/doc/push_rules/push_rules.md
+++ b/doc/push_rules/push_rules.md
@@ -1,6 +1,11 @@
+---
+type: reference, howto
+---
+
# Push Rules **[STARTER]**
-Gain additional control over pushes to your repository.
+Gain additional control over what can and can't be pushed to your repository by using
+regular expressions to reject pushes based on commit contents, branch names or file details.
## Overview
@@ -33,7 +38,7 @@ will be accepted.
### Restrict branch names
-Let's assume there's a strictly policy for branch names in your company, and
+Let's assume there's a strict policy for branch names in your company, and
you want the branches to start with a certain name because you have different
GitLab CI jobs (`feature`, `hotfix`, `docker`, `android`, etc.) that rely on the
branch name.
@@ -46,7 +51,7 @@ will get rejected.
## Enabling push rules
->**Note:**
+NOTE: **Note:**
GitLab administrators can set push rules globally under
**Admin area > Push Rules** that all new projects will inherit. You can later
override them in a project's settings.
@@ -61,7 +66,7 @@ The following options are available.
| --------- | :------------: | ----------- |
| Removal of tags with `git push` | **Starter** 7.10 | Forbid users to remove git tags with `git push`. Tags will still be able to be deleted through the web UI. |
| Check whether author is a GitLab user | **Starter** 7.10 | Restrict commits by author (email) to existing GitLab users. |
-| Check whether committer is the current authenticated user | **Premium** 10.2 | GitLab will reject any commit that was not committed by the current authenticated user |
+| Committer restriction | **Premium** 10.2 | GitLab will reject any commit that was not committed by the current authenticated user |
| Check whether commit is signed through GPG | **Premium** 10.1 | Reject commit when it is not signed through GPG. Read [signing commits with GPG][signing-commits]. |
| Prevent committing secrets to Git | **Starter** 8.12 | GitLab will reject any files that are likely to contain secrets. Read [what files are forbidden](#prevent-pushing-secrets-to-the-repository). |
| Restrict by commit message | **Starter** 7.10 | Only commit messages that match this regular expression are allowed to be pushed. Leave empty to allow any commit message. Uses multiline mode, which can be disabled using `(?-m)`. |
@@ -71,8 +76,8 @@ The following options are available.
| Prohibited file names | **Starter** 7.10 | Any committed filenames that match this regular expression are not allowed to be pushed. Leave empty to allow any filenames. |
| Maximum file size | **Starter** 7.12 | Pushes that contain added or updated files that exceed this file size (in MB) are rejected. Set to 0 to allow files of any size. |
->**Tip:**
-GitLab uses [RE2 syntax](https://github.com/google/re2/wiki/Syntax) for regular expressions in push rules. You can check your regular expressions at <https://regex-golang.appspot.com>.
+TIP: **Tip:**
+GitLab uses [RE2 syntax](https://github.com/google/re2/wiki/Syntax) for regular expressions in push rules, and you can test them at the [GoLang regex tester](https://regex-golang.appspot.com).
## Prevent pushing secrets to the repository
@@ -86,7 +91,7 @@ pushes to the repository when a file matches a regular expression as read from
[`files_blacklist.yml`][list] (make sure you are at the right branch
as your GitLab version when viewing this file).
-NOTE: **Note**:
+NOTE: **Note:**
Files already committed won't get restricted by this push rule.
Below is an example list of what will be rejected by these regular expressions:
@@ -148,6 +153,18 @@ pry.history
bash_history
```
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[protected-branches]: ../user/project/protected_branches.md
[signing-commits]: ../user/project/repository/gpg_signed_commits/index.md
[ee-385]: https://gitlab.com/gitlab-org/gitlab-ee/issues/385
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 56db7b5eb3a..764916ca82d 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -917,9 +917,9 @@ backup beforehand.
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.
+ 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
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index bb316df5b9a..b59c06a24ea 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -16,7 +16,7 @@
The new folder needs to have git user ownership and read/write/execute access for git user and its group:
```
-sudo -u git mkdir /var/opt/gitlab/git-data/repository-import-<date>/new_group
+sudo -u git mkdir -p /var/opt/gitlab/git-data/repository-import-<date>/new_group
```
### Copy your bare repositories inside this newly created folder:
diff --git a/doc/security/README.md b/doc/security/README.md
index a90127e0356..c48d5bc2065 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -1,5 +1,6 @@
---
comments: false
+type: index
---
# Security
diff --git a/doc/security/crime_vulnerability.md b/doc/security/crime_vulnerability.md
index d61a205d954..9665cc0337f 100644
--- a/doc/security/crime_vulnerability.md
+++ b/doc/security/crime_vulnerability.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# How we manage the TLS protocol CRIME vulnerability
> CRIME ("Compression Ratio Info-leak Made Easy") is a security exploit against
@@ -7,15 +11,15 @@ authentication cookies, it allows an attacker to perform session hijacking on an
authenticated web session, allowing the launching of further attacks.
([CRIME](https://en.wikipedia.org/w/index.php?title=CRIME&oldid=692423806))
-### Description
+## Description
-The TLS Protocol CRIME Vulnerability affects compression over HTTPS, therefore
-it warns against using SSL Compression (for example gzip) or SPDY which
-optionally uses compression as well.
+The TLS Protocol CRIME Vulnerability affects systems that use data compression
+over HTTPS. Your system might be vulnerable to the CRIME vulnerability if you use
+SSL Compression (for example, gzip) or SPDY (which optionally uses compression).
GitLab supports both gzip and [SPDY][ngx-spdy] and mitigates the CRIME
-vulnerability by deactivating gzip when HTTPS is enabled. You can see the
-sources of the files in question:
+vulnerability by deactivating gzip when HTTPS is enabled. The sources of the
+files are here:
- [Source installation NGINX file][source-nginx]
- [Omnibus installation NGINX file][omnibus-nginx]
@@ -24,7 +28,7 @@ Although SPDY is enabled in Omnibus installations, CRIME relies on compression
(the 'C') and the default compression level in NGINX's SPDY module is 0
(no compression).
-### Nessus
+## Nessus
The Nessus scanner, [reports a possible CRIME vulnerability][nessus] in GitLab
similar to the following format:
@@ -45,12 +49,12 @@ SPDY support earlier than version 4 is advertised.
```
From the report above it is important to note that Nessus is only checking if
-TLS advertises the SPDY protocol earlier than version 4, it does not perform an
-attack nor does it check if compression is enabled. With just this approach, it
+TLS advertises the SPDY protocol earlier than version 4. It does not perform an
+attack nor does it check if compression is enabled. The Nessus scanner alone
cannot tell that SPDY's compression is disabled and not subject to the CRIME
vulnerability.
-### References
+## References
- Nginx ["Module ngx_http_spdy_module"][ngx-spdy]
- Tenable Network Security, Inc. ["Transport Layer Security (TLS) Protocol CRIME Vulnerability"][nessus]
@@ -61,3 +65,15 @@ vulnerability.
[ngx-spdy]: http://nginx.org/en/docs/http/ngx_http_spdy_module.html
[nessus]: https://www.tenable.com/plugins/index.php?view=single&id=62565
[wiki-crime]: https://en.wikipedia.org/wiki/CRIME
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/security/img/two_factor_authentication_group_settings.png b/doc/security/img/two_factor_authentication_group_settings.png
deleted file mode 100644
index 05d95554fd9..00000000000
--- a/doc/security/img/two_factor_authentication_group_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/security/img/two_factor_authentication_settings.png b/doc/security/img/two_factor_authentication_settings.png
deleted file mode 100644
index 2a2208f98bd..00000000000
--- a/doc/security/img/two_factor_authentication_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/security/information_exclusivity.md b/doc/security/information_exclusivity.md
index 22756232025..62a20d3f257 100644
--- a/doc/security/information_exclusivity.md
+++ b/doc/security/information_exclusivity.md
@@ -1,9 +1,36 @@
+---
+type: concepts
+---
# Information exclusivity
-Git is a distributed version control system (DVCS).
-This means that everyone that works with the source code has a local copy of the complete repository.
-In GitLab every project member that is not a guest (so reporters, developers and maintainers) can clone the repository to get a local copy.
-After obtaining this local copy the user can upload the full repository anywhere, including another project under their control or another server.
-The consequence is that you can't build access controls that prevent the intentional sharing of source code by users that have access to the source code.
-This is an inherent feature of a DVCS and all git management systems have this limitation.
-Obviously you can take steps to prevent unintentional sharing and information destruction, this is why only some people are allowed to invite others and nobody can force push a protected branch.
+Git is a distributed version control system (DVCS). This means that everyone
+who works with the source code has a local copy of the complete repository.
+
+In GitLab every project member that is not a guest (reporters, developers, and
+maintainers) can clone the repository to create a local copy. After obtaining
+a local copy, the user can upload the full repository anywhere, including to
+another project that is under their control, or onto another server.
+
+Therefore, it is impossible to build access controls that prevent the
+intentional sharing of source code by users that have access to the source code.
+
+This is an inherent feature of a DVCS. All git management systems have this
+limitation.
+
+You can take steps to prevent unintentional sharing and information
+destruction. This limitation is the reason why only certain people are allowed
+to [add users to a project](../user/project/members/index.md)
+and why only a GitLab admin can [force push a protected
+branch](../user/project/protected_branches.md).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/security/password_length_limits.md b/doc/security/password_length_limits.md
index d21b26a43e8..d78293c75c6 100644
--- a/doc/security/password_length_limits.md
+++ b/doc/security/password_length_limits.md
@@ -1,11 +1,28 @@
+---
+type: reference, howto
+---
# Custom password length limits
-If you want to enforce longer user passwords you can create an extra Devise initializer with the steps below.
+If you want to enforce longer user passwords you can create an extra Devise
+initializer with the steps below.
-If you do not use the `devise_password_length.rb` initializer the password length is set to a minimum of 8 characters in `config/initializers/devise.rb`.
+If you do not use the `devise_password_length.rb` initializer the password
+length is set to a minimum of 8 characters in `config/initializers/devise.rb`.
```bash
cd /home/git/gitlab
sudo -u git -H cp config/initializers/devise_password_length.rb.example config/initializers/devise_password_length.rb
sudo -u git -H editor config/initializers/devise_password_length.rb # inspect and edit the new password length limits
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index ad83dc05a93..fa4b0d1fb09 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -1,24 +1,28 @@
+---
+type: reference, howto
+---
# Rack Attack
-Rack Attack, also known as Rack::Attack, is [a rubygem](https://github.com/kickstarter/rack-attack)
+[Rack Attack](https://github.com/kickstarter/rack-attack), also known as Rack::Attack, is a Ruby gem
that is meant to protect GitLab with the ability to customize throttling and
-blocking user IPs.
+to block user IP addresses.
+
You can prevent brute-force passwords attacks, scrapers, or any other offenders
-by throttling requests from IP addresses making large volumes of requests.
-In case you find throttling is not enough to protect you against abusive clients,
-Rack Attack offers IP whitelisting, blacklisting, Fail2ban style filtering and
+by throttling requests from IP addresses that are making large volumes of requests.
+If you find throttling is not enough to protect you against abusive clients,
+Rack Attack offers IP whitelisting, blacklisting, Fail2ban style filtering, and
tracking.
**Note:** Starting with 11.2, Rack Attack is disabled by default. To continue
-using this feature, please enable it by [configuring `gitlab.rb` as described in Settings](#settings).
+using Rack Attack, please enable it by [configuring `gitlab.rb` as described in Settings](#settings).
By default, user sign-in, user sign-up (if enabled), and user password reset is
limited to 6 requests per minute. After trying for 6 times, the client will
have to wait for the next minute to be able to try again.
-If you installed or upgraded GitLab by following the [official guides](../install/README.md)
-this should be disabled by default. If your instance is not exposed to any incoming
-connections, it is recommended to leave Rack Attack disabled.
+If you installed or upgraded GitLab by following the [official guides](../install/README.md),
+Rack Attack should be disabled by default. If your instance is not exposed to any incoming
+connections, it is recommended that you leave Rack Attack disabled.
For more information on how to use these options check out
[rack-attack README](https://github.com/kickstarter/rack-attack/blob/master/README.md).
@@ -27,7 +31,7 @@ For more information on how to use these options check out
**Omnibus GitLab**
-1. Open `/etc/gitlab/gitlab.rb` with you editor
+1. Open `/etc/gitlab/gitlab.rb` with your editor
1. Add the following:
```ruby
@@ -53,7 +57,7 @@ The following settings can be configured:
For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3"]`.
- `maxretry`: The maximum amount of times a request can be made in the
specified time.
-- `findtime`: The maximum amount of time failed requests can count against an IP
+- `findtime`: The maximum amount of time that failed requests can count against an IP
before it's blacklisted (in seconds).
- `bantime`: The total amount of time that a blacklisted IP will be blocked (in
seconds).
@@ -94,7 +98,7 @@ In case you want to remove a blocked IP, follow these steps:
1. Find the IPs that have been blocked in the production log:
```sh
- grep "Rack_Attack" /var/log/gitlab/gitlab-rails/production.log
+ grep "Rack_Attack" /var/log/gitlab/gitlab-rails/auth.log
```
1. Since the blacklist is stored in Redis, you need to open up `redis-cli`:
diff --git a/doc/security/reset_root_password.md b/doc/security/reset_root_password.md
index 6a882ed6fe5..a58d70f0ff2 100644
--- a/doc/security/reset_root_password.md
+++ b/doc/security/reset_root_password.md
@@ -1,8 +1,11 @@
+---
+type: howto
+---
# How to reset your root password
-Log into your server with root privileges. Then start a Ruby on Rails console.
+To reset your root password, first log into your server with root privileges.
-Start the console with this command:
+Start a Ruby on Rails console with this command:
```bash
gitlab-rails console production
@@ -38,3 +41,15 @@ user.save!
```
Exit the console and try to login with your new password.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/security/ssh_keys_restrictions.md b/doc/security/ssh_keys_restrictions.md
index 6b6a8a06cc9..ae4cc44519e 100644
--- a/doc/security/ssh_keys_restrictions.md
+++ b/doc/security/ssh_keys_restrictions.md
@@ -1,3 +1,6 @@
+---
+type: reference, howto
+---
# Restrict allowed SSH key technologies and minimum length
`ssh-keygen` allows users to create RSA keys with as few as 768 bits, which
@@ -25,3 +28,15 @@ An icon will be visible to the user of a restricted key in the SSH keys section
![Restricted SSH key icon](img/ssh_keys_restricted_key_icon.png)
Hovering over this icon will tell you why the key is restricted.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 4b65b901487..ad5daef805a 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -1,3 +1,6 @@
+---
+type: howto
+---
# Enforce Two-factor Authentication (2FA)
Two-factor Authentication (2FA) provides an additional level of security to your
@@ -16,39 +19,35 @@ enforce everyone to set up 2FA, you can choose from two different ways:
- Enforce on next login.
- Suggest on next login, but allow a grace period before enforcing.
-In the Admin area under **Settings** (`/admin/application_settings`), look for
-the "Sign-in Restrictions" area, where you can configure both.
+After the configured grace period has elapsed, users will be able to log in but
+won't be able to leave the 2FA configuration area at `/profile/two_factor_auth`.
+
+To enable 2FA for all users:
+
+1. Navigate to **Admin area > Settings > General** (`/admin/application_settings`).
+1. Expand the **Sign-in restrictions** section, where you can configure both.
If you want 2FA enforcement to take effect on next login, change the grace
period to `0`.
----
-
-![Two factor authentication admin settings](img/two_factor_authentication_settings.png)
+## Enforcing 2FA for all users in a group
----
+If you want to enforce 2FA only for certain groups, you can:
-## Enforcing 2FA for all users in a group
+1. Enable it in the group's **Settings > General** page.
+1. Optionally specify a grace period as above.
-If you want to enforce 2FA only for certain groups, you can enable it in the
-group settings and specify a grace period as above. To change this setting you
-need to be administrator or owner of the group.
+To change this setting, you need to be administrator or owner of the group.
If there are multiple 2FA requirements (i.e. group + all users, or multiple
groups) the shortest grace period will be used.
----
-
-![Two factor authentication group settings](img/two_factor_authentication_group_settings.png)
-
----
-
## Disabling 2FA for everyone
There may be some special situations where you want to disable 2FA for everyone
even when forced 2FA is disabled. There is a rake task for that:
-```
+```sh
# Omnibus installations
sudo gitlab-rake gitlab:two_factor:disable_for_all_users
@@ -56,5 +55,18 @@ sudo gitlab-rake gitlab:two_factor:disable_for_all_users
sudo -u git -H bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production
```
-**IMPORTANT: this is a permanent and irreversible action. Users will have to
- reactivate 2FA from scratch if they want to use it again.**
+CAUTION: **Caution:**
+This is a permanent and irreversible action. Users will have to
+reactivate 2FA from scratch if they want to use it again.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/security/unlock_user.md b/doc/security/unlock_user.md
index d5ecef7f605..75cf754e197 100644
--- a/doc/security/unlock_user.md
+++ b/doc/security/unlock_user.md
@@ -1,8 +1,12 @@
+---
+type: howto
+---
# How to unlock a locked user
-Log into your server with root privileges. Then start a Ruby on Rails console.
+To unlock a locked user, first log into your server with root privileges.
+
+Start a Ruby on Rails console with this command:
-Start the console with this command:
```bash
gitlab-rails console production
@@ -29,3 +33,15 @@ user.unlock_access!
```
Exit the console, the user should now be able to log in again.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/security/user_email_confirmation.md b/doc/security/user_email_confirmation.md
index 8c07e11dcb1..f0af0a7ac6a 100644
--- a/doc/security/user_email_confirmation.md
+++ b/doc/security/user_email_confirmation.md
@@ -1,7 +1,23 @@
+---
+type: howto
+---
# User email confirmation at sign-up
-GitLab admin can enable email confirmation on sign-up, if you want to confirm all
-user emails before they are able to sign-in.
+GitLab can be configured to require confirmation of a user's email address when
+the user signs up. When this setting is enabled, the user is unable to sign in until
+they confirm their email address.
In the Admin area under **Settings** (`/admin/application_settings`), go to section
-**Sign-up Restrictions** and look for **Send confirmation email on sign-up** option.
+**Sign-up Restrictions** and look for the **Send confirmation email on sign-up** option.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/security/user_file_uploads.md b/doc/security/user_file_uploads.md
index 98493d33b00..00a2607b607 100644
--- a/doc/security/user_file_uploads.md
+++ b/doc/security/user_file_uploads.md
@@ -1,11 +1,30 @@
+---
+type: reference
+---
# User File Uploads
-Images attached to issues, merge requests or comments do not require authentication
-to be viewed if someone knows the direct URL. This direct URL contains a random
-32-character ID that prevents unauthorized people from guessing the URL to an
-image containing sensitive information. We don't enable authentication because
-these images need to be visible in the body of notification emails, which are
-often read from email clients that are not authenticated with GitLab, like
-Outlook, Apple Mail, or the Mail app on your mobile device.
+Images that are attached to issues, merge requests, or comments
+do not require authentication to be viewed if they are accessed directly by URL.
+This direct URL contains a random 32-character ID that prevents unauthorized
+people from guessing the URL for an image, thus there is some protection if an
+image contains sensitive information.
-Note that non-image attachments do require authentication to be viewed.
+Authentication is not enabled because images must be visible in the body of
+notification emails, which are often read from email clients that are not
+authenticated with GitLab, such as Outlook, Apple Mail, or the Mail app on your
+mobile device.
+
+>**Note:**
+Non-image attachments do require authentication to be viewed.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index 8c26bbac6a7..d4fa088cb15 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -1,20 +1,56 @@
+---
+type: concepts, reference, howto
+---
# Webhooks and insecure internal web services
-If you have non-GitLab web services running on your GitLab server or within its local network, these may be vulnerable to exploitation via Webhooks.
+If you have non-GitLab web services running on your GitLab server or within its
+local network, these may be vulnerable to exploitation via Webhooks.
-With [Webhooks](../user/project/integrations/webhooks.md), you and your project maintainers and owners can set up URLs to be triggered when specific things happen to projects. Normally, these requests are sent to external web services specifically set up for this purpose, that process the request and its attached data in some appropriate way.
+With [Webhooks](../user/project/integrations/webhooks.md), you and your project
+maintainers and owners can set up URLs to be triggered when specific changes
+occur in your projects. Normally, these requests are sent to external web services
+specifically set up for this purpose, that process the request and its attached
+data in some appropriate way.
-Things get hairy, however, when a Webhook is set up with a URL that doesn't point to an external, but to an internal service, that may do something completely unintended when the webhook is triggered and the POST request is sent.
+Things get hairy, however, when a Webhook is set up with a URL that doesn't
+point to an external, but to an internal service, that may do something
+completely unintended when the webhook is triggered and the POST request is
+sent.
-Because Webhook requests are made by the GitLab server itself, these have complete access to everything running on the server (`http://localhost:123`) or within the server's local network (`http://192.168.1.12:345`), even if these services are otherwise protected and inaccessible from the outside world.
+Because Webhook requests are made by the GitLab server itself, these have
+complete access to everything running on the server (`http://localhost:123`) or
+within the server's local network (`http://192.168.1.12:345`), even if these
+services are otherwise protected and inaccessible from the outside world.
-If a web service does not require authentication, Webhooks can be used to trigger destructive commands by getting the GitLab server to make POST requests to endpoints like `http://localhost:123/some-resource/delete`.
+If a web service does not require authentication, Webhooks can be used to
+trigger destructive commands by getting the GitLab server to make POST requests
+to endpoints like `http://localhost:123/some-resource/delete`.
-To prevent this type of exploitation from happening, starting with GitLab 10.6, all Webhook requests to the current GitLab instance server address and/or in a private network will be forbidden by default. That means that all requests made to 127.0.0.1, ::1 and 0.0.0.0, as well as IPv4 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 and IPv6 site-local (ffc0::/10) addresses won't be allowed.
+To prevent this type of exploitation from happening, starting with GitLab 10.6,
+all Webhook requests to the current GitLab instance server address and/or in a
+private network will be forbidden by default. That means that all requests made
+to 127.0.0.1, ::1 and 0.0.0.0, as well as IPv4 10.0.0.0/8, 172.16.0.0/12,
+192.168.0.0/16 and IPv6 site-local (ffc0::/10) addresses won't be allowed.
-This behavior can be overridden by enabling the option *"Allow requests to the local network from hooks and services"* in the *"Outbound requests"* section inside the Admin area under **Settings** (`/admin/application_settings/network`):
+This behavior can be overridden by enabling the option *"Allow requests to the
+local network from hooks and services"* in the *"Outbound requests"* section
+inside the Admin area under **Settings**
+(`/admin/application_settings/network`):
![Outbound requests admin settings](img/outbound_requests_section.png)
>**Note:**
-*System hooks* are exempt from this protection because they are set up by admins.
+*System hooks* are exempt from this protection because they are set up by
+admins.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 9c4a391e8da..09b5fbd9260 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -1,3 +1,7 @@
+---
+type: howto, reference
+---
+
# GitLab and SSH keys
Git is a distributed version control system, which means you can work locally
@@ -50,12 +54,11 @@ more information, you can read this
We'll focus on ED25519 and RSA and here.
NOTE: **Note:**
-As an admin, you can restrict
-[which keys should be permitted and their minimum length](../security/ssh_keys_restrictions.md).
+As an admin, you can [restrict which keys should be permitted and their minimum length](../security/ssh_keys_restrictions.md).
By default, all keys are permitted, which is also the case for
[GitLab.com](../user/gitlab_com/index.md#ssh-host-keys-fingerprints).
-## ED25519 SSH keys
+### ED25519 SSH keys
Following [best practices](https://linux-audit.com/using-ed25519-openssh-keys-instead-of-dsa-rsa-ecdsa/),
you should always favor [ED25519](https://ed25519.cr.yp.to/) SSH keys, since they
@@ -65,7 +68,7 @@ They were introduced in OpenSSH 6.5, so any modern OS should include the
option to create them. If for any reason your OS or the GitLab instance you
interact with doesn't support this, you can fallback to RSA.
-## RSA SSH keys
+### RSA SSH keys
RSA keys are the most common ones and therefore the most compatible with
servers that may have an old OpenSSH version. Use them if the GitLab server
@@ -91,9 +94,8 @@ ssh-keygen -o -f ~/.ssh/id_rsa
## Generating a new SSH key pair
-Before creating an SSH key pair, make sure to read about the
-[different types of keys](#types-of-ssh-keys-and-which-to-choose) to understand
-their differences.
+Before creating an SSH key pair, make sure to understand the
+[different types of keys](#types-of-ssh-keys-and-which-to-choose).
To create a new SSH key pair:
@@ -166,12 +168,13 @@ Now, it's time to add the newly created public key to your GitLab account.
NOTE: **Note:**
If you opted to create an RSA key, the name might differ.
-1. Add your public SSH key to your GitLab account by clicking your avatar
- in the upper right corner and selecting **Settings**. From there on,
- navigate to **SSH Keys** and paste your public key in the "Key" section.
- If you created the key with a comment, this will appear under "Title".
- If not, give your key an identifiable title like _Work Laptop_ or
- _Home Workstation_, and click **Add key**.
+1. Add your **public** SSH key to your GitLab account by:
+ 1. Clicking your avatar in the upper right corner and selecting **Settings**.
+ 1. Navigating to **SSH Keys** and pasting your **public** key in the **Key** field. If you:
+
+ - Created the key with a comment, this will appear in the **Title** field.
+ - Created the key without a comment, give your key an identifiable title like _Work Laptop_ or _Home Workstation_.
+ 1. Click the **Add key** button.
NOTE: **Note:**
If you manually copied your public SSH key make sure you copied the entire
@@ -305,7 +308,7 @@ who needs to know and configure the private key.
GitLab administrators set up Global Deploy keys in the Admin area under the
section **Deploy Keys**. Ensure keys have a meaningful title as that will be
the primary way for project maintainers and owners to identify the correct Global
-Deploy key to add. For instance, if the key gives access to a SaaS CI instance,
+Deploy key to add. For instance, if the key gives access to a SaaS CI instance,
use the name of that service in the key name if that is all it is used for.
When creating Global Shared Deploy keys, give some thought to the granularity
of keys - they could be of very narrow usage such as just a specific service or
@@ -331,7 +334,7 @@ not implicitly give any access just by setting them up.
### Eclipse
-How to add your SSH key to Eclipse: <https://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration>
+If you are using [EGit](https://www.eclipse.org/egit/), you can [add your SSH key to Eclipse](https://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration).
## SSH on the GitLab server
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index b6d16536fee..37051f6b10f 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -1,3 +1,7 @@
+---
+type: index, reference
+---
+
# Subscription setup and management
This page will help get you started with your new subscription or manage an existing one, whether you have subscribed to GitLab.com or self-managed GitLab.
@@ -38,11 +42,12 @@ Future purchases will use the information in this section. The email listed in t
### Self-managed: Apply your license file
-After purchase, the license file is sent to the email address tied to the Customers portal account, which needs to be [uploaded to the GitLab instance](https://docs.gitlab.com/ee/user/admin_area/license.html#uploading-your-license).
+After purchase, the license file is sent to the email address tied to the Customers portal account, which needs to be [uploaded to the GitLab instance](../user/admin_area/license.md#uploading-your-license).
### Link your GitLab.com account with your Customers Portal account
-NOTE: **Note:** This is *required* for GitLab.com subscriptions.
+NOTE: **Note:**
+This is *required* for GitLab.com subscriptions.
Once signed into the customers portal, if your account is not already linked, you should be prompted to link your account with a "Link my GitLab Account" button.
@@ -87,6 +92,14 @@ plan - in the included table:
| Subscription start date | The date your subscription started. If this is for a Free plan, this is the date you transitioned off your group's paid plan. |
| Subscription end date | The date your current subscription will end. This does not apply to Free plans. |
+### Subscription changes and your data
+
+When your subscription or trial expires, GitLab does not delete your data, however, depending on the tier and feature, it may become inaccessible. Please note that some features may not behave as expected if a graceful fallback is not currently implemented, such as [environment specific variables not being passed](https://gitlab.com/gitlab-org/gitlab-ce/issues/52825).
+
+If you renew or upgrade, your data will again be accessible.
+
+For self-managed customers, there is a two-week grace period when your features will continue to work as-is, after which the entire instance will become read only. However, if you remove the license, you will immediately revert to Core features.
+
## Need help?
[GitLab's Documentation](https://docs.gitlab.com/) offers a wide range of topics covering the use and administration of GitLab.
@@ -101,3 +114,15 @@ These issues are the best avenue for getting updates on specific product plans a
### Contacting Support
Learn more about the tiers of [GitLab Support](https://about.gitlab.com/support/) or [submit a request via the Support Portal](https://support.gitlab.com/hc/en-us/requests/new).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index a3698d60f6d..a46f7d30892 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# System hooks
Your GitLab instance can perform HTTP POST requests on the following events:
@@ -27,11 +31,9 @@ The triggers for most of these are self-explanatory, but `project_update` and `p
System hooks can be used, e.g. for logging or changing information in a LDAP server.
-> **Note:**
->
-> We follow the same structure from Webhooks for Push and Tag events, but we never display commits.
->
-> Same deprecations from Webhooks are valid here.
+NOTE: **Note:**
+We follow the same structure and deprecations as [Webhooks](../user/project/integrations/webhooks.md)
+for Push and Tag events, but we never display commits.
## Hooks request example
@@ -640,3 +642,15 @@ X-Gitlab-Event: System Hook
"refs":["refs/heads/master"]
}
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/tools/email.md b/doc/tools/email.md
index ab39206ffa4..a2d677484f0 100644
--- a/doc/tools/email.md
+++ b/doc/tools/email.md
@@ -1,12 +1,12 @@
-# Email from GitLab **[STARTER ONLY]**
-
-As a GitLab administrator you can email GitLab users from within GitLab.
+---
+type: howto, reference
+---
-## Overview
+# Email from GitLab **[STARTER ONLY]**
-GitLab provides a simple tool to email all users or users of a chosen group or
-project right from the admin area. Users will receive the email to their primary
-email address.
+GitLab provides a simple tool to administrators for emailing all users, or users of
+a chosen group or project, right from the admin area. Users will receive the email
+at their primary email address.
## Use-cases
@@ -28,11 +28,21 @@ email address.
## Unsubscribing from emails
-User can choose to unsubscribe from receiving emails from GitLab by following
-the unsubscribe link from the email. Unsubscribing is unauthenticated in order
-to keep the simplicity of this feature.
+Users can choose to unsubscribe from receiving emails from GitLab by following
+the unsubscribe link in the email. Unsubscribing is unauthenticated in order
+to keep this feature simple.
-On unsubscribe, user will receive an email notifying that unsubscribe happened.
+On unsubscribe, users will receive an email notification that unsubscribe happened.
The endpoint that provides the unsubscribe option is rate-limited.
-[ee]: https://about.gitlab.com/pricing/
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index 00b6c1dfdc2..228da2d1f57 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -17,11 +17,11 @@ This page gathers all the resources for the topic **Authentication** within GitL
## GitLab administrators
- [LDAP (Community Edition)](../../administration/auth/ldap.md)
-- [LDAP (Enterprise Edition)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html) **[STARTER]**
+- [LDAP (Enterprise Edition)](../../administration/auth/ldap-ee.md) **[STARTER]**
- [Enforce Two-factor Authentication (2FA)](../../security/two_factor_authentication.md#enforce-two-factor-authentication-2fa)
- **Articles:**
- [How to Configure LDAP with GitLab CE](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md)
- - [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html) **[STARTER]**
+ - [How to Configure LDAP with GitLab EE](../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md) **[STARTER]**
- [Feature Highlight: LDAP Integration](https://about.gitlab.com/2014/07/10/feature-highlight-ldap-sync/)
- [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/support-engineering/ldap/debugging_ldap.html)
- **Integrations:**
@@ -30,10 +30,10 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Atlassian Crowd OmniAuth Provider](../../administration/auth/crowd.md)
- [CAS OmniAuth Provider](../../integration/cas.md)
- [SAML OmniAuth Provider](../../integration/saml.md)
- - [SAML for GitLab.com Groups](https://docs.gitlab.com/ee/user/group/saml_sso/index.html) **[SILVER ONLY]**
- - [SCIM user provisioning for GitLab.com Groups](https://docs.gitlab.com/ee/user/group/saml_sso/scim_setup.html) **[SILVER ONLY]**
+ - [SAML for GitLab.com Groups](../../user/group/saml_sso/index.md) **[SILVER ONLY]**
+ - [SCIM user provisioning for GitLab.com Groups](../../user/group/saml_sso/scim_setup.md) **[SILVER ONLY]**
- [Okta SSO provider](../../administration/auth/okta.md)
- - [Kerberos integration (GitLab EE)](https://docs.gitlab.com/ee/integration/kerberos.html) **[STARTER]**
+ - [Kerberos integration (GitLab EE)](../../integration/kerberos.md) **[STARTER]**
## API
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 5a8744d71f9..2fd26bb4899 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -8,20 +8,23 @@ to simplify the setup and execution of a mature & modern software development li
## Overview
-NOTE: **Enabled by default:**
-Starting with GitLab 11.3, the Auto DevOps pipeline is enabled by default for all
-projects. If it has not been explicitly enabled for the project, Auto DevOps will be automatically
-disabled on the first pipeline failure. Your project will continue to use an alternative
-[CI/CD configuration file](../../ci/yaml/README.md) if one is found. A GitLab
-administrator can [change this setting](../../user/admin_area/settings/continuous_integration.html#auto-devops-core-only)
-in the admin area.
-
With Auto DevOps, the software development process becomes easier to set up
as every project can have a complete workflow from verification to monitoring
with minimal configuration. Just push your code and GitLab takes
care of everything else. This makes it easier to start new projects and brings
consistency to how applications are set up throughout a company.
+For a demonstration of Auto DevOps, watch the video [GitLab Auto DevOps demo](https://youtu.be/4Uo_QP9rSGM).
+
+## Enabled by default
+
+Starting with GitLab 11.3, the Auto DevOps pipeline is enabled by default for all
+projects. If it has not been explicitly enabled for the project, Auto DevOps will be automatically
+disabled on the first pipeline failure. Your project will continue to use an alternative
+[CI/CD configuration file](../../ci/yaml/README.md) if one is found. A GitLab
+administrator can [change this setting](../../user/admin_area/settings/continuous_integration.md#auto-devops-core-only)
+in the admin area.
+
## Quick start
If you are using GitLab.com, see the [quick start guide](quick_start_guide.md)
@@ -62,7 +65,7 @@ project in a simple and automatic way:
1. [Auto SAST (Static Application Security Testing)](#auto-sast-ultimate) **[ULTIMATE]**
1. [Auto Dependency Scanning](#auto-dependency-scanning-ultimate) **[ULTIMATE]**
1. [Auto License Management](#auto-license-management-ultimate) **[ULTIMATE]**
-1. [Auto Container Scanning](#auto-container-scanning)
+1. [Auto Container Scanning](#auto-container-scanning-ultimate) **[ULTIMATE]**
1. [Auto Review Apps](#auto-review-apps)
1. [Auto DAST (Dynamic Application Security Testing)](#auto-dast-ultimate) **[ULTIMATE]**
1. [Auto Deploy](#auto-deploy)
@@ -120,16 +123,11 @@ To make full use of Auto DevOps, you will need:
[default service template](../../user/project/integrations/services_templates.md)
for the entire GitLab installation.
-NOTE: **Note:**
If you do not have Kubernetes or Prometheus installed, then Auto Review Apps,
Auto Deploy, and Auto Monitoring will be silently skipped.
## Auto DevOps base domain
-NOTE: **Note**
-`AUTO_DEVOPS_DOMAIN` environment variable is deprecated and
-[is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959).
-
The Auto DevOps base domain is required if you want to make use of [Auto
Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It can be defined
in any of the following places:
@@ -139,10 +137,13 @@ in any of the following places:
- or at the project level as a variable: `KUBE_INGRESS_BASE_DOMAIN`
- or at the group level as a variable: `KUBE_INGRESS_BASE_DOMAIN`.
-NOTE: **Note**
-The Auto DevOps base domain variable (`KUBE_INGRESS_BASE_DOMAIN`) follows the same order of precedence
+The base domain variable `KUBE_INGRESS_BASE_DOMAIN` follows the same order of precedence
as other environment [variables](../../ci/variables/README.md#priority-of-environment-variables).
+NOTE: **Note**
+`AUTO_DEVOPS_DOMAIN` environment variable is deprecated and
+[is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959).
+
A wildcard DNS A record matching the base domain(s) is required, for example,
given a base domain of `example.com`, you'd need a DNS entry like:
@@ -162,6 +163,12 @@ Auto DevOps base domain to `1.2.3.4.nip.io`.
Once set up, all requests will hit the load balancer, which in turn will route
them to the Kubernetes pods that run your application(s).
+NOTE: **Note:**
+From GitLab 11.8, `KUBE_INGRESS_BASE_DOMAIN` replaces `AUTO_DEVOPS_DOMAIN`.
+Support for `AUTO_DEVOPS_DOMAIN` was [removed in GitLab
+12.0](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959).
+
+
## Using multiple Kubernetes clusters **[PREMIUM]**
When using Auto DevOps, you may want to deploy different environments to
@@ -179,7 +186,7 @@ Those environments are tied to jobs that use [Auto Deploy](#auto-deploy), so
except for the environment scope, they would also need to have a different
domain they would be deployed to. This is why you need to define a separate
`KUBE_INGRESS_BASE_DOMAIN` variable for all the above
-[based on the environment](https://docs.gitlab.com/ee/ci/variables/#limiting-environment-scopes-of-environment-variables-premium).
+[based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium).
The following table is an example of how the three different clusters would
be configured.
@@ -209,10 +216,6 @@ and verifying that your app is deployed as a review app in the Kubernetes
cluster with the `review/*` environment scope. Similarly, you can check the
other environments.
-NOTE: **Note:**
-From GitLab 11.8, `KUBE_INGRESS_BASE_DOMAIN` replaces `AUTO_DEVOPS_DOMAIN`.
-`AUTO_DEVOPS_DOMAIN` [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959).
-
## Enabling/Disabling Auto DevOps
When first using Auto Devops, review the [requirements](#requirements) to ensure all necessary components to make
@@ -224,19 +227,20 @@ can enable/disable Auto DevOps at either the project-level or instance-level.
### Enabling/disabling Auto DevOps at the instance-level (Administrators only)
+Even when disabled at the instance level, group owners and project maintainers can still enable
+Auto DevOps at the group and project level, respectively.
+
1. Go to **Admin area > Settings > Continuous Integration and Deployment**.
1. Toggle the checkbox labeled **Default to Auto DevOps pipeline for all projects**.
1. If enabling, optionally set up the Auto DevOps [base domain](#auto-devops-base-domain) which will be used for Auto Deploy and Auto Review Apps.
1. Click **Save changes** for the changes to take effect.
-NOTE: **Note:**
-Even when disabled at the instance level, group owners and project maintainers are still able to enable
-Auto DevOps at group-level and project-level, respectively.
-
### Enabling/disabling Auto DevOps at the group-level
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52447) in GitLab 11.10.
+Only administrators and group owners can enable or disable Auto DevOps at the group level.
+
To enable or disable Auto DevOps at the group-level:
1. Go to group's **Settings > CI/CD > Auto DevOps** page.
@@ -247,9 +251,6 @@ When enabling or disabling Auto DevOps at group-level, group configuration will
the subgroups and projects inside that group, unless Auto DevOps is specifically enabled or disabled on
the subgroup or project.
-NOTE: **Note**
-Only administrators and group owners are allowed to enable or disable Auto DevOps at group-level.
-
### Enabling/disabling Auto DevOps at the project-level
If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one exists, remove it.
@@ -263,16 +264,13 @@ If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one
When the feature has been enabled, an Auto DevOps pipeline is triggered on the default branch.
-NOTE: **Note:**
-For GitLab versions 10.0 - 10.2, when enabling Auto DevOps, a pipeline needs to be
-manually triggered either by pushing a new commit to the repository or by visiting
-`https://example.gitlab.com/<username>/<project>/pipelines/new` and creating
-a new pipeline for your default branch, generally `master`.
+### Feature flag to enable for a percentage of projects
-NOTE: **Note:**
-There is also a feature flag to enable Auto DevOps to a percentage of projects
-which can be enabled from the console with
-`Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(10)`.
+There is also a feature flag to enable Auto DevOps by default to your chosen percentage of projects.
+
+This can be enabled from the console with the following, which uses the example of 10%:
+
+`Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(10)`
### Deployment strategy
@@ -352,7 +350,6 @@ frameworks are detected automatically, but if your language is not detected,
you may succeed with a [custom buildpack](#custom-buildpacks). Check the
[currently supported languages](#currently-supported-languages).
-NOTE: **Note:**
Auto Test uses tests you already have in your application. If there are no
tests, it's up to you to add them.
@@ -365,7 +362,7 @@ created, and is uploaded as an artifact which you can later download and check
out.
Any differences between the source and target branches are also
-[shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html).
+[shown in the merge request widget](../../user/project/merge_requests/code_quality.md).
### Auto SAST **[ULTIMATE]**
@@ -373,18 +370,14 @@ Any differences between the source and target branches are also
Static Application Security Testing (SAST) uses the
[SAST Docker image](https://gitlab.com/gitlab-org/security-products/sast) to run static
-analysis on the current code and checks for potential security issues. Once the
-report is created, it's uploaded as an artifact which you can later download and
+analysis on the current code and checks for potential security issues. The
+the Auto SAST stage will be skipped on licenses other than Ultimate and requires GitLab Runner 11.5 or above.
+
+Once the report is created, it's uploaded as an artifact which you can later download and
check out.
Any security warnings are also shown in the merge request widget. Read more how
-[SAST works](https://docs.gitlab.com/ee/user/application_security/sast/index.html).
-
-NOTE: **Note:**
-The Auto SAST stage will be skipped on licenses other than Ultimate.
-
-NOTE: **Note:**
-The Auto SAST job requires GitLab Runner 11.5 or above.
+[SAST works](../../user/application_security/sast/index.md).
### Auto Dependency Scanning **[ULTIMATE]**
@@ -392,18 +385,16 @@ The Auto SAST job requires GitLab Runner 11.5 or above.
Dependency Scanning uses the
[Dependency Scanning Docker image](https://gitlab.com/gitlab-org/security-products/dependency-scanning)
-to run analysis on the project dependencies and checks for potential security issues. Once the
+to run analysis on the project dependencies and checks for potential security issues.
+The Auto Dependency Scanning stage will be skipped on licenses other than Ultimate
+and requires GitLab Runner 11.5 or above.
+
+Once the
report is created, it's uploaded as an artifact which you can later download and
check out.
Any security warnings are also shown in the merge request widget. Read more about
-[Dependency Scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html).
-
-NOTE: **Note:**
-The Auto Dependency Scanning stage will be skipped on licenses other than Ultimate.
-
-NOTE: **Note:**
-The Auto Dependency Scanning job requires GitLab Runner 11.5 or above.
+[Dependency Scanning](../../user/application_security/dependency_scanning/index.md).
### Auto License Management **[ULTIMATE]**
@@ -411,35 +402,34 @@ The Auto Dependency Scanning job requires GitLab Runner 11.5 or above.
License Management uses the
[License Management Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
-to search the project dependencies for their license. Once the
+to search the project dependencies for their license. The Auto License Management stage
+will be skipped on licenses other than Ultimate.
+
+Once the
report is created, it's uploaded as an artifact which you can later download and
check out.
Any licenses are also shown in the merge request widget. Read more how
-[License Management works](https://docs.gitlab.com/ee/user/application_security/license_management/index.html).
+[License Management works](../../user/application_security/license_management/index.md).
-NOTE: **Note:**
-The Auto License Management stage will be skipped on licenses other than Ultimate.
-
-### Auto Container Scanning
+### Auto Container Scanning **[ULTIMATE]**
> Introduced in GitLab 10.4.
Vulnerability Static Analysis for containers uses
[Clair](https://github.com/coreos/clair) to run static analysis on a
-Docker image and checks for potential security issues. Once the report is
+Docker image and checks for potential security issues. The Auto Container Scanning stage
+will be skipped on licenses other than Ultimate.
+
+Once the report is
created, it's uploaded as an artifact which you can later download and
check out.
Any security warnings are also shown in the merge request widget. Read more how
-[Container Scanning works](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html).
-
-NOTE: **Note:**
-The Auto Container Scanning stage will be skipped on licenses other than Ultimate.
+[Container Scanning works](../../user/application_security/container_scanning/index.md).
### Auto Review Apps
-NOTE: **Note:**
This is an optional step, since many projects do not have a Kubernetes cluster
available. If the [requirements](#requirements) are not met, the job will
silently be skipped.
@@ -484,14 +474,13 @@ in the first place, and thus not realize that it needs to re-apply the old confi
Dynamic Application Security Testing (DAST) uses the
popular open source tool [OWASP ZAProxy](https://github.com/zaproxy/zaproxy)
to perform an analysis on the current code and checks for potential security
-issues. Once the report is created, it's uploaded as an artifact which you can
+issues. The Auto DAST stage will be skipped on licenses other than Ultimate.
+
+Once the report is created, it's uploaded as an artifact which you can
later download and check out.
Any security warnings are also shown in the merge request widget. Read how
-[DAST works](https://docs.gitlab.com/ee/user/application_security/dast/index.html).
-
-NOTE: **Note:**
-The Auto DAST stage will be skipped on licenses other than Ultimate.
+[DAST works](../../user/application_security/dast/index.md).
### Auto Browser Performance Testing **[PREMIUM]**
@@ -506,11 +495,10 @@ Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://h
```
Any performance differences between the source and target branches are also
-[shown in the merge request widget](https://docs.gitlab.com/ee//user/project/merge_requests/browser_performance_testing.html).
+[shown in the merge request widget](../../user/project/merge_requests/browser_performance_testing.md).
### Auto Deploy
-NOTE: **Note:**
This is an optional step, since many projects do not have a Kubernetes cluster
available. If the [requirements](#requirements) are not met, the job will
silently be skipped.
@@ -549,7 +537,7 @@ in the first place, and thus not realize that it needs to re-apply the old confi
For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token)
will be automatically created, when Auto DevOps is enabled and the Auto DevOps settings are saved. This Deploy Token
-can be used for permanent access to the registry.
+can be used for permanent access to the registry. When the GitLab Deploy Token has been manually revoked, it won't be automatically created.
If the GitLab Deploy Token cannot be found, `CI_REGISTRY_PASSWORD` is
used. Note that `CI_REGISTRY_PASSWORD` is only valid during deployment.
@@ -559,9 +547,6 @@ be pulled again, e.g. after pod eviction, Kubernetes will fail to do so
as it will be attempting to fetch the image using
`CI_REGISTRY_PASSWORD`.
-NOTE: **Note:**
-When the GitLab Deploy Token has been manually revoked, it won't be automatically created.
-
#### Migrations
> [Introduced][ce-21955] in GitLab 11.4
@@ -584,22 +569,19 @@ Note that a post-install hook means that if any deploy succeeds,
If present, `DB_MIGRATE` will be run as a shell command within an application pod as
a helm pre-upgrade hook.
-For example, in a Rails application:
+For example, in a Rails application in an image built with
+[Herokuish](https://github.com/gliderlabs/herokuish):
-- `DB_INITIALIZE` can be set to `cd /app && RAILS_ENV=production
- bin/setup`
-- `DB_MIGRATE` can be set to `cd /app && RAILS_ENV=production bin/update`
+- `DB_INITIALIZE` can be set to `RAILS_ENV=production /bin/herokuish procfile exec bin/rails db:setup`
+- `DB_MIGRATE` can be set to `RAILS_ENV=production /bin/herokuish procfile exec bin/rails db:migrate`
-NOTE: **Note:**
-The `/app` path is the directory of your project inside the docker image
-as [configured by
-Herokuish](https://github.com/gliderlabs/herokuish#paths)
+Unless you have a `Dockerfile` in your repo, your image is built with
+Herokuish, and you must prefix commands run in these images with `/bin/herokuish
+procfile exec` to replicate the environment where your application will run.
### Auto Monitoring
-NOTE: **Note:**
-Check the [requirements](#requirements) for Auto Monitoring to make this stage
-work.
+See the [requirements](#requirements) for Auto Monitoring to enable this stage.
Once your application is deployed, Auto Monitoring makes it possible to monitor
your application's server and response metrics right out of the box. Auto
@@ -675,7 +657,7 @@ repo or by specifying a project variable:
### Custom Helm chart per environment **[PREMIUM]**
You can specify the use of a custom Helm chart per environment by scoping the environment variable
-to the desired environment. See [Limiting environment scopes of variables](https://docs.gitlab.com/ee/ci/variables/#limiting-environment-scopes-of-variables-premium).
+to the desired environment. See [Limiting environment scopes of variables](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium).
### Customizing `.gitlab-ci.yml`
@@ -734,7 +716,6 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| **Variable** | **Description** |
| ------------ | --------------- |
-| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-base-domain). By default, set automatically by the [Auto DevOps setting](#enablingdisabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959). Use `KUBE_INGRESS_BASE_DOMAIN` instead. |
| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). |
| `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From Gitlab 11.11, this variable can be used to set the name of the helm repository; defaults to "gitlab" |
@@ -742,8 +723,8 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From Gitlab 11.11, this variable can be used to set a password to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_USERNAME) |
| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. This takes precedence over `REPLICAS`; defaults to 1. |
-| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html); defaults to 1 |
-| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 |
+| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md); defaults to 1 |
+| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 |
| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
@@ -828,11 +809,6 @@ metadata:
type: Opaque
```
-CAUTION: **Caution:**
-Variables with multiline values are not currently supported due to
-limitations with the current Auto DevOps scripting environment.
-
-NOTE: **Note:**
Environment variables are generally considered immutable in a Kubernetes
pod. Therefore, if you update an application secret without changing any
code then manually create a new pipeline, you will find that any running
@@ -841,6 +817,10 @@ can either push a code update to GitLab to force the Kubernetes
Deployment to recreate pods or manually delete running pods to
cause Kubernetes to create new pods with updated secrets.
+NOTE: **Note:**
+Variables with multiline values are not currently supported due to
+limitations with the current Auto DevOps scripting environment.
+
#### Advanced replica variables setup
Apart from the two replica-related variables for production mentioned above,
@@ -920,7 +900,7 @@ you when you're ready to manually deploy to production.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ci-yml/merge_requests/171)
in GitLab 11.0.
-A [canary environment](https://docs.gitlab.com/ee/user/project/canary_deployments.html) can be used
+A [canary environment](../../user/project/canary_deployments.md) can be used
before any changes are deployed to production.
If `CANARY_ENABLED` is defined in your project (e.g., set `CANARY_ENABLED` to
@@ -1009,8 +989,7 @@ Everything behaves the same way, except:
## Currently supported languages
-NOTE: **Note:**
-Not all buildpacks support Auto Test yet, as it's a relatively new
+Note that not all buildpacks support Auto Test yet, as it's a relatively new
enhancement. All of Heroku's [officially supported
languages](https://devcenter.heroku.com/articles/heroku-ci#currently-supported-languages)
support it, and some third-party buildpacks as well e.g., Go, Node, Java, PHP,
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index 367e192b85a..6717e95266e 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -161,7 +161,7 @@ In the **test** stage, GitLab runs various checks on the application:
- The `code_quality` job checks the code quality and is allowed to fail
([Auto Code Quality](index.md#auto-code-quality-starter)) **[STARTER]**
- The `container_scanning` job checks the Docker container if it has any
- vulnerabilities and is allowed to fail ([Auto Container Scanning](index.md#auto-container-scanning))
+ vulnerabilities and is allowed to fail ([Auto Container Scanning](index.md#auto-container-scanning-ultimate))
- The `dependency_scanning` job checks if the application has any dependencies
susceptible to vulnerabilities and is allowed to fail ([Auto Dependency Scanning](index.md#auto-dependency-scanning-ultimate)) **[ULTIMATE]**
- The `sast` job runs static analysis on the current code to check for potential
@@ -208,7 +208,7 @@ applications. In the rightmost column for the production environment, you can ma
application is running.
Right below, there is the
-[Deploy Board](https://docs.gitlab.com/ee/user/project/deploy_boards.html).
+[Deploy Board](../../user/project/deploy_boards.md).
The squares represent pods in your Kubernetes cluster that are associated with
the given environment. Hovering above each square you can see the state of a
deployment and clicking a square will take you to the pod's logs page.
diff --git a/doc/topics/git/how_to_install_git/index.md b/doc/topics/git/how_to_install_git/index.md
index d7e1979217e..c7bede2d269 100644
--- a/doc/topics/git/how_to_install_git/index.md
+++ b/doc/topics/git/how_to_install_git/index.md
@@ -5,14 +5,20 @@ level: beginner
article_type: user guide
date: 2017-05-15
description: 'This article describes how to install Git on macOS, Ubuntu Linux and Windows.'
+type: howto
+last_updated: 2019-05-31
---
# Installing Git
-To begin contributing to GitLab projects
+To begin contributing to GitLab projects,
you will need to install the Git client on your computer.
+
This article will show you how to install Git on macOS, Ubuntu Linux and Windows.
+Information on [installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+is also available at the official Git website.
+
## Install Git on macOS using the Homebrew package manager
Although it is easy to use the version of Git shipped with macOS
@@ -21,7 +27,7 @@ we recommend installing it via Homebrew to get access to
an extensive selection of dependency managed libraries and applications.
If you are sure you don't need access to any additional development libraries
-or don't have approximately 15gb of available disk space for Xcode and Homebrew
+or don't have approximately 15gb of available disk space for Xcode and Homebrew,
use one of the aforementioned methods.
### Installing Xcode
@@ -40,11 +46,12 @@ for the official Homebrew installation instructions.
With Homebrew installed you are now ready to install Git.
Open a Terminal and enter in the following command:
-```bash
+```sh
brew install git
```
Congratulations you should now have Git installed via Homebrew.
+
Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
## Install Git on Ubuntu Linux
@@ -55,16 +62,30 @@ it is recommended to use the built in package manager to install Git.
Open a Terminal and enter in the following commands
to install the latest Git from the official Git maintained package archives:
-```bash
+```sh
sudo apt-add-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
```
Congratulations you should now have Git installed via the Ubuntu package manager.
+
Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
## Installing Git on Windows from the Git website
Browse to the [Git website](https://git-scm.com/) and download and install Git for Windows.
+
Next read our article on [adding an SSH key to GitLab](../../../ssh/README.md).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md
index 7707d56764e..cdcd8215b23 100644
--- a/doc/topics/git/index.md
+++ b/doc/topics/git/index.md
@@ -1,8 +1,12 @@
-# Git documentation
+---
+type: index
+---
+
+# Git
Git is a [free and open source](https://git-scm.com/about/free-and-open-source)
distributed version control system designed to handle everything from small to
-very large projects with speed and efficiency.
+large projects with speed and efficiency.
[GitLab](https://about.gitlab.com) is a Git-based fully integrated platform for
software development. Besides Git's functionalities, GitLab has a lot of
@@ -11,64 +15,73 @@ powerful [features](https://about.gitlab.com/features/) to enhance your
We've gathered some resources to help you to get the best from Git with GitLab.
+More information is also available on the [Git website](https://git-scm.com).
+
## Getting started
-- [Git concepts](../../university/training/user_training.md#git-concepts)
+The following resources will help you get started with Git:
+
+- [Git-ing started with Git](https://www.youtube.com/watch?v=Ce5nz5n41z4),
+ a video introduction to Git.
+- [Git Basics](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics)
+- [Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab)
- [How to install Git](how_to_install_git/index.md)
- [Start using Git on the command line](../../gitlab-basics/start-using-git.md)
- [Command Line basic commands](../../gitlab-basics/command-line-commands.md)
- [GitLab Git Cheat Sheet (download)](https://about.gitlab.com/images/press/git-cheat-sheet.pdf)
-- Commits
- - [Revert a commit](../../user/project/merge_requests/revert_changes.md#reverting-a-commit)
+- Commits:
+ - [Revert a commit](../../user/project/merge_requests/revert_changes.md#reverting-a-commit)
- [Cherry-picking a commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
- [Squashing commits](../../workflow/gitlab_flow.md#squashing-commits-with-rebase)
-**Third-party references:**
+### Concepts
-- [Getting Started - Git website](https://git-scm.com)
-- [Getting Started - Version control](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control)
-- [Getting Started - Git Basics](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics)
-- [Getting Started - Installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
-- [Git on the Server - GitLab](https://git-scm.com/book/en/v2/Git-on-the-Server-GitLab)
+The following are resources about version control concepts:
-### Concepts
+- [Git concepts](../../university/training/user_training.md#git-concepts)
+- [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/)
+- [The future of SaaS hosted Git repository pricing](https://about.gitlab.com/2016/05/11/git-repository-pricing/)
+- [Git website topic about version control](https://git-scm.com/book/en/v2/Getting-Started-About-Version-Control)
+- [GitLab University presentation about Version Control](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit?usp=sharing)
-- Article (2017-05-17): [Why Git is Worth the Learning Curve](https://about.gitlab.com/2017/05/17/learning-curve-is-the-biggest-challenge-developers-face-with-git/)
-- Article (2016-05-11): [The future of SaaS hosted Git repository pricing](https://about.gitlab.com/2016/05/11/git-repository-pricing/)
-- GLU Course (Presentation): [About Version Control](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit?usp=sharing)
+## Git tips
-## Exploring Git
+The following resources may help you become more efficient at using Git:
- [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/)
- [Eight Tips to help you work better with Git](https://about.gitlab.com/2015/02/19/8-tips-to-help-you-work-better-with-git/)
## Troubleshooting Git
+If you have problems with Git, the following may help:
+
- [Numerous _undo_ possibilities in Git](numerous_undo_possibilities_in_git/index.md)
-- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques to help you out.
+- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques
## Branching strategies
-- [GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)
-
-**Third-party references:**
-
- [Git Branching - Branches in a Nutshell](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell)
- [Git Branching - Branching Workflows](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows)
+- [GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)
## Advanced use
+The following are advanced topics for those who want to get the most out of Git:
+
- [Custom Git Hooks](../../administration/custom_hooks.md)
- [Git Attributes](../../user/project/git_attributes.md)
- Git Submodules: [Using Git submodules with GitLab CI](../../ci/git_submodules.md#using-git-submodules-with-gitlab-ci)
## API
-- [Gitignore templates](../../api/templates/gitignores.md)
+[Gitignore templates](../../api/templates/gitignores.md) API allow for
+Git-related queries from GitLab.
## Git LFS
+The following relate to Git Large File Storage:
+
- [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/)
- [GitLab Git LFS documentation](../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
-- [Git-Annex to Git-LFS migration guide](https://docs.gitlab.com/ee/workflow/lfs/migrate_from_git_annex_to_git_lfs.html)
-- Article (2015-08-13): [Towards a production quality open source Git LFS server](https://about.gitlab.com/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
+- [Git-Annex to Git-LFS migration guide](../../workflow/lfs/migrate_from_git_annex_to_git_lfs.md)
+- [Towards a production quality open source Git LFS server](https://about.gitlab.com/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
diff --git a/doc/topics/git/numerous_undo_possibilities_in_git/index.md b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
index 8a8021dc36d..84201e11831 100644
--- a/doc/topics/git/numerous_undo_possibilities_in_git/index.md
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
@@ -4,25 +4,30 @@ author_gitlab: Letme
level: intermediary
article_type: tutorial
date: 2017-05-15
+type: howto
+last_updated: 2019-05-31
---
# Numerous undo possibilities in Git
-## Introduction
-
In this tutorial, we will show you different ways of undoing your work in Git, for which
we will assume you have a basic working knowledge of. Check GitLab's
-[Git documentation](../index.md#git-documentation) for reference.
+[Git documentation](../index.md) for reference.
+
Also, we will only provide some general info of the commands, which is enough
-to get you started for the easy cases/examples, but for anything more advanced please refer to the [Git book](https://git-scm.com/book/en/v2).
+to get you started for the easy cases/examples, but for anything more advanced
+please refer to the [Git book](https://git-scm.com/book/en/v2).
We will explain a few different techniques to undo your changes based on the stage
of the change in your current development. Also, keep in mind that [nothing in
-Git is really deleted.][git-autoclean-ref]
+Git is really deleted][git-autoclean-ref].
+
This means that until Git automatically cleans detached commits (which cannot be
accessed by branch or tag) it will be possible to view them with `git reflog` command
and access them with direct commit-id. Read more about _[redoing the undo](#redoing-the-undo)_ on the section below.
+## Introduction
+
This guide is organized depending on the [stage of development][git-basics]
where you want to undo your changes from and if they were shared with other developers
or not. Because Git is tracking changes a created or edited file is in the unstaged state
@@ -31,35 +36,41 @@ a file into the **staged** state, which is then committed (`git commit`) to your
local repository. After that, file can be shared with other developers (`git push`).
Here's what we'll cover in this tutorial:
- - [Undo local changes](#undo-local-changes) which were not pushed to remote repository
+- [Undo local changes](#undo-local-changes) which were not pushed to remote repository:
- - Before you commit, in both unstaged and staged state
- - After you committed
+ - Before you commit, in both unstaged and staged state.
+ - After you committed.
- - Undo changes after they are pushed to remote repository
+- Undo changes after they are pushed to remote repository:
- - [Without history modification](#undo-remote-changes-without-changing-history) (preferred way)
- - [With history modification](#undo-remote-changes-with-modifying-history) (requires
- coordination with team and force pushes).
- - [Usecases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable)
- - [How to modify history](#how-modifying-history-is-done)
- - [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits)
+ - [Without history modification](#undo-remote-changes-without-changing-history) (preferred way).
+ - [With history modification](#undo-remote-changes-with-modifying-history) (requires
+ coordination with team and force pushes).
+ - [Use cases when modifying history is generally acceptable](#where-modifying-history-is-generally-acceptable).
+ - [How to modify history](#how-modifying-history-is-done).
+ - [How to remove sensitive information from repository](#deleting-sensitive-information-from-commits).
### Branching strategy
[Git][git-official] is a de-centralized version control system, which means that beside regular
versioning of the whole repository, it has possibilities to exchange changes
-with other repositories. To avoid chaos with
+with other repositories.
+
+To avoid chaos with
[multiple sources of truth][git-distributed], various
development workflows have to be followed, and it depends on your internal
workflow how certain changes or commits can be undone or changed.
+
[GitLab Flow][gitlab-flow] provides a good
balance between developers clashing with each other while
developing the same feature and cooperating seamlessly, but it does not enable
joined development of the same feature by multiple developers by default.
+
When multiple developers develop the same feature on the same branch, clashing
with every synchronization is unavoidable, but a proper or chosen Git Workflow will
-prevent that anything is lost or out of sync when feature is complete. You can also
+prevent that anything is lost or out of sync when feature is complete.
+
+You can also
read through this blog post on [Git Tips & Tricks][gitlab-git-tips-n-tricks]
to learn how to easily **do** things in Git.
@@ -97,19 +108,19 @@ no changes added to commit (use "git add" and/or "git commit -a")
At this point there are 3 options to undo the local changes you have:
-- Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes)
+- Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes):
```shell
git stash
```
-- Discarding local changes (permanently) to a file
+- Discarding local changes (permanently) to a file:
```shell
git checkout -- <file>
```
-- Discard all local changes to all files permanently
+- Discard all local changes to all files permanently:
```shell
git reset --hard
@@ -150,7 +161,7 @@ of the staging tree. You also have an option to discard all changes with
Lets start the example by editing a file, with your favorite editor, to change the
content and add it to staging
-```
+```sh
vim <file>
git add <file>
```
@@ -164,30 +175,30 @@ Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
- new file: <file>
+ new file: <file>
```
Now you have 4 options to undo your changes:
-- Unstage the file to current commit (HEAD)
+- Unstage the file to current commit (HEAD):
```shell
git reset HEAD <file>
```
-- Unstage everything - retain changes
+- Unstage everything - retain changes:
```shell
git reset
```
-- Discard all local changes, but save them for [later](#quickly-save-local-changes)
+- Discard all local changes, but save them for [later](#quickly-save-local-changes):
```shell
git stash
```
-- Discard everything permanently
+- Discard everything permanently:
```shell
git reset --hard
@@ -206,7 +217,9 @@ your code, you'll have less options to troubleshoot your work.
Through the development process some of the previously committed changes do not
fit anymore in the end solution, or are source of the bugs. Once you find the
commit which triggered bug, or once you have a faulty commit, you can simply
-revert it with `git revert commit-id`. This command inverts (swaps) the additions and
+revert it with `git revert commit-id`.
+
+This command inverts (swaps) the additions and
deletions in that commit, so that it does not modify history. Retaining history
can be helpful in future to notice that some changes have been tried
unsuccessfully in the past.
@@ -225,19 +238,19 @@ through simple bisection process. You can read more about it [in official Git To
In our example we will end up with commit `B`, that introduced bug/error. We have
4 options on how to remove it (or part of it) from our repository.
-- Undo (swap additions and deletions) changes introduced by commit `B`.
+- Undo (swap additions and deletions) changes introduced by commit `B`:
```shell
git revert commit-B-id
```
-- Undo changes on a single file or directory from commit `B`, but retain them in the staged state
+- Undo changes on a single file or directory from commit `B`, but retain them in the staged state:
```shell
git checkout commit-B-id <file>
```
-- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state
+- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state:
```shell
git reset commit-B-id <file>
@@ -246,7 +259,9 @@ In our example we will end up with commit `B`, that introduced bug/error. We hav
- There is one command we also must not forget: **creating a new branch**
from the point where changes are not applicable or where the development has hit a
dead end. For example you have done commits `A-B-C-D` on your feature-branch
- and then you figure `C` and `D` are wrong. At this point you either reset to `B`
+ and then you figure `C` and `D` are wrong.
+
+ At this point you either reset to `B`
and do commit `F` (which will cause problems with pushing and if forced pushed also with other developers)
since branch now looks `A-B-F`, which clashes with what other developers have locally (you will
[change history](#with-history-modification)), or you simply checkout commit `B` create
@@ -269,13 +284,13 @@ In our example we will end up with commit `B`, that introduced bug/error. We hav
There is one command for history modification and that is `git rebase`. Command
provides interactive mode (`-i` flag) which enables you to:
- - **reword** commit messages (there is also `git commit --amend` for editing
- last commit message)
- - **edit** the commit content (changes introduced by commit) and message
- - **squash** multiple commits into a single one, and have a custom or aggregated
- commit message
- - **drop** commits - simply delete them
- - and few more options
+- **reword** commit messages (there is also `git commit --amend` for editing
+ last commit message).
+- **edit** the commit content (changes introduced by commit) and message.
+- **squash** multiple commits into a single one, and have a custom or aggregated
+ commit message.
+- **drop** commits - simply delete them.
+- and few more options.
Let us check few examples. Again there are commits `A-B-C-D` where you want to
delete commit `B`.
@@ -301,7 +316,7 @@ In case you want to modify something introduced in commit `B`.
- Command opens your favorite text editor where you write `edit` in front of commit
`B`, but leave default `pick` with all other commits. Save and exit the editor to
- perform a rebase
+ perform a rebase.
- Now do your edits and commit changes:
@@ -348,7 +363,9 @@ and then on end description of that action.
This topic is roughly same as modifying committed local changes without modifying
history. **It should be the preferred way of undoing changes on any remote repository
or public branch.** Keep in mind that branching is the best solution when you want
-to retain the history of faulty development, yet start anew from certain point. Branching
+to retain the history of faulty development, yet start anew from certain point.
+
+Branching
enables you to include the existing changes in new development (by merging) and
it also provides a clear timeline and development structure.
@@ -386,12 +403,14 @@ the cleanup of detached commits (happens automatically).
Modified history breaks the development chain of other developers, as changed
history does not have matching commits'ids. For that reason it should not
be used on any public branch or on branch that *might* be used by other
-developers. When contributing to big open source repositories (e.g. [GitLab CE][gitlab-ce]),
+developers. When contributing to big open source repositories (for example, [GitLab CE][gitlab-ce]),
it is acceptable to *squash* commits into a single one, to present
a nicer history of your contribution.
+
Keep in mind that this also removes the comments attached to certain commits
in merge requests, so if you need to retain traceability in GitLab, then
modifying history is not acceptable.
+
A feature-branch of a merge request is a public branch and might be used by
other developers, but project process and rules might allow or require
you to use `git rebase` (command that changes history) to reduce number of
@@ -400,8 +419,8 @@ GitLab). There is a `git merge --squash` command which does exactly that
(squashes commits on feature-branch to a single commit on target branch
at merge).
->**Note:**
-Never modify the commit history of `master` or shared branch
+NOTE: **Note:**
+Never modify the commit history of `master` or shared branch.
### How modifying history is done
@@ -436,7 +455,7 @@ pick <commit3-id> <commit3-commit-message>
# Note that empty commits are commented out
```
->**Note:**
+NOTE: **Note:**
It is important to notice that comment from the output clearly states that, if
you decide to abort, then do not just close your editor (as that will in-fact
modify history), but remove all uncommented lines and save.
@@ -470,7 +489,7 @@ tools that can use some of Git specifics to enable faster execution of common
tasks (which is exactly what removing sensitive information file is about).
An alternative is [BFG Repo-cleaner][bfg-repo-cleaner]. Keep in mind that these
tools are faster because they do not provide a same fully feature set as `git filter-branch`
-does, but focus on specific usecases.
+does, but focus on specific use cases.
## Conclusion
@@ -480,6 +499,18 @@ depending on the stage of your process. Git also enables rewriting history, but
should be avoided as it might cause problems when multiple developers are
contributing to the same codebase.
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
<!-- Identifiers, in alphabetical order -->
[bfg-repo-cleaner]: https://rtyley.github.io/bfg-repo-cleaner/
diff --git a/doc/topics/git/troubleshooting_git.md b/doc/topics/git/troubleshooting_git.md
index 71651fcf421..417d91bf834 100644
--- a/doc/topics/git/troubleshooting_git.md
+++ b/doc/topics/git/troubleshooting_git.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Troubleshooting Git
Sometimes things don't work the way they should or as you might expect when
@@ -9,7 +13,7 @@ with Git.
'Broken pipe' errors can occur when attempting to push to a remote repository.
When pushing you will usually see:
-```
+```text
Write failed: Broken pipe
fatal: The remote end hung up unexpectedly
```
@@ -39,14 +43,15 @@ There's another option where you can prevent session timeouts by configuring
SSH 'keep alive' either on the client or on the server (if you are a GitLab
admin and have access to the server).
-NOTE: **Note:** configuring *both* the client and the server is unnecessary.
+NOTE: **Note:**
+Configuring *both* the client and the server is unnecessary.
**To configure SSH on the client side**:
-- On UNIX, edit `~/.ssh/config` (create the file if it doesn’t exist) and
- add or edit:
+- On UNIX, edit `~/.ssh/config` (create the file if it doesn’t exist) and
+ add or edit:
- ```
+ ```text
Host your-gitlab-instance-url.com
ServerAliveInterval 60
ServerAliveCountMax 5
@@ -58,7 +63,7 @@ NOTE: **Note:** configuring *both* the client and the server is unnecessary.
**To configure SSH on the server side**, edit `/etc/ssh/sshd_config` and add:
-```
+```text
ClientAliveInterval 60
ClientAliveCountMax 5
```
@@ -83,35 +88,35 @@ to >= 2.9 (see [Broken pipe when pushing to Git repository][Broken-Pipe]).
Users may experience the following error when attempting to push or pull
using Git over SSH:
-```
-Please make sure you have the correct access rights
-and the repository exists.
+```text
+Please make sure you have the correct access rights
+and the repository exists.
...
-ssh_exchange_identification: read: Connection reset by peer
-fatal: Could not read from remote repository.
+ssh_exchange_identification: read: Connection reset by peer
+fatal: Could not read from remote repository.
```
This error usually indicates that SSH daemon's `MaxStartups` value is throttling
-SSH connections. This setting specifies the maximum number of unauthenticated
+SSH connections. This setting specifies the maximum number of unauthenticated
connections to the SSH daemon. This affects users with proper authentication
credentials (SSH keys) because every connection is 'unauthenticated' in the
-beginning. The default value is `10`.
+beginning. The default value is `10`.
Increase `MaxStartups` by adding or modifying the value in `/etc/ssh/sshd_config`:
-```
+```text
MaxStartups 100
```
-Restart SSHD for the change to take effect.
+Restart SSHD for the change to take effect.
## Timeout during git push/pull
If pulling/pushing from/to your repository ends up taking more than 50 seconds,
-a timeout will be issued with a log of the number of operations performed
+a timeout will be issued with a log of the number of operations performed
and their respective timings, like the example below:
-```
+```text
remote: Running checks for branch: master
remote: Scanning for LFS objects... (153ms)
remote: Calculating new repository size... (cancelled after 729ms)
diff --git a/doc/university/README.md b/doc/university/README.md
index cf13246067f..c116e54ad48 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -1,5 +1,6 @@
---
comments: false
+type: index
---
# GitLab University
@@ -8,26 +9,22 @@ GitLab University is a great place to start when learning about version control
If you're looking for a GitLab subscription for _your university_, see our [Education](https://about.gitlab.com/solutions/education/) page.
-## GitLab University Curriculum
+The GitLab University curriculum is composed of GitLab videos, screencasts, presentations, projects and external GitLab content hosted on other services and has been organized into the following sections:
-The curriculum is composed of GitLab videos, screencasts, presentations, projects and external GitLab content hosted on other services and has been organized into the following sections.
+1. [GitLab Beginner](#1-gitlab-beginner).
+1. [GitLab Intermediate](#2-gitlab-intermediate).
+1. [GitLab Advanced](#3-gitlab-advanced).
+1. [External Articles](#4-external-articles).
+1. [Resources for GitLab Team Members](#5-resources-for-gitlab-team-members).
-1. [GitLab Beginner](#1-gitlab-beginner)
-1. [GitLab Intermediate](#2-gitlab-intermediate)
-1. [GitLab Advanced](#3-gitlab-advanced)
-1. [External Articles](#4-external-articles)
-1. [Resources for GitLab Team Members](#5-resources-for-gitlab-team-members)
+## 1. GitLab Beginner
----
-
-### 1. GitLab Beginner
-
-#### 1.1. Version Control and Git
+### 1.1. Version Control and Git
1. [Version Control Systems](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit#slide=id.g72f2e4906_2_29)
1. [Code School: An Introduction to Git](https://www.codeschool.com/account/courses/try-git)
-#### 1.2. GitLab Basics
+### 1.2. GitLab Basics
1. [An Overview of GitLab.com - Video](https://www.youtube.com/watch?v=WaiL5DGEMR4)
1. [Why Use Git and GitLab - Slides](https://docs.google.com/a/gitlab.com/presentation/d/1RcZhFmn5VPvoFu6UMxhMOy7lAsToeBZRjLRn0LIdaNc/edit?usp=drive_web)
@@ -36,12 +33,12 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [Git and GitLab Basics - Online Course](https://courses.platzi.com/classes/git-gitlab/concepto/part-1/part-23370/material/)
1. [Comparison of GitLab Versions](https://about.gitlab.com/features/#compare)
-#### 1.3. Your GitLab Account
+### 1.3. Your GitLab Account
1. [Create a GitLab Account - Online Course](https://courses.platzi.com/classes/git-gitlab/concepto/first-steps/create-an-account-on-gitlab/material/)
1. [Create and Add your SSH key to GitLab - Video](https://www.youtube.com/watch?v=54mxyLo3Mqk)
-#### 1.4. GitLab Projects
+### 1.4. GitLab Projects
1. [Repositories, Projects and Groups - Video](https://www.youtube.com/watch?v=4TWfh1aKHHw&index=1&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e)
1. [Creating a Project in GitLab - Video](https://www.youtube.com/watch?v=7p0hrpNaJ14)
@@ -49,14 +46,14 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [GitLab Todos](https://about.gitlab.com/2016/03/02/gitlab-todos-feature-highlight/)
1. [GitLab's Work in Progress (WIP) Flag](https://about.gitlab.com/2016/01/08/feature-highlight-wip/)
-#### 1.5. Migrating from other Source Control
+### 1.5. Migrating from other Source Control
-1. [Migrating from BitBucket/Stash](https://docs.gitlab.com/ee/user/project/import/bitbucket.html)
-1. [Migrating from GitHub](https://docs.gitlab.com/ee/user/project/import/github.html)
-1. [Migrating from SVN](https://docs.gitlab.com/ee/user/project/import/svn.html)
-1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/user/project/import/fogbugz.html)
+1. [Migrating from BitBucket/Stash](../user/project/import/bitbucket.md)
+1. [Migrating from GitHub](../user/project/import/github.md)
+1. [Migrating from SVN](../user/project/import/svn.md)
+1. [Migrating from Fogbugz](../user/project/import/fogbugz.md)
-#### 1.6. GitLab Inc.
+### 1.6. GitLab Inc.
1. [About GitLab](https://about.gitlab.com/about/)
1. [GitLab Direction](https://about.gitlab.com/direction/)
@@ -67,7 +64,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [The GitLab Book Club](bookclub/index.md)
1. [GitLab Resources](https://about.gitlab.com/resources/)
-#### 1.7 Community and Support
+### 1.7 Community and Support
1. [Getting Help](https://about.gitlab.com/getting-help/)
- Proposing Features and Reporting and Tracking bugs for GitLab
@@ -79,22 +76,19 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [GitLab Training Workshops](https://docs.gitlab.com/ce/university/training/end-user/)
1. [GitLab Professional Services](https://about.gitlab.com/services/)
-#### 1.8 GitLab Training Material
+### 1.8 GitLab Training Material
-1. [Git and GitLab Terminology](glossary/README.md)
1. [Git and GitLab Workshop - Slides](https://docs.google.com/presentation/d/1JzTYD8ij9slejV2-TO-NzjCvlvj6mVn9BORePXNJoMI/edit?usp=drive_web)
----
-
-### 2. GitLab Intermediate
+## 2. GitLab Intermediate
-#### 2.1 GitLab Pages
+### 2.1 GitLab Pages
1. [Using any Static Site Generator with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
1. [Securing GitLab Pages with SSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/)
-1. [GitLab Pages Documentation](https://docs.gitlab.com/ce/user/project/pages/)
+1. [GitLab Pages Documentation](../user/project/pages/index.md)
-#### 2.2. GitLab Issues
+### 2.2. GitLab Issues
1. [Markdown in GitLab](../user/markdown.md)
1. [Issues and Merge Requests - Video](https://www.youtube.com/watch?v=raXvuwet78M)
@@ -106,7 +100,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [Designing GitLab Issue Board](https://about.gitlab.com/2016/08/31/designing-issue-boards/)
1. [From Idea to Production with GitLab - Video](https://www.youtube.com/watch?v=25pHyknRgEo&index=14&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e)
-#### 2.3. Continuous Integration
+### 2.3. Continuous Integration
1. [Operating Systems, Servers, VMs, Containers and Unix - Video](https://www.youtube.com/watch?v=V61kL6IC-zY&index=8&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e)
1. [GitLab CI - Product Page](https://about.gitlab.com/gitlab-ci/)
@@ -125,15 +119,15 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [TechBeacon: Doing continuous delivery? Focus first on reducing release cycle times](https://techbeacon.com/doing-continuous-delivery-focus-first-reducing-release-cycle-times)
1. See **[Integrations](#39-integrations)** for integrations with other CI services.
-#### 2.4. Workflow
+### 2.4. Workflow
1. [GitLab Flow - Video](https://youtu.be/enMumwvLAug?list=PLFGfElNsQthZnwMUFi6rqkyUZkI00OxIV)
1. [GitLab Flow vs Forking in GitLab - Video](https://www.youtube.com/watch?v=UGotqAUACZA)
1. [GitLab Flow Overview](https://about.gitlab.com/2014/09/29/gitlab-flow/)
1. [Always Start with an Issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/)
-1. [GitLab Flow Documentation](https://docs.gitlab.com/ee/workflow/gitlab_flow.html)
+1. [GitLab Flow Documentation](../workflow/gitlab_flow.md)
-#### 2.5. GitLab Comparisons
+### 2.5. GitLab Comparisons
1. [GitLab Compared to Other Tools](https://about.gitlab.com/comparison/)
1. [Comparing GitLab Terminology](https://about.gitlab.com/2016/01/27/comparing-terms-gitlab-github-bitbucket/)
@@ -141,17 +135,15 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [GitLab Position FAQ](https://about.gitlab.com/handbook/positioning-faq)
1. [Customer review of GitLab with points on why they prefer GitLab](https://www.enovate.co.uk/web-design-blog/2015/11/25/gitlab-review/)
----
+## 3. GitLab Advanced
-### 3. GitLab Advanced
-
-#### 3.1. Dev Ops
+### 3.1. Dev Ops
1. [Xebia Labs: Dev Ops Terminology](https://xebialabs.com/glossary/)
1. [Xebia Labs: Periodic Table of DevOps Tools](https://xebialabs.com/periodic-table-of-devops-tools/)
1. [Puppet Labs: State of Dev Ops 2016 - Book](https://puppet.com/resources/white-paper/2016-state-of-devops-report)
-#### 3.2. Installing GitLab with Omnibus
+### 3.2. Installing GitLab with Omnibus
1. [What is Omnibus - Video](https://www.youtube.com/watch?v=XTmpKudd-Oo)
1. [How to Install GitLab with Omnibus - Video](https://www.youtube.com/watch?v=Q69YaOjqNhg)
@@ -161,62 +153,58 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
1. [Installing GitLab on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/)
1. [Installing GitLab on Digital Ocean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/)
-#### 3.3. Permissions
+### 3.3. Permissions
1. [How to Manage Permissions in GitLab EE - Video](https://www.youtube.com/watch?v=DjUoIrkiNuM)
-#### 3.4. Large Files
+### 3.4. Large Files
1. [Big files in Git (Git LFS) - Video](https://www.youtube.com/watch?v=DawznUxYDe4)
-#### 3.5. LDAP and Active Directory
+### 3.5. LDAP and Active Directory
1. [How to Manage LDAP, Active Directory in GitLab - Video](https://www.youtube.com/watch?v=HPMjM-14qa8)
-#### 3.6 Custom Languages
+### 3.6 Custom Languages
1. [How to add Syntax Highlighting Support for Custom Languages to GitLab - Video](https://youtu.be/6WxTMqatrrA)
-#### 3.7. Scalability and High Availability
+### 3.7. Scalability and High Availability
1. [Scalability and High Availability - Video](https://www.youtube.com/watch?v=cXRMJJb6sp4&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=2)
1. [High Availability - Video](https://www.youtube.com/watch?v=36KS808u6bE&index=15&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e)
1. [High Availability Documentation](https://about.gitlab.com/high-availability/)
-#### 3.8 Cycle Analytics
+### 3.8 Cycle Analytics
1. [GitLab Cycle Analytics Overview](https://about.gitlab.com/2016/09/21/cycle-analytics-feature-highlight/)
1. [GitLab Cycle Analytics - Product Page](https://about.gitlab.com/product/cycle-analytics/)
-#### 3.9. Integrations
+### 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 with GitLab](https://docs.gitlab.com/ce/user/project/integrations/jira.html)
-1. [How to Integrate Jenkins with GitLab](https://docs.gitlab.com/ee/integration/jenkins.html)
-1. [How to Integrate Bamboo with GitLab](https://docs.gitlab.com/ce/user/project/integrations/bamboo.html)
-1. [How to Integrate Slack with GitLab](https://docs.gitlab.com/ce/user/project/integrations/slack.html)
+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)
+1. [How to Integrate Slack with GitLab](../user/project/integrations/slack.md)
1. [How to Integrate Convox with GitLab](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/)
1. [Getting Started with GitLab and Shippable CI](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/)
----
-
-### 4. External Articles
+## 4. External Articles
1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460)
1. [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/)
1. [2015 Venture Beat article - Actually, Open Source is Eating the World](http://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/)
----
-
-### 5. Resources for GitLab Team Members
+## 5. Resources for GitLab Team Members
NOTE: **Note:**
-Some content can only be accessed by GitLab team members
+Some content can only be accessed by GitLab team members.
1. [Support Path](support/README.md)
-1. [Sales Path (redirect to sales handbook)](https://about.gitlab.com/handbook/sales-onboarding/)
+1. [Sales Path](https://about.gitlab.com/handbook/sales-onboarding/)
1. [User Training](training/user_training.md)
1. [GitLab Flow Training](training/gitlab_flow.md)
-1. [Training Topics](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/university/training/topics/)
-1. [GitLab architecture for noobs](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/development/architecture.md)
+1. [Training Topics](training/index.md)
+1. [GitLab architecture](../development/architecture.md)
1. [Client Assessment of GitLab versus GitHub](https://docs.google.com/a/gitlab.com/spreadsheets/d/18cRF9Y5I6I7Z_ab6qhBEW55YpEMyU4PitZYjomVHM-M/edit?usp=sharing)
diff --git a/doc/university/bookclub/booklist.md b/doc/university/bookclub/booklist.md
index d5662be6fa6..33298e45393 100644
--- a/doc/university/bookclub/booklist.md
+++ b/doc/university/bookclub/booklist.md
@@ -1,117 +1,118 @@
---
comments: false
+type: index
---
# Books
-List of books and resources, that may be worth reading.
+List of books and resources that may be worth reading.
## Papers
-1. **The Humble Programmer**
+1. **The Humble Programmer**
- Edsger W. Dijkstra, 1972 ([paper](https://dl.acm.org/citation.cfm?id=361591))
+ Edsger W. Dijkstra, 1972 ([paper](https://dl.acm.org/citation.cfm?id=361591))
## Programming
-1. **Design Patterns: Elements of Reusable Object-Oriented Software**
+1. **Design Patterns: Elements of Reusable Object-Oriented Software**
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, 1994 ([amazon](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612))
+ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, 1994 ([amazon](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612))
-1. **Clean Code: A Handbook of Agile Software Craftsmanship**
+1. **Clean Code: A Handbook of Agile Software Craftsmanship**
- Robert C. "Uncle Bob" Martin, 2008 ([amazon](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882))
+ Robert C. "Uncle Bob" Martin, 2008 ([amazon](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882))
-1. **Code Complete: A Practical Handbook of Software Construction**, 2nd Edition
+1. **Code Complete: A Practical Handbook of Software Construction**, 2nd Edition
- Steve McConnell, 2004 ([amazon](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670))
+ Steve McConnell, 2004 ([amazon](https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670))
-1. **The Pragmatic Programmer: From Journeyman to Master**
+1. **The Pragmatic Programmer: From Journeyman to Master**
- Andrew Hunt, David Thomas, 1999 ([amazon](https://www.amazon.com/Pragmatic-Programmer-Journeyman-Master/dp/020161622X))
+ Andrew Hunt, David Thomas, 1999 ([amazon](https://www.amazon.com/Pragmatic-Programmer-Journeyman-Master/dp/020161622X))
-1. **Working Effectively with Legacy Code**
+1. **Working Effectively with Legacy Code**
- Michael Feathers, 2004 ([amazon](https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052))
+ Michael Feathers, 2004 ([amazon](https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052))
-1. **Eloquent Ruby**
+1. **Eloquent Ruby**
- Russ Olsen, 2011 ([amazon](https://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104))
+ Russ Olsen, 2011 ([amazon](https://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104))
-1. **Domain-Driven Design: Tackling Complexity in the Heart of Software**
+1. **Domain-Driven Design: Tackling Complexity in the Heart of Software**
- Eric Evans, 2003 ([amazon](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215))
+ Eric Evans, 2003 ([amazon](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215))
-1. **How to Solve It: A New Aspect of Mathematical Method**
+1. **How to Solve It: A New Aspect of Mathematical Method**
- Polya G. 1957 ([amazon](https://www.amazon.com/How-Solve-Mathematical-Princeton-Science/dp/069116407X))
+ Polya G. 1957 ([amazon](https://www.amazon.com/How-Solve-Mathematical-Princeton-Science/dp/069116407X))
-1. **Software Creativity 2.0**
+1. **Software Creativity 2.0**
- Robert L. Glass, 2006 ([amazon](https://www.amazon.com/Software-Creativity-2-0-Robert-Glass/dp/0977213315))
+ Robert L. Glass, 2006 ([amazon](https://www.amazon.com/Software-Creativity-2-0-Robert-Glass/dp/0977213315))
-1. **Object-Oriented Software Construction**
+1. **Object-Oriented Software Construction**
- Bertrand Meyer, 1997 ([amazon](https://www.amazon.com/Object-Oriented-Software-Construction-Book-CD-ROM/dp/0136291554))
+ Bertrand Meyer, 1997 ([amazon](https://www.amazon.com/Object-Oriented-Software-Construction-Book-CD-ROM/dp/0136291554))
-1. **Refactoring: Improving the Design of Existing Code**
+1. **Refactoring: Improving the Design of Existing Code**
- Martin Fowler, Kent Beck, 1999 ([amazon](https://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672))
+ Martin Fowler, Kent Beck, 1999 ([amazon](https://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672))
-1. **Test Driven Development: By Example**
+1. **Test Driven Development: By Example**
- Kent Beck, 2002 ([amazon](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530))
+ Kent Beck, 2002 ([amazon](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530))
-1. **Algorithms in C++: Fundamentals, Data Structure, Sorting, Searching**
+1. **Algorithms in C++: Fundamentals, Data Structure, Sorting, Searching**
- Robert Sedgewick, 1990 ([amazon](https://www.amazon.com/Algorithms-Parts-1-4-Fundamentals-Structure/dp/0201350882))
+ Robert Sedgewick, 1990 ([amazon](https://www.amazon.com/Algorithms-Parts-1-4-Fundamentals-Structure/dp/0201350882))
-1. **Effective C++**
+1. **Effective C++**
- Scott Mayers, 1996 ([amazon](https://www.amazon.com/Effective-Specific-Improve-Programs-Designs/dp/0321334876))
+ Scott Mayers, 1996 ([amazon](https://www.amazon.com/Effective-Specific-Improve-Programs-Designs/dp/0321334876))
-1. **Extreme Programming Explained: Embrace Change**
+1. **Extreme Programming Explained: Embrace Change**
- Kent Beck, 1999 ([amazon](https://www.amazon.com/Extreme-Programming-Explained-Embrace-Change/dp/0321278658))
+ Kent Beck, 1999 ([amazon](https://www.amazon.com/Extreme-Programming-Explained-Embrace-Change/dp/0321278658))
-1. **The Art of Computer Programming**
+1. **The Art of Computer Programming**
- Donald E. Knuth, 1997 ([amazon](https://www.amazon.com/Computer-Programming-Volumes-1-4A-Boxed/dp/0321751043))
+ Donald E. Knuth, 1997 ([amazon](https://www.amazon.com/Computer-Programming-Volumes-1-4A-Boxed/dp/0321751043))
-1. **Writing Efficient Programs**
+1. **Writing Efficient Programs**
- Jon Louis Bentley, 1982 ([amazon](https://www.amazon.com/Writing-Efficient-Programs-Prentice-Hall-Software/dp/013970244X))
+ Jon Louis Bentley, 1982 ([amazon](https://www.amazon.com/Writing-Efficient-Programs-Prentice-Hall-Software/dp/013970244X))
-1. **The Mythical Man-Month: Essays on Software Engineering**
+1. **The Mythical Man-Month: Essays on Software Engineering**
- Frederick Phillips Brooks, 1975 ([amazon](https://www.amazon.com/Mythical-Man-Month-Essays-Software-Engineering/dp/0201006502))
+ Frederick Phillips Brooks, 1975 ([amazon](https://www.amazon.com/Mythical-Man-Month-Essays-Software-Engineering/dp/0201006502))
-1. **Peopleware: Productive Projects and Teams** 3rd Edition
+1. **Peopleware: Productive Projects and Teams** 3rd Edition
- Tom DeMarco, Tim Lister, 2013 ([amazon](https://www.amazon.com/Peopleware-Productive-Projects-Teams-3rd/dp/0321934113))
+ Tom DeMarco, Tim Lister, 2013 ([amazon](https://www.amazon.com/Peopleware-Productive-Projects-Teams-3rd/dp/0321934113))
-1. **Principles Of Software Engineering Management**
+1. **Principles Of Software Engineering Management**
- Tom Gilb, 1988 ([amazon](https://www.amazon.com/Principles-Software-Engineering-Management-Gilb/dp/0201192462))
+ Tom Gilb, 1988 ([amazon](https://www.amazon.com/Principles-Software-Engineering-Management-Gilb/dp/0201192462))
## Other
-1. **Thinking, Fast and Slow**
+1. **Thinking, Fast and Slow**
- Daniel Kahneman, 2013 ([amazon](https://www.amazon.com/Thinking-Fast-Slow-Daniel-Kahneman/dp/0374533555))
+ Daniel Kahneman, 2013 ([amazon](https://www.amazon.com/Thinking-Fast-Slow-Daniel-Kahneman/dp/0374533555))
-1. **The Social Animal** 11th Edition
+1. **The Social Animal** 11th Edition
- Elliot Aronson, 2011 ([amazon](https://www.amazon.com/Social-Animal-Elliot-Aronson/dp/1429233419))
+ Elliot Aronson, 2011 ([amazon](https://www.amazon.com/Social-Animal-Elliot-Aronson/dp/1429233419))
-1. **Influence: Science and Practice** 5th Edition
+1. **Influence: Science and Practice** 5th Edition
- Robert B. Cialdini, 2008 ([amazon](https://www.amazon.com/Influence-Practice-Robert-B-Cialdini/dp/0205609996))
+ Robert B. Cialdini, 2008 ([amazon](https://www.amazon.com/Influence-Practice-Robert-B-Cialdini/dp/0205609996))
-1. **Getting to Yes: Negotiating Agreement Without Giving In**
+1. **Getting to Yes: Negotiating Agreement Without Giving In**
- Roger Fisher, William L. Ury, Bruce Patton, 2011 ([amazon](https://www.amazon.com/Getting-Yes-Negotiating-Agreement-Without/dp/0143118757))
+ Roger Fisher, William L. Ury, Bruce Patton, 2011 ([amazon](https://www.amazon.com/Getting-Yes-Negotiating-Agreement-Without/dp/0143118757))
-1. **How to Win Friends & Influence People**
+1. **How to Win Friends & Influence People**
- Dale Carnegie, 1981 ([amazon](https://www.amazon.com/How-Win-Friends-Influence-People/dp/0671027034))
+ Dale Carnegie, 1981 ([amazon](https://www.amazon.com/How-Win-Friends-Influence-People/dp/0671027034))
diff --git a/doc/university/bookclub/index.md b/doc/university/bookclub/index.md
index 63238685b2b..330078e979f 100644
--- a/doc/university/bookclub/index.md
+++ b/doc/university/bookclub/index.md
@@ -1,5 +1,6 @@
---
comments: false
+type: index
---
# The GitLab Book Club
@@ -11,13 +12,13 @@ See the [book list](booklist.md) for additional recommendations.
## Currently reading : Books about remote work
-1. **Remote: Office not required**
+1. **Remote: Office not required**
- David Heinemeier Hansson and Jason Fried, 2013
- ([amazon](http://www.amazon.co.uk/Remote-Required-David-Heinemeier-Hansson/dp/0091954673))
+ David Heinemeier Hansson and Jason Fried, 2013
+ ([amazon](http://www.amazon.co.uk/Remote-Required-David-Heinemeier-Hansson/dp/0091954673))
-1. **The Year Without Pants**
+1. **The Year Without Pants**
- Scott Berkun, 2013 ([ScottBerkun.com](http://scottberkun.com/yearwithoutpants/))
+ Scott Berkun, 2013 ([ScottBerkun.com](http://scottberkun.com/yearwithoutpants/))
Any other books you'd like to suggest? Edit this page and add them to the queue.
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 0af2f8d2f54..21e2da3e47c 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -2,707 +2,10 @@
comments: false
---
-# What is the Glossary
+# Glossary
-This contains a simplified list and definitions of some of the terms that you will encounter in your day to day activities when working with GitLab.
-Please add any terms that you discover that you think would be useful for others.
+This page has been removed after an effort to ensure that all applicable GitLab-specific
+terms are available in context on the relevant [GitLab Documentation](https://docs.gitlab.com/)
+or [about.gitlab.com](https://about.gitlab.com/) pages.
-### 2FA
-
-User authentication by combination of 2 different steps during login. This allows for [more security](https://about.gitlab.com/handbook/security/).
-
-### Access Levels
-
-Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions. See [GitLab's Permission Guidelines](../../user/permissions.md)
-
-### Active Directory (AD)
-
-A Microsoft-based [directory service](https://msdn.microsoft.com/en-us/library/bb742424.aspx) for windows domain networks. It uses LDAP technology under the hood.
-
-### Agile
-
-Building and [delivering software](http://agilemethodology.org/) in phases/parts rather than trying to build everything at once then delivering to the user/client. The latter is known as the WaterFall model.
-
-### Amazon RDS
-
-External reference: <http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html>
-
-### Application Lifecycle Management (ALM)
-
-The entire product lifecycle management process for an application, from requirements management, development, and testing until deployment. GitLab has [advantages](https://docs.google.com/presentation/d/1vCU-NbZWz8NTNK8Vu3y4zGMAHb5DpC8PE5mHtw1PWfI/edit#slide=id.g72f2e4906_2_288) over both legacy and modern ALM tools.
-
-### Artifactory
-
-A version control [system](https://www.jfrog.com/open-source/#os-arti) for non-text files.
-
-### Artifacts
-
-Objects (usually binary and large) created by a build process. These can include use cases, class diagrams, requirements and design documents.
-
-### Atlassian
-
-A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Hipchat, Confluence, Bamboo.
-
-### Audit Log
-
-Also called an [audit trail](https://en.wikipedia.org/wiki/Audit_trail), an audit log is a document that records an event in an IT system.
-
-### Auto Defined User Group
-
-User groups are a way of centralizing control over important management tasks, particularly access control and password policies. A simple example of such groups are the users and the admins groups.
-In most of the cases these groups are auto defined in terms of access, rules of usage, conditions to be part of, etc.
-
-### Bamboo
-
-Atlassian's CI tool similar to GitLab CI and Jenkins.
-
-### Basic Subscription
-
-Entry level [subscription](https://about.gitlab.com/pricing/) for GitLab EE currently available in packs of 10.
-
-### Bitbucket
-
-Atlassian's web hosting service for Git and Mercurial Projects. Read about [migrating](https://docs.gitlab.com/ce/workflow/importing/import_projects_from_bitbucket.html) from BitBucket to a GitLab instance.
-
-### Branch
-
-A branch is a parallel version of a repository. This allows you to work on the repository without affecting the "master" branch, and without affecting the current "live" version. When you have made all your changes to your branch you can then merge to the master. When your merge request is accepted your changes will be "live."
-
-### Branded Login
-
-Having your own logo on [your GitLab instance login page](https://docs.gitlab.com/ee/customization/branded_login_page.html) instead of the GitLab logo.
-
-### Job triggers (Build Triggers)
-These protect your code base against breaks, for instance when a team is working on the same project. Learn about [setting up](https://docs.gitlab.com/ce/ci/triggers/README.html) job triggers.
-
-### CEPH
-
- A distributed object store and file [system](http://ceph.com/) designed to provide excellent performance, reliability and scalability.
-
-### ChatOps
-
-The ability to [initiate an action](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/1412) from chat. ChatBots run in your chat application and give you the ability to do "anything" from chat.
-
-### Clone
-
-A [copy](https://git-scm.com/docs/git-clone) of a repository stored on your machine that allows you to use your own editor without being online, but still tracks the changes made remotely.
-
-### Code Review
-
-Examination of a program's code. The main aim is to maintain high quality standards of code that is being shipped. Merge requests [serve as a code review tool](https://about.gitlab.com/2014/09/29/gitlab-flow/) in GitLab.
-
-### Code Snippet
-
-A small amount of code, usually selected for the purpose of showing other developers how to do something specific or reproduce a problem.
-
-### Collaborator
-
-Person with read and write access to a repository who has been invited by repository owner.
-
-### Commit
-
-A [change](https://git-scm.com/docs/git-commit) (revision) to a file that also creates an ID, allowing you to see revision history and the author of the changes.
-
-### Community
-
-[Everyone](https://about.gitlab.com/community/) who uses GitLab.
-
-### Confluence
-
-Atlassian's product for collaboration on documents and projects.
-
-### Continuous Delivery
-
-A [software engineering approach](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) in which continuous integration, automated testing, and automated deployment capabilities allow software to be developed and deployed rapidly, reliably and repeatedly with minimal human intervention. Still, the deployment to production is defined strategically and triggered manually. [Amazon moves toward continuous delivery](https://www.youtube.com/watch?v=esEFaY0FDKc)
-
-### Continuous Deployment
-
-A [software development practice](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) in which every code change goes through the entire pipeline and is put into production automatically, resulting in many production deployments every day. It does everything that Continuous Delivery does, but the process is fully automated, there's no human intervention at all. [The difference between Continuous Delivery and Continuous Integration.](https://www.youtube.com/watch?v=igwFj8PPSnw)
-
-### Continuous Integration
-
-A [software development practice](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) in which you build and test software every time a developer pushes code to the application, and it happens several times a day. [Thoughtworks discusses continuous integration.](https://www.thoughtworks.com/continuous-integration)
-
-### Contributor
-
-Term used for a person contributing to an open source project.
-
-### Conversational Development (ConvDev)
-
-A [natural evolution](https://about.gitlab.com/2016/09/14/gitlab-live-event-recap/) of software development that carries a conversation across functional groups throughout the development process, enabling developers to track the full path of development in a cohesive and intuitive way. ConvDev accelerates the development lifecycle by fostering collaboration and knowledge sharing from idea to production.
-
-### Cycle Analytics
-
-See <https://gitlab.com/gitlab-org/gitlab-ce/issues/22458>
-
-### Cycle Time
-
-The time it takes to move from [idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab).
-
-### Data Centre
-
-Atlassian product for High Availability.
-
-### Dependencies
-
-As in "specify [dependencies](https://gitlab.com/gitlab-org/gitlab-ce/issues/14728) between stages."
-
-### Deploy Keys
-
-A [SSH key](https://docs.gitlab.com/ce/gitlab-basics/create-your-ssh-keys.html)stored on your server that grants access to a single GitLab repository. This is used by a GitLab runner to clone a project's code so that tests can be run against the checked out code.
-
-### Developer
-
-For us at GitLab, this means a software developer, or someone who makes software. It is also one of the levels of access in our multi-level approval system.
-
-### DevOps
-
-The intersection of software engineering, quality assurance, and technology operations. Explore more DevOps topics in the [glossary by XebiaLabs](https://xebialabs.com/glossary/)
-
-### Diff
-
-The difference between two commits, or saved changes. This will also be shown visually after the changes.
-
-### Directory
-
-A folder used for storing multiple files.
-
-### Docker Container Registry
-
-A [feature](https://docs.gitlab.com/ce/user/project/container_registry.html) of [GitLab projects](https://about.gitlab.com/2016/05/23/gitlab-container-registry/). Containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in.
-
-### Dynamic Environment (review apps)
-
-### EC2 Instance
-
-### Elasticsearch
-
-Elasticsearch is a flexible, scalable and powerful search service. When [enabled](https://gitlab.com/help/integration/elasticsearch.md), it helps keep GitLab's search fast when dealing with a huge amount of data.
-
-### Emacs
-
-External reference: <https://www.masteringemacs.org/article/mastering-key-bindings-emacs>
-
-### First Byte
-
-External reference: <https://en.wikipedia.org/wiki/Time_To_First_Byte>
-
-First Byte (sometimes referred to as time to first byte or [TTFB](https://en.wikipedia.org/wiki/Time_To_First_Byte)) measures the time between making a request and receiving the first byte of information in return. As a result, First Byte encompasses everything that is the backend as well as network transit issues. It differs from [_Speed Index_](#speed-index) mostly by frontend related issues which are included in Speed Index such as javascript loading, page rendering, and so on.
-
-### Fork
-
-Your [own copy](https://docs.gitlab.com/ce/workflow/forking_workflow.html) of a repository that allows you to make changes to the repository without affecting the original.
-
-### Funnel, or: TOFU, MOFU, BOFU
-
-External reference: [Blog post](https://www.weidert.com/whole_brain_marketing_blog/bid/113688/ToFu-MoFu-BoFu-Serving-Up-The-Right-Content-for-Lead-Nurturing)
-
-TOFU: top of funnel
-MOFU: middle of funnel
-BOFU: bottom of funnel
-
-### Gerrit
-
-A code review [tool](https://www.gerritcodereview.com/) built on top of Git.
-
-### Git Attributes
-
-A [git attributes file](https://git-scm.com/docs/gitattributes) is a simple text file that gives attributes to pathnames.
-
-### Git Hooks
-
-[Scripts](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) you can use to trigger actions at certain points.
-
-Difference between a [webhook](#webhooks) and a git hook: a git hook is local to its repo (usually) while a webhook is not (it can make API or http calls). So for example if you want your linter to fire before you commit, you can set that up with a git hook. If the linter fails, the commit does not go through. A git hook _can_ be configured to go beyond its repo, e.g. by having it make an API call.
-
-### GitHost.io
-
-A single-tenant solution that provides GitLab CE or EE as a managed service. GitLab Inc. is responsible for installing, updating, hosting, and backing up customers' own private and secure GitLab instance.
-
-### GitHub
-
-A web-based Git repository hosting service with an enterprise offering. Its main features are: issue tracking, pull request with code review, abundancy of integrations and wiki. It offers free public repos, private repos and enterprise services are paid. Read about [importing a project](https://docs.gitlab.com/ce/workflow/importing/import_projects_from_github.html) from GitHub to GitLab.
-
-### GitLab CE
-
-Our free on Premise solution with >100,000 users
-
-### GitLab CI
-
-Our own Continuous Integration [feature](https://about.gitlab.com/gitlab-ci/) that is shipped with each instance
-
-### GitLab EE
-
-Our premium on premise [solution](https://about.gitlab.com/features/#enterprise) that currently has Basic, Standard and Plus subscription packages with additional features and support.
-
-### GitLab.com
-
-Our free SaaS for public and private repositories.
-
-### GitLab Geo
-
-Allows you to replicate your GitLab instance to other geographical locations as a read-only fully operational version. It [can be used](https://docs.gitlab.com/ee/administration/geo/replication/index.html) for cloning and fetching projects, in addition to reading any data. This will make working with large repositories over large distances much faster.
-
-### GitLab High Availability
-
-### GitLab Master Plan
-
-Related blog post: <https://about.gitlab.com/2016/09/13/gitlab-master-plan/>.
-
-### GitLab Pages
-
-These allow you to [create websites](https://gitlab.com/help/pages/README.md) for your GitLab projects, groups, or user account.
-
-### GitLab Runner
-
-Related project: <https://gitlab.com/gitlab-org/gitlab-runner>
-
-### Gitolite
-
-An [access layer](https://git-scm.com/book/en/v1/Git-on-the-Server-Gitolite) that sits on top of Git. Users are granted access to repos via a simple config file. As an admin, you only need the users' public SSH key and a username.
-
-### Gitorious
-
-A web-based hosting service for projects using Git. It was acquired by GitLab and we discontinued the service. Read the[Gitorious Acquisition Blog Post](https://about.gitlab.com/2015/03/03/gitlab-acquires-gitorious/).
-
-### Go
-
-An open source programming [language](https://golang.org/).
-
-### Gogs
-
-External reference: <https://gogs.io/>
-
-### GUI/ Git GUI
-
-A portable [graphical interface](https://git-scm.com/docs/git-gui) to Git that allows users to make changes to their repository by making new commits, amending existing ones, creating branches, performing local merges, and fetching/pushing to remote repositories.
-
-### High Availability for Disaster Recovery (HADR)
-
-Sometimes written HA/DR, this usually refers to a strategy for having a failover server in place in case the main server fails.
-
-### Hip Chat
-
-Atlassian's real time chat application for teams, Hip Chat is a competitor to Slack, RocketChat and MatterMost.
-
-### High Availability
-
-Refers to a [system or component](https://about.gitlab.com/high-availability/) that is continuously operational for a desirably long length of time. Availability can be measured relative to "100% operational" or "never failing."
-
-### Inner-sourcing
-
-The [use of](https://about.gitlab.com/2014/09/05/innersourcing-using-the-open-source-workflow-to-improve-collaboration-within-an-organization/) open source development techniques within the corporation.
-
-### Internet Relay Chat (IRC)
-
-An [application layer protocol](http://www.irchelp.org/) that facilitates communication in the form of text.
-
-### Issue Tracker
-
-A [tool](https://docs.gitlab.com/ee/integration/external-issue-tracker.html) used to manage, organize, and maintain a list of issues, making it easier for an organization to manage.
-
-### Jenkins
-
-An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins.io/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. Related [documentation](https://docs.gitlab.com/ee/integration/jenkins.html).
-
-### Jira
-
-Atlassian's [project management software](https://www.atlassian.com/software/jira), i.e. a complex issue tracker. GitLab [can be configured](https://docs.gitlab.com/ee/project_services/jira.html) to interact with JIRA Core either using an on-premise instance or the SaaS solution that Atlassian offers.
-
-### JUnit
-
-A testing framework for the Java programming language, [JUnit](http://junit.org/junit4/) has been important in the evolution of test-driven development.
-
-### Kerberos
-
-A network authentication [protocol](http://web.mit.edu/kerberos/) that uses secret-key cryptography for security.
-
-### Kubernetes
-
-An open source container cluster manager originally designed by Google. It's basically a platform for automating deployment, scaling, and operations of application containers over clusters of hosts.
-
-### Labels
-
-An [identifier](https://docs.gitlab.com/ce/user/project/labels.html) to describe a group of one or more specific file revisions.
-
-### Lightweight Directory Access Protocol (LDAP)
-
- A directory (electronic address book) with user information (e.g. name, phone_number etc.)
-
-### LDAP User Authentication
-
-GitLab [integrates](https://docs.gitlab.com/ce/administration/auth/ldap.html) with LDAP to support user authentication. This enables GitLab to sign in people from an LDAP server (i.e., allowing people whose names are on the electronic user directory server to be able to use their LDAP accounts to login.)
-
-### LDAP Group Sync
-
-Allows you to synchronize the members of a GitLab group with one or more LDAP groups.
-
-### Lint
-
-Static code analysis for our various file types. For example, we use [scss-lint](https://github.com/brigade/scss-lint) to ensure that a consistent code styling is respected. Similar tools: rubocop / eslint.
-
-### Load Balancer
-
-A [device](https://en.wikipedia.org/wiki/Load_balancing_(computing)) that distributes network or application traffic across multiple servers.
-
-### Git Large File Storage (LFS)
-
-A way [to enable](https://about.gitlab.com/2015/11/23/announcing-git-lfs-support-in-gitlab/) git to handle large binary files by using reference pointers within small text files to point to the large files. Large files such as high resolution images and videos, audio files, and assets can be called from a remote server.
-
-### Linux
-
-An operating system like Windows or OS X. It is mostly used by software developers and on servers.
-
-### Markdown
-
-A lightweight markup language with plain text formatting syntax designed so that it can be converted to HTML and many other formats using a tool by the same name. Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor. Checkout GitLab's [Markdown guide](https://gitlab.com/help/user/markdown.md).
-
-### Maria DB
-
-A community developed fork/variation of MySQL. MySQL is owned by Oracle.
-
-### Master
-
-Name of the [default branch](https://git-scm.com/book/en/v1/Git-Branching-What-a-Branch-Is) in every git repository.
-
-### Mattermost
-
-An open source, self-hosted messaging alternative to Slack. View GitLab's Mattermost [feature](https://gitlab.com/gitlab-org/gitlab-mattermost).
-
-### Mercurial
-
-A free distributed version control system similar to and a competitor with Git.
-
-### Merge
-
-Takes changes from one branch, and [applies them](https://git-scm.com/docs/git-merge) into another branch.
-
-### Merge Conflict
-
-[Arises](https://about.gitlab.com/2016/09/06/resolving-merge-conflicts-from-the-gitlab-ui/) when a merge can't be performed cleanly between two versions of the same file.
-
-#### Merge Request
-
-[Takes changes](https://docs.gitlab.com/ce/gitlab-basics/add-merge-request.html) from one branch, and applies them into another branch.
-
-### Meteor
-
-A [platform](https://www.meteor.com) for building javascript apps.
-
-### Milestones
-
-Allow you to [organize issues](../../user/project/milestones/index.md) and merge requests in GitLab into a cohesive group, optionally setting a due date. A common use is keeping track of an upcoming software version. Milestones are created per-project.
-
-### Mirror Repositories
-
-A project that is set up to automatically have its branches, tags, and commits [updated from an upstream repository](https://docs.gitlab.com/ee/workflow/repository_mirroring.html). This is useful when a repository you're interested in is located on a different server, and you want to be able to browse its content and activity using the familiar GitLab interface.
-
-### MIT License
-
-A type of software license. It lets people do anything with your code with proper attribution and without warranty. It is the most common license for open source applications written in Ruby on Rails. GitLab CE is issued under this [license](https://docs.gitlab.com/ce/development/licensing.html). This means you can download the code, modify it as you want, and even build a new commercial product using the underlying code and it's not illegal. The only condition is that there is no form of warranty provided by GitLab so whatever happens when you use the code is your own problem.
-
-### Mondo Rescue
-
-A free disaster recovery [software](https://help.ubuntu.com/community/MondoMindi).
-
-#### Mount
-
-External reference:
-
-As stated on the [wikipedia page](https://en.wikipedia.org/wiki/Mount_(Unix)), "Mounting makes file systems, files, directories, devices and special files available for use and available to the user."
-
-For example, we have NFS servers where the _git files_ reside. In order for a worker node to "see" or "use" the git files, the NFS server needs to be _mounted_ on the worker; that is, the worker needs to know that the NFS server exists and how to connect to it. Think of it as getting a shared drive to show up in your Finder (on Mac) or Explorer (on Windows).
-
-### MySQL
-
-A relational [database](http://www.mysql.com/) owned by Oracle. Currently only supported if you are using EE.
-
-### Namespace
-
-A set of symbols that are used to organize objects of various kinds so that these objects may be referred to by name. Examples of namespaces in action include file systems that assign names to files; programming languages that organize their variables and subroutines in namespaces; and computer networks and distributed systems that assign names to resources, such as computers, printers, websites, (remote) files, etc.
-
-### Nginx
-
-A web [server](https://www.nginx.com/resources/wiki/) (pronounced "engine x"). [It can act](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/nginx.md) as a reverse proxy server for HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer and an HTTP cache.
-
-### OAuth
-
-An open standard for authorization, commonly used as a way for internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password. GitLab [is](https://docs.gitlab.com/ce/integration/oauth_provider.html) an OAuth2 authentication service provider.
-
-### Omnibus Packages
-
-A way to [package different services and tools](https://docs.gitlab.com/omnibus/) required to run GitLab, so that most developers can install it without laborious configuration.
-
-### On Premise
-
-On your own server. In GitLab, this [refers](https://about.gitlab.com/2015/02/12/why-ship-on-premises-in-the-saas-era/) to the ability to download GitLab EE/GitLab CE and host it on your own server rather than using GitLab.com, which is hosted by GitLab Inc's servers.
-
-### Open Core
-
-GitLab's [business model](https://about.gitlab.com/2016/07/20/gitlab-is-open-core-github-is-closed-source/). Coined by Andrew Lampitt in 2008, the [open core model](https://en.wikipedia.org/wiki/Open_core) primarily involves offering a "core" or feature-limited version of a software product as free and open-source software, while offering "commercial" versions or add-ons as proprietary software.
-
-### Open Source Software
-
-Software for which the original source code is freely [available](https://opensource.org/docs/osd) and may be redistributed and modified. GitLab prioritizes open source [stewardship](https://about.gitlab.com/2016/01/11/being-a-good-open-source-steward/). Including to providing access to the source code, open source software must comply with a number of criteria, among them free distribution and no discrimination against persons, groups, or fields of endeavor.
-
-#### Open Source Stewardship
-
-[Related blog post](https://about.gitlab.com/2016/01/11/being-a-good-open-source-steward/).
-
-### Owner
-
-The most powerful person on a GitLab project. They have the permissions of all the other users plus the additional permission of being able to destroy (i.e. delete) the project.
-
-### Platform as a Service (PaaS)
-
-Typically referred to in regards to application development, PaaS is a model in which a cloud provider delivers hardware and software tools to its users as a service.
-
-### Perforce
-
-The company that produces Helix. A commercial, proprietary, centralised VCS well known for its ability to version files of any size and type. They OEM a re-branded version of GitLab called "GitSwarm" that is tightly integrated with their "GitFusion" product, which in turn represents a portion of a Helix repository (called a depot) as a git repo.
-
-### Phabricator
-
-A suite of web-based software development collaboration tools, including the Differential code review tool, the Diffusion repository browser, the Herald change monitoring tool, the Maniphest bug tracker and the Phriction wiki. Phabricator integrates with Git, Mercurial, and Subversion.
-
-### Piwik Analytics
-
-An open source analytics software to help you analyze web traffic. It is similar to Google Analytics, except that the latter is not open source and information is stored by Google. In Piwik, the information is stored on your own server and hence is fully private.
-
-### Plus Subscription
-
-GitLab Premium EE [subscription](https://about.gitlab.com/pricing/) that includes training and dedicated Account Management and Service Engineer and complete support package.
-
-### PostgreSQL
-
-An [object-relational](https://en.wikipedia.org/wiki/PostgreSQL) database. Touted as the most advanced open source database, it is one of two database management systems [supported by](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/database.md) GitLab, the other being MySQL.
-
-### Protected Branches
-
-A [feature](https://docs.gitlab.com/ce/user/project/protected_branches.html) that protects branches from unauthorized pushes, force pushing or deletion.
-
-### Protected Tags
-
-A [feature](https://docs.gitlab.com/ce/user/project/protected_tags.html) that protects tags from unauthorized creation, update or deletion
-
-### Pull
-
-Git command to [synchronize](https://git-scm.com/docs/git-pull) the local repository with the remote repository, by fetching all remote changes and merging them into the local repository.
-
-### Puppet
-
-A popular DevOps [automation tool](https://puppet.com/product/how-puppet-works).
-
-### Push
-
-Git [command](https://git-scm.com/docs/git-push) to send commits from the local repository to the remote repository. Read about [advanced push rules](https://gitlab.com/help/pages/README.md) in GitLab.
-
-### Raketasks
-
-### RE Read Only
-
-Permissions to see a file and its contents, but not change it.
-
-### Rebase
-
-In addition to the merge, the [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) is a main way to integrate changes from one branch into another.
-
-### Regression
-
-A regression is something that used to work one way in the last release and then we made a **breaking change** and it no longer works the same way.
-
-_or_
-
-A regression is defined as a change that results in a negative impact on the functionality of an existing feature due to recent changes, i.e. the latest release.
-
-### Remote mirroring
-
-### (Git) Repository
-
-A directory where Git [has been initiatlized](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository) to start version controlling your files. The history of your work is stored here. A remote repository is not on your machine, but usually online (like on GitLab.com, for instance). The main remote repository is usually called "Origin."
-
-##### Remote repository
-
-A [repository](https://about.gitlab.com/2015/05/18/simple-words-for-a-gitlab-newbie/) that is not-on-your-machine, so it's anything that is not your computer. Usually, it is online, GitLab.com for instance. The main remote repository is usually called “Origin”.
-
-### Requirements management
-
-Gives your distributed teams a single shared repository to collaborate and share requirements, understand their relationship to tests, and evaluate linked defects. It includes multiple, preconfigured requirement types.
-
-### Revision Control
-
-Also known as version control or source control, this is the management of changes to documents, computer programs, large web sites, and other collections of information. Changes are usually identified by a number or letter code, termed the "revision number," "revision level," or simply "revision."
-
-### RocketChat
-
-An open source chat application for teams, RocketChat is very similar to Slack but it is also open-source.
-
-### Route Table
-
-A route table contains rules (called routes) that determine where network traffic is directed. Each [subnet in a VPC](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Route_Tables.html) must be associated with a route table.
-
-### Runners
-
-Actual build machines/containers that [run and execute tests](https://gitlab.com/gitlab-org/gitlab-runner) you have specified to be run on GitLab CI.
-
-### Sidekiq
-
-The background job processor GitLab [uses](https://docs.gitlab.com/ce/administration/troubleshooting/sidekiq.html) to asynchronously run tasks.
-
-### Software as a service (SaaS)
-
-Software that is hosted centrally and accessed on-demand (i.e. whenever you want to). This applies to GitLab.com.
-
-### Software Configuration Management (SCM)
-
-This term is often used by people when they mean "Version Control."
-
-### Scrum
-
-An Agile [framework](https://www.scrum.org/Resources/What-is-Scrum) designed to typically help complete complex software projects. It's made up of several parts: product requirements backlog, sprint planning, sprint (development), sprint review, and retrospec (analyzing the sprint). The goal is to end up with potentially shippable products.
-
-### Scrum Board
-
-The board used to track the status and progress of each of the sprint backlog items.
-
-### Shell
-
-Terminal on Mac OSX, GitBash on Windows, or Linux Terminal on Linux. You [use git](https://docs.gitlab.com/ce/gitlab-basics/start-using-git.html) and make changes to GitLab projects in your shell. You [use git](https://docs.gitlab.com/ce/gitlab-basics/start-using-git.html) and make changes to GitLab projects in your shell.
-
-### Shell command runner
-
-### Single-tenant
-
-The tenant purchases their own copy of the software and the software can be customized to meet the specific and needs of that customer. [GitHost.io](https://about.gitlab.com/handbook/positioning-faq/) is our provider of single-tenant 'managed cloud' GitLab instances.
-
-### Slack
-
-Real time messaging app for teams that is used internally by GitLab team members. GitLab users can enable [Slack integration](https://docs.gitlab.com/ce/project_services/slack.html) to trigger push, issue, and merge request events among others.
-
-### Slash commands
-
-### Slave Servers
-
-Also known as secondary servers, these help to spread the load over multiple machines. They also provide backups when the master/primary server crashes.
-
-### Source Code
-
-Program code as typed by a computer programmer (i.e. it has not yet been compiled/translated by the computer to machine language).
-
-### Speed Index
-
-[Speed Index](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index) is "the average time at which visible parts of the page are displayed".
-
-### SSH Key
-
-A unique identifier of a computer. It is used to identify computers without the need for a password (e.g., On GitLab I have [added the ssh key](https://docs.gitlab.com/ce/gitlab-basics/create-your-ssh-keys.html) of all my work machines so that the GitLab instance knows that it can accept code pushes and pulls from this trusted machines whose keys are I have added.)
-
-### Single Sign On (SSO)
-
-An authentication process that allows you enter one username and password to access multiple applications.
-
-### Staging Area
-
-[Staging occurs](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics) before the commit process in git. The staging area is a file, generally contained in your Git directory, that stores information about what will go into your next commit. It’s sometimes referred to as the “index.""
-
-### Standard Subscription
-
-Our mid range EE subscription that includes 24/7 support and support for High Availability [Standard Subscription](https://about.gitlab.com/pricing/).
-
-### Stash
-
-Atlassian's Git on-premise solution. Think of it as Atlassian's GitLab EE, now known as BitBucket Server.
-
-### Static Site Generators (SSGs)
-
-A [software](https://wiki.python.org/moin/StaticSiteGenerator) that takes some text and templates as input and produces html files on the output.
-
-### Subversion
-
-Non-proprietary, centralized version control system.
-
-### Sudo
-
-A program that allows you to perform superuser/administrator actions on Unix Operating Systems (e.g., Linux, OS X.) It actually stands for 'superuser do.'
-
-### Subversion (SVN)
-
-An open source version control system. Read about [migrating from SVN](https://docs.gitlab.com/ce/workflow/importing/migrating_from_svn.html) to GitLab using SubGit.
-
-### Tag
-
-[Represents](https://docs.gitlab.com/ce/api/tags.html) a version of a particular branch at a moment in time.
-
-### Tenancy
-
-#### Multi-tenant
-
-A [multi-tenant](http://whatis.techtarget.com/definition/multi-tenancy) GitLab instance can have any number of customers - such as companies or groups of users using it. GitLab.com is an example of a multi-tenant GitLab instance.
-
-#### Single-tenant
-
-A [single-tenant](http://searchcloudapplications.techtarget.com/definition/single-tenancy) GitLab instance has only one customer - such as a company - using it. On premise GitLab instances are almost exclusively single-tenant.
-
-### Tool Stack
-
-The set of tools used in a process to achieve a common outcome (e.g. set of tools used in Application Lifecycle Management).
-
-### Trac
-
-An open source project management and bug tracking web [application](https://trac.edgewall.org/).
-
-### True-Up licensing model
-
-### Ubuntu
-
-### Untracked files
-
-New files that Git has not [been told](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository) to track previously. Add them by using the command "git add [file path]"
-
-### Upstream repository vs. GitLab repository
-
-[External conversation](https://news.ycombinator.com/item?id=12487112)
-
-### User
-
-Anyone interacting with the software.
-
-### Version Control Software (VCS)
-
-Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. VCS [has evolved](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit#slide=id.gd69537a19_0_32) from local version control systems, to centralized version control systems, to the present [distributed version control systems](https://en.wikipedia.org/wiki/Distributed_version_control) like Git, Mercurial, Bazaar, and Darcs. If any server dies, and these systems were collaborating via it, any of the client repositories can be copied back up to the server to restore it.
-
-### Virtual Private Cloud (VPC)
-
-A [VPC](https://docs.gitlab.com/ce/university/glossary/README.html#virtual-private-cloud-vpc) is an on demand configurable pool of shared computing resources allocated within a public cloud environment, providing some isolation between the different users using the resources. GitLab users need to create a new Amazon VPC in order to [set up High Availability](https://docs.gitlab.com/ce/university/high-availability/aws/).
-
-### Virtual private server (VPS)
-
-A [virtual machine](https://en.wikipedia.org/wiki/Virtual_private_server) sold as a service by an Internet hosting service. A VPS runs its own copy of an operating system, and customers have superuser-level access to that operating system instance, so they can install almost any software that runs on that OS.
-
-### VM Instance
-
-In object-oriented programming, an [instance](http://stackoverflow.com/questions/20461907/what-is-meaning-of-instance-in-programming) is a specific realization of any [object](https://cloud.google.com/compute/docs/instances/). An object may be varied in a number of ways. Each realized variation of that object is an instance. Therefore, a VM instance is an instance of a virtual machine, which is an emulation of a computer system.
-
-### Waterfall
-
-A [model](http://www.umsl.edu/~hugheyd/is6840/waterfall.html) of building software that involves collecting all requirements from the customer, then building and refining all the requirements and finally delivering the complete software to the customer that meets all the requirements they specified.
-
-### Webhooks
-
-A way for an app to [provide](https://docs.gitlab.com/ce/user/project/integrations/webhooks.html) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) Read about setting up [custom git hooks](https://gitlab.com/help/administration/custom_hooks.md) for when webhooks are insufficient.
-
-### Wiki
-
-A [website/system](http://www.wiki.com/) that allows for collaborative editing of its content by the users. In programming, wikis usually contain documentation of how to use the software.
-
-### Working area
-
-Files that have been modified but are not committed. Check them by using the command "git status".
-
-### Working Tree
-
-[Consists of files](http://stackoverflow.com/questions/3689838/difference-between-head-working-tree-index-in-git) that you are currently working on.
-
-### YAML
-
-A human-readable data serialization [language](http://www.yaml.org/about.html) that takes concepts from programming languages such as C, Perl, and Python, and ideas from XML and the data format of electronic mail.
+If you are looking for a definition of a specific term, please search these sites.
diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md
index 01d5073ab7e..caaa0a3675b 100644
--- a/doc/university/high-availability/aws/README.md
+++ b/doc/university/high-availability/aws/README.md
@@ -1,394 +1,5 @@
---
-comments: false
+redirect_to: '../../../install/aws/index.md'
---
-> **Note**: We **do not** recommend using the AWS Elastic File System (EFS), as it can result
-in [significantly degraded performance](../../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs).
-
-# High Availability on AWS
-
-GitLab on AWS can leverage many of the services that are already
-configurable with High Availability. These services have a lot of
-flexibility and are able to adapt to most companies, best of all is the
-ability to automate both vertical and horizontal scaling.
-
-In this article we'll go through a basic HA setup where we'll start by
-configuring our Virtual Private Cloud and subnets to later integrate
-services such as RDS for our database server and ElastiCache as a Redis
-cluster to finally manage them within an auto scaling group with custom
-scaling policies.
-
-***
-
-## Where to Start
-
-Login to your AWS account through the `My Account` dropdown on
-`https://aws.amazon.com` or through the URI assigned to your team such as
-`https://myteam.signin.aws.amazon.com/console/`. You'll start on the
-Amazon Web Services console from where we can choose all of the services
-we'll be using to configure our cloud infrastructure.
-
-### Reference Architecture
-
-![Reference Architecture](img/reference-arch2.png)
-
-***
-
-## Network
-
-We'll start by creating a VPC for our GitLab cloud infrastructure, then
-we can create subnets to have public and private instances in at least
-two AZs. Public subnets will require a Route Table keep an associated
-Internet Gateway.
-
-### VPC
-
-Start by looking for the VPC option on the web console. Now create a new
-VPC. We can use `10.0.0.0/16` for the CIDR block and leave tenancy as
-default if we don't require dedicated hardware.
-
-![New VPC](img/new_vpc.png)
-
-If you're setting up the Elastic File System service then select the VPC
-and from the Actions dropdown choose Edit DNS Hostnames and select Yes.
-
-### Subnet
-
-Now let's create some subnets in different Availability Zones. Make sure
-that each subnet is associated to the VPC we just created, that it has
-a distinct VPC and lastly that CIDR blocks don't overlap. This will also
-allow us to enable multi-AZ for redundancy.
-
-We will create private and public subnets to match load balancers and
-RDS instances as well.
-
-![Subnet Creation](img/subnet.png)
-
-The subnets are listed with their name, AZ and CIDR block:
-
-- gitlab-public-10.0.0.0 - us-west-2a - 10.0.0.0
-- gitlab-private-10.0.1.0 - us-west-2a - 10.0.1.0
-- gitlab-public-10.0.2.0 - us-west-2b - 10.0.2.0
-- gitlab-private-10.0.3.0 - us-west-2b - 10.0.3.0
-
-### Route Table
-
-Up to now all our subnets are private. We need to create a Route Table
-to associate an Internet Gateway. On the same VPC dashboard choose
-Route Tables on the left column and give it a name and associate it to
-our newly created VPC.
-
-![Route Table](img/route_table.png)
-
-### Internet Gateway
-
-Now still on the same dashboard head over to Internet Gateways and
-create a new one. After its created press on the `Attach to VPC` button and
-select our VPC.
-
-![Internet Gateway](img/ig.png)
-
-### Configure Subnets
-
-Go back to the Router Tables screen and select the newly created one,
-press the Routes tab on the bottom section and edit it. We need to add a
-new target which will be our Internet Gateway and have it receive
-traffic from any destination.
-
-![Subnet Config](img/ig-rt.png)
-
-Before leaving this screen select the next tab to the right which is
-Subnet Associations and add our public subnets. If you followed our
-naming convention they should be easy to find.
-
-***
-
-## Database with RDS
-
-For our database server we will use Amazon RDS which offers Multi-AZ
-for redundancy. Let's start by creating a subnet group and then we'll
-create the actual RDS instance.
-
-### Subnet Group
-
-From the RDS dashboard select Subnet Groups. Lets select our VPC from
-the VPC ID dropdown and at the bottom we can add our private subnets.
-
-![Subnet Group](img/db-subnet-group.png)
-
-### RDS
-
-Select the RDS service from the Database section and create a new
-PostgreSQL instance. After choosing between a Production or
-Development instance we'll start with the actual configuration. On the
-image below we have the settings for this article but note the
-following two options which are of particular interest for HA:
-
-1. Multi-AZ-Deployment is recommended as redundancy. Read more at
- [High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html)
-1. While we chose a General Purpose (SSD) for this article a Provisioned
- IOPS (SSD) is best suited for HA. Read more about it at
- [Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html)
-
-![RDS Instance Specs](img/instance_specs.png)
-
-The rest of the setting on this page request a DB identifier, username,
-and a master password. We've chosen to use `gitlab-ha`, `gitlab` and a
-very secure password respectively. Keep these in hand for later.
-
-![Network and Security](img/rds-net-opt.png)
-
-Make sure to choose our gitlab VPC, our subnet group, not have it public,
-and to leave it to create a new security group. The only additional
-change which will be helpful is the database name for which we can use
-`gitlabhq_production`.
-
-***
-
-## ElastiCache
-
-EC is an in-memory hosted caching solution. Redis maintains its own
-persistence and is used for certain types of application.
-
-Let's choose the ElastiCache service in the Database section from our
-AWS console. Now let's create a cache subnet group which will be very
-similar to the RDS subnet group. Make sure to select our VPC and its
-private subnets.
-
-![ElastiCache](img/ec-subnet.png)
-
-Now press the Launch a Cache Cluster and choose Redis for our
-DB engine. You'll be able to configure details such as replication,
-Multi-AZ and node types. The second section will allow us to choose our
-subnet and security group and
-
-![Redis Cluster details](img/redis-cluster-det.png)
-
-![Redis Network](img/redis-net.png)
-
-***
-
-## Network File System
-
-GitLab requires a shared filesystem such as NFS. The file share(s) will be
-mounted on all application servers. There are a variety of ways to build an
-NFS server on AWS.
-
-One option is to use a third-party AMI that offers NFS as a service. A [search
-for 'NFS' in the AWS Marketplace](https://aws.amazon.com/marketplace/search/results?x=0&y=0&searchTerms=NFS&page=1&ref_=nav_search_box)
-shows options such as NetApp, SoftNAS and others.
-
-Another option is to build a simple NFS server using a vanilla Linux server backed
-by AWS Elastic Block Storage (EBS).
-
-> **Note:** GitLab does not recommend using AWS Elastic File System (EFS). See
- details in [High Availability NFS documentation](../../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs)
-
-***
-
-## Initiate AMI
-
-We are going to launch an EC2 instance and bake an image so that we can
-later use it for auto scaling. We'll also take this opportunity to add an
-extension to our RDS through this temporary EC2 instance.
-
-### EC2 Instance
-
-Look for the EC2 option and choose to create an instance. We'll need at
-least a t2.medium type and for this article we'll choose an Ubuntu 14.04
-HVM 64-bit. In the Configure Instance section choose our GitLab VPC and
-a public subnet. I'd choose at least 10GB of storage.
-
-In the security group we'll create a new one considering that we need to
-SSH into the instance and also try it out through http. So let's add the
-http traffic from anywhere and name it something such as
-`gitlab-ec2-security-group`.
-
-While we wait for it to launch we can allocate an Elastic IP and
-associate it with our new EC2 instance.
-
-### RDS and Redis Security Group
-
-After the instance is being created we will navigate to our EC2 security
-groups and add a small change for our EC2 instances to be able to
-connect to RDS. First copy the security group name we just defined,
-namely `gitlab-ec2-security-group`, and edit select the RDS security
-group and edit the inbound rules. Choose the rule type to be PostgreSQL
-and paste the name under source.
-
-![RDS security group](img/rds-sec-group.png)
-
-Similar to the above we'll jump to the `gitlab-ec2-security-group` group
-and add a custom TCP rule for port 6379 accessible within itself.
-
-### Install GitLab
-
-To connect through SSH you will need to have the `pem` file which you
-chose available and with the correct permissions such as `400`.
-
-After accessing your server don't forget to update and upgrade your
-packages.
-
- sudo apt-get update && sudo apt-get upgrade -y
-
-Then follow installation instructions from
-[GitLab](https://about.gitlab.com/downloads-ee/#ubuntu1404), but before
-running reconfigure we need to make sure all our services are tied down
-so just leave the reconfigure command until after we edit our gitlab.rb
-file.
-
-### Extension for PostgreSQL
-
-Connect to your new RDS instance to verify access and to install
-a required extension. We can find the host or endpoint by selecting the
-instance and we just created and after the details drop down we'll find
-it labeled as 'Endpoint'; do remember not to include the colon and port
-number.
-
- sudo /opt/gitlab/embedded/bin/psql -U gitlab -h <rds-endpoint> -d gitlabhq_production
- psql (9.4.7)
- Type "help" for help.
-
- gitlab=# CREATE EXTENSION pg_trgm;
- gitlab=# \q
-
-### Configure GitLab
-
-While connected to your server edit the `gitlab.rb` file at `/etc/gitlab/gitlab.rb`
-find the `external_url 'http://gitlab.example.com'` option and change it
-to the domain you will be using or the public IP address of the current
-instance to test the configuration.
-
-For a more detailed description about configuring GitLab read [Configuring GitLab for HA](http://docs.gitlab.com/ee/administration/high_availability/gitlab.html)
-
-Now look for the GitLab database settings and uncomment as necessary. In
-our current case we'll specify the adapter, encoding, host, db name,
-username, and password.
-
- gitlab_rails['db_adapter'] = "postgresql"
- gitlab_rails['db_encoding'] = "unicode"
- gitlab_rails['db_database'] = "gitlabhq_production"
- gitlab_rails['db_username'] = "gitlab"
- gitlab_rails['db_password'] = "mypassword"
- gitlab_rails['db_host'] = "<rds-endpoint>"
-
-Next, we only need to configure the Redis section by adding the host and
-uncommenting the port.
-
-The last configuration step is to [change the default file locations ](http://docs.gitlab.com/ee/administration/high_availability/nfs.html)
-to make the EFS integration easier to manage.
-
- gitlab_rails['redis_host'] = "<redis-endpoint>"
- gitlab_rails['redis_port'] = 6379
-
-Finally, run reconfigure. You might find it useful to run a check and
-a service status to make sure everything has been set up correctly.
-
- sudo gitlab-ctl reconfigure
- sudo gitlab-rake gitlab:check
- sudo gitlab-ctl status
-
-If everything looks good copy the Elastic IP over to your browser and
-test the instance manually.
-
-### AMI
-
-After you finish testing your EC2 instance go back to its dashboard and
-while the instance is selected press on the Actions dropdown to choose
-Image -> Create an Image. Give it a name and description and confirm.
-
-***
-
-## Load Balancer
-
-On the same dashboard look for Load Balancer on the left column and press
-the Create button. Choose a classic Load Balancer, our gitlab VPC, not
-internal and make sure its listening for HTTP and HTTPS on port 80.
-
-Here is a tricky part though, when adding subnets we need to associate
-public subnets instead of the private ones where our instances will
-actually live.
-
-On the security group section let's create a new one named
-`gitlab-loadbalancer-sec-group` and allow both HTTP ad HTTPS traffic
-from anywhere.
-
-The Load Balancer Health will allow us to indicate where to ping and what
-makes up a healthy or unhealthy instance.
-
-We won't add the instance on the next session because we'll destroy it
-momentarily as we'll be using the image we were creating. We will keep
-the Enable Cross-Zone and Enable Connection Draining active.
-
-After we finish creating the Load Balancer we can revisit our Security
-Groups to improve access only through the ELB and any other requirement
-you might have.
-
-***
-
-## Auto Scaling Group
-
-Our AMI should be done by now so we can start working on our Auto
-Scaling Group.
-
-This option is also available through the EC2 dashboard on the left
-sidebar. Press on the create button. Select the new image on My AMIs and
-give it a `t2.medium` size. To be able to use Elastic File System we need
-to add a script to mount EFS automatically at launch. We'll do this at
-the Advanced Details section where we have a [User Data](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html)
-text area that allows us to add a lot of custom configurations which
-allows you to add a custom script for when launching an instance. Let's
-add the following script to the User Data section:
-
- #cloud-config
- package_upgrade: true
- packages:
- - nfs-common
- runcmd:
- - mkdir -p /gitlab-data
- - chown ec2-user:ec2-user /gitlab-data
- - echo "$(curl --silent http://169.254.169.254/latest/meta-data/placement/availability-zone).file-system-id.aws-region.amazonaws.com:/ /gitlab-data nfs defaults,vers=4.1 0 0" >> /etc/fstab
- - mount -a -t nfs
- - sudo gitlab-ctl reconfigure
-
-On the security group section we can choose our existing
-`gitlab-ec2-security-group` group which has already been tested.
-
-After this is launched we are able to start creating our Auto Scaling
-Group. Start by giving it a name and assigning it our VPC and private
-subnets. We also want to always start with two instances and if you
-scroll down to Advanced Details we can choose to receive traffic from ELBs.
-Let's enable that option and select our ELB. We also want to use the ELB's
-health check.
-
-![Auto scaling](img/auto-scaling-det.png)
-
-### Policies
-
-This is the really great part of Auto Scaling, we get to choose when AWS
-launches new instances and when it removes them. For this group we'll
-scale between 2 and 4 instances where one instance will be added if CPU
-utilization is greater than 60% and one instance is removed if it falls
-to less than 45%. Here are the complete policies:
-
-![Policies](img/policies.png)
-
-You'll notice that after we save this AWS starts launching our two
-instances in different AZs and without a public IP which is exactly what
-we where aiming for.
-
-***
-
-## Final Thoughts
-
-After you're done with the policies section have some fun trying to break
-instances. You should be able to see how the Auto Scaling Group and the
-EC2 screen starts bringing them up again.
-
-High Availability is a vast area, we went mostly through scaling and
-some redundancy options but it might also imply Geographic replication.
-There is a lot of ground yet to cover so have a read through these other
-resources and feel free to open an issue to request additional material.
-
-- [GitLab High Availability](http://docs.gitlab.com/ce/administration/high_availability/README.html#sts=High%20Availability)
-- [GitLab Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html)
+This document was moved to [another location](../../../install/aws/index.md).
diff --git a/doc/university/high-availability/aws/img/auto-scaling-det.png b/doc/university/high-availability/aws/img/auto-scaling-det.png
deleted file mode 100644
index cf32c024bf8..00000000000
--- a/doc/university/high-availability/aws/img/auto-scaling-det.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/db-subnet-group.png b/doc/university/high-availability/aws/img/db-subnet-group.png
deleted file mode 100644
index 875184af310..00000000000
--- a/doc/university/high-availability/aws/img/db-subnet-group.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/ec-subnet.png b/doc/university/high-availability/aws/img/ec-subnet.png
deleted file mode 100644
index 43ef76b62d3..00000000000
--- a/doc/university/high-availability/aws/img/ec-subnet.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/ig-rt.png b/doc/university/high-availability/aws/img/ig-rt.png
deleted file mode 100644
index 62cca074a1e..00000000000
--- a/doc/university/high-availability/aws/img/ig-rt.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/ig.png b/doc/university/high-availability/aws/img/ig.png
deleted file mode 100644
index 2798d4beac3..00000000000
--- a/doc/university/high-availability/aws/img/ig.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/instance_specs.png b/doc/university/high-availability/aws/img/instance_specs.png
deleted file mode 100644
index 2a2b80103fb..00000000000
--- a/doc/university/high-availability/aws/img/instance_specs.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/new_vpc.png b/doc/university/high-availability/aws/img/new_vpc.png
deleted file mode 100644
index d872554fab7..00000000000
--- a/doc/university/high-availability/aws/img/new_vpc.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/policies.png b/doc/university/high-availability/aws/img/policies.png
deleted file mode 100644
index e99497a52a2..00000000000
--- a/doc/university/high-availability/aws/img/policies.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/rds-net-opt.png b/doc/university/high-availability/aws/img/rds-net-opt.png
deleted file mode 100644
index 13130ac96b8..00000000000
--- a/doc/university/high-availability/aws/img/rds-net-opt.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/rds-sec-group.png b/doc/university/high-availability/aws/img/rds-sec-group.png
deleted file mode 100644
index a88caba62c2..00000000000
--- a/doc/university/high-availability/aws/img/rds-sec-group.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/redis-cluster-det.png b/doc/university/high-availability/aws/img/redis-cluster-det.png
deleted file mode 100644
index 51d3a08eab6..00000000000
--- a/doc/university/high-availability/aws/img/redis-cluster-det.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/redis-net.png b/doc/university/high-availability/aws/img/redis-net.png
deleted file mode 100644
index 9022a9ada78..00000000000
--- a/doc/university/high-availability/aws/img/redis-net.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/reference-arch2.png b/doc/university/high-availability/aws/img/reference-arch2.png
deleted file mode 100644
index 0f8790d0f74..00000000000
--- a/doc/university/high-availability/aws/img/reference-arch2.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/route_table.png b/doc/university/high-availability/aws/img/route_table.png
deleted file mode 100644
index c8bef75f01a..00000000000
--- a/doc/university/high-availability/aws/img/route_table.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/high-availability/aws/img/subnet.png b/doc/university/high-availability/aws/img/subnet.png
deleted file mode 100644
index 681c29bf07a..00000000000
--- a/doc/university/high-availability/aws/img/subnet.png
+++ /dev/null
Binary files differ
diff --git a/doc/university/support/README.md b/doc/university/support/README.md
index feb90ae9bad..9563492c137 100644
--- a/doc/university/support/README.md
+++ b/doc/university/support/README.md
@@ -1,12 +1,13 @@
---
comments: false
+type: reference
---
# Support Boot Camp
**Goal:** Prepare new Service Engineers at GitLab
-For each stage there are learning goals and content to support the learning of the engineer.
+For each stage, there are learning goals and content to support the learning of the engineer.
The goal of this boot camp is to have every Service Engineer prepared to help our customers
with whatever needs they might have and to also assist our awesome community with their
questions.
@@ -15,7 +16,7 @@ Always start with the [University Overview](../README.md) and then work
your way here for more advanced and specific training. Once you feel comfortable
with the topics of the current stage, move to the next.
-### Stage 1
+## Stage 1
Follow the topics on the [University Overview](../README.md), concentrate on it
during your first Stage, but also:
@@ -23,22 +24,22 @@ during your first Stage, but also:
- Perform the [first steps](https://about.gitlab.com/handbook/support/onboarding/#first-steps) of
the on-boarding process for new Service Engineers
-#### Goals
+### Goals
Aim to have a good overview of the Product and main features, Git and the Company
-### Stage 2
+## Stage 2
Continue to look over remaining portions of the [University Overview](../README.md) and continue on to these topics:
-#### Set up your development machine
+### Set up your development machine
Get your development machine ready to familiarize yourself with the codebase, the components, and to be prepared to reproduce issues that our users encounter
- Install the [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit)
- [Set up OpenLDAP as part of this](https://gitlab.com/gitlab-org/gitlab-development-kit#openldap)
-#### Become comfortable with the Installation processes that we support
+### Become comfortable with the Installation processes that we support
It's important to understand how to install GitLab in the same way that our users do. Try installing different versions and upgrading and downgrading between them. Installation from source will give you a greater understanding of the components that we employ and how everything fits together.
@@ -54,58 +55,57 @@ Sometimes we need to upgrade customers from old versions of GitLab to latest, so
- Keep this up-to-date as patch and version releases become available, just like our customers would
- Try out the following installation path
- [Install GitLab 4.2 from source](https://gitlab.com/gitlab-org/gitlab-ce/blob/d67117b5a185cfb15a1d7e749588ff981ffbf779/doc/install/installation.md)
- - External MySQL database
- - External NGINX
+ - External MySQL database
+ - External NGINX
- Create some test data
- - Populated Repos
- - Users
- - Groups
- - Projects
- - [Backup using our Backup rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
+ - Populated Repos
+ - Users
+ - Groups
+ - Projects
+ - [Backup using our Backup rake task](../../raketasks/backup_restore.md#creating-a-backup-of-the-gitlab-system)
- [Upgrade to 5.0 source using our Upgrade documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/4.2-to-5.0.md)
- [Upgrade to 5.1 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.0-to-5.1.md)
- [Upgrade to 6.0 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/5.1-to-6.0.md)
- [Upgrade to 7.14 source](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/6.x-or-7.x-to-7.14.md)
- - [Backup using our Backup rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#create-a-backup-of-the-gitlab-system)
- - [Perform the MySQL to PostgreSQL migration to convert your backup](https://docs.gitlab.com/ce/update/mysql_to_postgresql.html#converting-a-gitlab-backup-file-from-mysql-to-postgres)
+ - [Perform the MySQL to PostgreSQL migration to convert your backup](../../update/mysql_to_postgresql.md)
- [Upgrade to Omnibus 7.14](https://docs.gitlab.com/omnibus/update/README.html#upgrading-from-a-non-omnibus-installation-to-an-omnibus-installation)
- - [Restore backup using our Restore rake task](https://docs.gitlab.com/ce/raketasks/backup_restore.html#restore-a-previously-created-backup)
+ - [Restore backup using our Restore rake task](../../raketasks/backup_restore.md#restore)
- [Upgrade to latest EE](https://about.gitlab.com/downloads-ee)
- - (GitLab inc. only) Acquire and apply a license for the Enterprise Edition product, ask in #support
-- Perform a downgrade from [EE to CE](https://docs.gitlab.com/ee/downgrade_ee_to_ce/README.html)
+ - (GitLab inc. only) Acquire and apply a license for the Enterprise Edition product, ask in #support
+- Perform a downgrade from [EE to CE](../../downgrade_ee_to_ce/README.md)
-#### Start to learn about some of the integrations that we support
+### Start to learn about some of the integrations that we support
Our integrations add great value to GitLab. User questions often relate to integrating GitLab with existing external services and the configuration involved
- Learn about our Integrations (specially, not only):
- - [LDAP](https://docs.gitlab.com/ee/integration/ldap.html)
- - [JIRA](https://docs.gitlab.com/ee/project_services/jira.html)
- - [Jenkins](https://docs.gitlab.com/ee/integration/jenkins.html)
- - [SAML](https://docs.gitlab.com/ce/integration/saml.html)
+ - [LDAP](../../integration/ldap.md)
+ - [JIRA](../../project_services/jira.md)
+ - [Jenkins](../../integration/jenkins.md)
+ - [SAML](../../integration/saml.md)
-#### Goals
+### Goals
- Aim to be comfortable with installation of the GitLab product and configuration of some of the major integrations
- Aim to have an installation available for reproducing customer reports
-### Stage 3
+## Stage 3
-#### Understand the gathering of diagnostics for GitLab instances
+### Understand the gathering of diagnostics for GitLab instances
-- Learn about the GitLab checks that are available
- - [Environment Information and maintenance checks](https://docs.gitlab.com/ce/raketasks/maintenance.html)
- - [GitLab check](https://docs.gitlab.com/ce/raketasks/check.html)
+- Learn about the GitLab checks that are available:
+ - [Environment Information and maintenance checks](../../raketasks/maintenance.md)
+ - [GitLab check](../../raketasks/check.md)
- Omnibus commands
- - [Status](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#get-service-status)
- - [Starting and stopping services](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#starting-and-stopping)
- - [Starting a rails console](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#invoking-rake-tasks)
+ - [Status](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#get-service-status)
+ - [Starting and stopping services](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#starting-and-stopping)
+ - [Starting a rails console](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/maintenance/README.md#invoking-rake-tasks)
-#### Learn about the Support process
+### Learn about the Support process
Zendesk is our Support Centre and our main communication line with our Customers. We communicate with customers through several other channels too
-- Familiarize yourself with ZenDesk
+- Familiarize yourself with ZenDesk:
- [UI Overview](https://support.zendesk.com/hc/en-us/articles/203661806-Introduction-to-the-Zendesk-agent-interface)
- [Updating Tickets](https://support.zendesk.com/hc/en-us/articles/212530318-Updating-and-solving-tickets)
- [Working w/ Tickets](https://support.zendesk.com/hc/en-us/articles/203690856-Working-with-tickets) *Read: avoiding agent collision.*
@@ -117,16 +117,16 @@ Zendesk is our Support Centre and our main communication line with our Customers
- Here you will find a large variety of queries mainly from our Users who are self hosting GitLab CE
- Understand the questions that are asked and dig in to try to find a solution
- [Proceed on to the GitLab.com Support Forum](https://about.gitlab.com/handbook/support/#gitlabcom-support-trackera-namesupp-foruma)
- - Here you will find queries regarding our own GitLab.com
- - Helping Users here will give you an understanding of our Admin interface and other tools
+ - Here you will find queries regarding our own GitLab.com
+ - Helping Users here will give you an understanding of our Admin interface and other tools
- [Proceed on to the Twitter tickets in Zendesk](https://about.gitlab.com/handbook/support/#twitter)
- - Here you will gain a great insight into our userbase
- - Learn from any complaints and problems and feed them back to the team
- - Tweets can range from help needed with GitLab installations, the API and just general queries
+ - Here you will gain a great insight into our userbase
+ - Learn from any complaints and problems and feed them back to the team
+ - Tweets can range from help needed with GitLab installations, the API and just general queries
- [Proceed on to Regular email Support tickets](https://about.gitlab.com/handbook/support/#regular-zendesk-tickets-a-nameregulara)
- - Here you will find tickets from our GitLab EE Customers and GitLab CE Users
- - Tickets here are extremely varied and often very technical
- - You should be prepared for these tickets, given the knowledge gained from previous tiers and your training
+ - Here you will find tickets from our GitLab EE Customers and GitLab CE Users
+ - Tickets here are extremely varied and often very technical
+ - You should be prepared for these tickets, given the knowledge gained from previous tiers and your training
- Check out your colleagues' responses
- Hop on to the #support-live-feed channel in Slack and see the tickets as they come in and are updated
- Read through old tickets that your colleagues have worked on
@@ -134,14 +134,14 @@ Zendesk is our Support Centre and our main communication line with our Customers
- [Learn about Cisco WebEx](https://about.gitlab.com/handbook/support/onboarding/#webexa-namewebexa)
- Training calls
- Information gathering calls
- - It's good to find out how new and prospective customers are going to be using the product and how they will set up their infrastructure
+ - It's good to find out how new and prospective customers are going to be using the product and how they will set up their infrastructure
- Diagnosis calls
- - When email isn't enough we may need to hop on a call and do some debugging along side the customer
- - These paired calls are a great learning experience
+ - When email isn't enough we may need to hop on a call and do some debugging along side the customer
+ - These paired calls are a great learning experience
- Upgrade calls
- Emergency calls
-#### Learn about the Escalation process for tickets
+### Learn about the Escalation process for tickets
Some tickets need specific knowledge or a deep understanding of a particular component and will need to be escalated to a Senior Service Engineer or Developer
@@ -149,7 +149,7 @@ Some tickets need specific knowledge or a deep understanding of a particular com
- Find the macros in Zendesk for ticket escalations
- Take a look at the [GitLab.com Team page](https://about.gitlab.com/team/) to find the resident experts in their fields
-#### Learn about raising issues and fielding feature proposals
+### Learn about raising issues and fielding feature proposals
- Understand what's in the pipeline and proposed features at GitLab: [Direction Page](https://about.gitlab.com/direction/)
- Practice searching issues and filtering using [labels](https://gitlab.com/gitlab-org/gitlab-ce/labels) to find existing feature proposals and bugs
@@ -158,33 +158,45 @@ Some tickets need specific knowledge or a deep understanding of a particular com
- Take a look at the [existing issue templates](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker) to see what is expected
- Raise issues for bugs in a manner that would make the issue easily reproducible. A Developer or a contributor may work on your issue
-#### Goals
+### Goals
- Aim to have a good understanding of the problems that customers are facing
- Aim to have gained experience in scheduling and participating in calls with customers
- Aim to have a good understanding of ticket flow through Zendesk and how to interact with our various channels
-### Stage 4
+## Stage 4
-#### Advanced GitLab topics
+### Advanced GitLab topics
Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective
-- Set up and try [Git LFS](https://docs.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html)
-- Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings
-- Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
-- Set up [GitLab CI](https://docs.gitlab.com/ee/ci/quick_start/README.html)
-- Create your first [GitLab Page](https://docs.gitlab.com/ce/administration/pages/)
+- Set up and try [Git LFS](../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
+- Get to know the [GitLab API](../../api/README.md), its capabilities and shortcomings
+- Learn how to [migrate from SVN to Git](../../user/project/import/svn.md)
+- Set up [GitLab CI](../../ci/quick_start/README.md)
+- Create your first [GitLab Page](../../administration/pages/index.md)
- Get to know the GitLab Codebase by reading through the source code:
- Find the differences between the [EE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
and the [CE codebase](https://gitlab.com/gitlab-org/gitlab-ce)
- Ask as many questions as you can think of on the `#support` chat channel
-#### Get initiated for on-call duty
+### Get initiated for on-call duty
- Read over the [public run-books to understand common tasks](https://gitlab.com/gitlab-com/runbooks)
- Create an issue on the internal Organization tracker to schedule time with the DevOps / Production team, so that you learn how to handle GitLab.com going down. Once you are trained for this, you are ready to be added to the on-call rotation.
-#### Goals
+### Goals
- Aim to become a fully-fledged Service Engineer!
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/university/training/gitlab_flow.md b/doc/university/training/gitlab_flow.md
index d7bc7bda43f..0ce92be4994 100644
--- a/doc/university/training/gitlab_flow.md
+++ b/doc/university/training/gitlab_flow.md
@@ -1,5 +1,6 @@
---
comments: false
+type: reference
---
# What is the GitLab Flow
@@ -41,5 +42,17 @@ comments: false
## More details
-For more information read through the [GitLab Flow](../../workflow/gitlab_flow.md)
+For more information, read through the [GitLab Flow](../../workflow/gitlab_flow.md)
documentation.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/university/training/index.md b/doc/university/training/index.md
index 14f096b130f..ddfc662123d 100644
--- a/doc/university/training/index.md
+++ b/doc/university/training/index.md
@@ -1,5 +1,6 @@
---
comments: false
+type: index
---
# GitLab Training Material
@@ -8,3 +9,34 @@ All GitLab training material is stored in markdown format. Slides are
generated using [Deskset](http://www.decksetapp.com/).
All training material is open to public contribution.
+
+This section contains the following topics:
+
+- [Agile and Git](topics/agile_git.md).
+- [Bisect](topics/bisect.md).
+- [Cherry pick](topics/cherry_picking.md).
+- [Code review and collaboration with Merge Requests](topics/merge_requests.md).
+- [Configure your environment](topics/env_setup.md).
+- [Explore GitLab](topics/explore_gitlab.md).
+- [Feature branching](topics/feature_branching.md).
+- [Getting started](topics/getting_started.md).
+- [GitLab flow](gitlab_flow.md).
+- [GitLab Git workshop](user_training.md).
+- [Git add](topics/git_add.md).
+- [Git introduction](topics/git_intro.md).
+- [Git log](topics/git_log.md).
+- [Git stash](topics/stash.md).
+- [Merge conflicts](topics/merge_conflicts.md).
+- [Rollback commits](topics/rollback_commits.md).
+- [Subtree](topics/subtree.md).
+- [Tags](topics/tags.md).
+- [Unstage](topics/unstage.md).
+
+## Additional Resources
+
+1. [GitLab Documentation](http://docs.gitlab.com)
+1. [GUI Clients](http://git-scm.com/downloads/guis)
+1. [Pro Git book](http://git-scm.com/book)
+1. [Platzi Course](https://courses.platzi.com/courses/git-gitlab/)
+1. [Code School tutorial](http://try.github.io/)
+1. Contact us at `subscribers@gitlab.com`
diff --git a/doc/university/training/topics/additional_resources.md b/doc/university/training/topics/additional_resources.md
deleted file mode 100644
index 4871372d105..00000000000
--- a/doc/university/training/topics/additional_resources.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-comments: false
----
-
-# Additional Resources
-
-1. GitLab Documentation: <http://docs.gitlab.com>.
-1. GUI Clients: <http://git-scm.com/downloads/guis>.
-1. Pro Git book: <http://git-scm.com/book>.
-1. Platzi Course: <https://courses.platzi.com/courses/git-gitlab/>.
-1. Code School tutorial: <http://try.github.io/>.
-1. Contact us at `subscribers@gitlab.com`.
diff --git a/doc/university/training/topics/agile_git.md b/doc/university/training/topics/agile_git.md
index 251af99bed7..42d91a50045 100644
--- a/doc/university/training/topics/agile_git.md
+++ b/doc/university/training/topics/agile_git.md
@@ -4,22 +4,16 @@ comments: false
# Agile and Git
-----------
-
## Agile
Lean software development methods focused on collaboration and interaction
with fast and smaller deployment cycles.
-----------
-
## Where Git comes in
Git is an excellent tool for an Agile team considering that it allows
decentralized and simultaneous development.
-----------
-
### Branching And Workflows
Branching in an Agile environment usually happens around user stories with one
@@ -30,8 +24,7 @@ with his/her initials, and US id.
After its tested merge into master and remove the branch.
-----------
-
## What about GitLab
+
Tools like GitLab enhance collaboration by adding dialog around code mainly
through issues and merge requests.
diff --git a/doc/university/training/topics/bisect.md b/doc/university/training/topics/bisect.md
index 4848d0412c1..4178afa2086 100644
--- a/doc/university/training/topics/bisect.md
+++ b/doc/university/training/topics/bisect.md
@@ -4,16 +4,12 @@ comments: false
# Bisect
-----------
-
## Bisect
- Find a commit that introduced a bug
- Works through a process of elimination
- Specify a known good and bad revision to begin
-----------
-
## Bisect
1. Start the bisect process
@@ -23,11 +19,9 @@ comments: false
1. Tell bisect the result
1. Repeat the previous 2 items until you find the offending commit
-----------
-
## Setup
-```
+```sh
mkdir bisect-ex
cd bisect-ex
touch index.html
@@ -44,9 +38,7 @@ comments: false
vi index.html
```
-----------
-
-```
+```sh
# Add all good 3
git add -A
git commit -m "fourth commit"
@@ -64,11 +56,9 @@ comments: false
git commit -m "seventh commit"
```
-----------
-
## Commands
-```
+```sh
git bisect start
# Test your code
git bisect bad
diff --git a/doc/university/training/topics/cherry_picking.md b/doc/university/training/topics/cherry_picking.md
index df23024b6ee..fa0cb5fe6a4 100644
--- a/doc/university/training/topics/cherry_picking.md
+++ b/doc/university/training/topics/cherry_picking.md
@@ -4,16 +4,12 @@ comments: false
# Cherry Pick
-----------
-
## Cherry Pick
- Given an existing commit on one branch, apply the change to another branch
- Useful for backporting bug fixes to previous release branches
- Make the commit on the master branch and pick in to stable
-----------
-
## Cherry Pick
1. Check out a new 'stable' branch from 'master'
@@ -23,8 +19,6 @@ comments: false
1. Check out the 'stable' branch
1. Cherry pick the commit using the SHA obtained earlier
-----------
-
## Commands
```bash
diff --git a/doc/university/training/topics/env_setup.md b/doc/university/training/topics/env_setup.md
index 78ca30e0f55..305f5ecb1fb 100644
--- a/doc/university/training/topics/env_setup.md
+++ b/doc/university/training/topics/env_setup.md
@@ -4,7 +4,6 @@ comments: false
# Configure your environment
-----------
## Install
- **Windows**
@@ -22,8 +21,6 @@ comments: false
sudo apt-get install git-all
```
-----------
-
## Configure Git
One-time configuration of the Git client
@@ -33,8 +30,6 @@ git config --global user.name "Your Name"
git config --global user.email you@example.com
```
-----------
-
## Configure SSH Key
```bash
diff --git a/doc/university/training/topics/explore_gitlab.md b/doc/university/training/topics/explore_gitlab.md
index 84a1879cd92..4ca931d0e26 100644
--- a/doc/university/training/topics/explore_gitlab.md
+++ b/doc/university/training/topics/explore_gitlab.md
@@ -4,8 +4,6 @@ comments: false
# Explore GitLab projects
-----------
-
- Dashboard
- User Preferences
- Issues
diff --git a/doc/university/training/topics/feature_branching.md b/doc/university/training/topics/feature_branching.md
index 0df5f26dbea..d2efe634533 100644
--- a/doc/university/training/topics/feature_branching.md
+++ b/doc/university/training/topics/feature_branching.md
@@ -4,8 +4,6 @@ comments: false
# Feature branching
-----------
-
- Efficient parallel workflow for teams
- Develop each feature in a branch
- Keeps changes isolated
@@ -13,8 +11,6 @@ comments: false
- Push branches to the server frequently
- Hint: This is a cheap backup for your work-in-progress code
-----------
-
## Feature branching
1. Create a new feature branch called 'squash_some_bugs'
@@ -22,11 +18,9 @@ comments: false
1. Commit
1. Push
-----------
-
## Commands
-```
+```sh
git checkout -b squash_some_bugs
# Edit `bugs.rb`
git status
diff --git a/doc/university/training/topics/getting_started.md b/doc/university/training/topics/getting_started.md
index d76ff57bfa3..08027c5d15b 100644
--- a/doc/university/training/topics/getting_started.md
+++ b/doc/university/training/topics/getting_started.md
@@ -4,8 +4,6 @@ comments: false
# Getting Started
-----------
-
## Instantiating Repositories
- Create a new repository by instantiating it through:
@@ -19,8 +17,6 @@ comments: false
git clone <url>
```
-----------
-
## Central Repos
- To instantiate a central repository a `--bare` flag is required.
@@ -31,27 +27,22 @@ comments: false
git init --bare project-name.git
```
-----------
-
## Instantiate workflow with clone
1. Create a project in your user namespace.
- - Choose to import from 'Any Repo by URL' and use <https://gitlab.com/gitlab-org/training-examples.git>.
+ - Choose to import from 'Any Repo by URL' and use <https://gitlab.com/gitlab-org/training-examples.git>.
1. Create a '`Workspace`' directory in your home directory.
1. Clone the '`training-examples`' project.
-----------
-
## Commands
-```
+```sh
mkdir ~/workspace
cd ~/workspace
git clone git@gitlab.example.com:<username>/training-examples.git
cd training-examples
```
-----------
## Git concepts
@@ -67,8 +58,6 @@ Files that have been modified but are not committed.
Modified files that have been marked to go in the next commit.
-----------
-
## Committing Workflow
1. Edit '`edit_this_file.rb`' in '`training-examples`'
@@ -79,11 +68,9 @@ Modified files that have been marked to go in the next commit.
1. Push the commit to the remote
1. View the git log
-----------
-
## Commands
-```
+```sh
# Edit `edit_this_file.rb`
git status
git diff
@@ -93,8 +80,6 @@ git push origin master
git log
```
-----------
-
## Note
- git fetch vs pull
diff --git a/doc/university/training/topics/git_add.md b/doc/university/training/topics/git_add.md
index e02a7deab91..4c61d5eb175 100644
--- a/doc/university/training/topics/git_add.md
+++ b/doc/university/training/topics/git_add.md
@@ -4,8 +4,6 @@ comments: false
# Git Add
-----------
-
## Git Add
Adds content to the index or staging area.
@@ -22,8 +20,6 @@ Adds content to the index or staging area.
git add -A
```
-----------
-
## Git add continued
- Add all text files in current dir:
diff --git a/doc/university/training/topics/git_intro.md b/doc/university/training/topics/git_intro.md
index 127323c292c..845bb7f0a81 100644
--- a/doc/university/training/topics/git_intro.md
+++ b/doc/university/training/topics/git_intro.md
@@ -4,8 +4,6 @@ comments: false
# Git introduction
-----------
-
## Intro
<https://git-scm.com/about>
@@ -17,8 +15,6 @@ comments: false
- Adapts to nearly any workflow
- Fast, reliable and stable file format
-----------
-
## Help!
Use the tools at your disposal when you get stuck.
diff --git a/doc/university/training/topics/git_log.md b/doc/university/training/topics/git_log.md
index 763ef802a04..11addcd3ee1 100644
--- a/doc/university/training/topics/git_log.md
+++ b/doc/university/training/topics/git_log.md
@@ -4,44 +4,38 @@ comments: false
# Git Log
-----------
-
Git log lists commit history. It allows searching and filtering.
- Initiate log:
- ```
+ ```sh
git log
```
- Retrieve set number of records:
- ```
+ ```sh
git log -n 2
```
- Search commits by author. Allows user name or a regular expression.
- ```
+ ```sh
git log --author="user_name"
```
-----------
-
- Search by comment message:
- ```
+ ```sh
git log --grep="<pattern>"
```
- Search by date:
- ```
+ ```sh
git log --since=1.month.ago --until=3.weeks.ago
```
-----------
-
## Git Log Workflow
1. Change to workspace directory
@@ -51,11 +45,9 @@ Git log lists commit history. It allows searching and filtering.
1. Search by date
1. Combine
-----------
-
## Commands
-```
+```sh
cd ~/workspace
git clone git@gitlab.com:gitlab-org/gitlab-runner.git
cd gitlab-runner
diff --git a/doc/university/training/topics/merge_conflicts.md b/doc/university/training/topics/merge_conflicts.md
index a7d42904229..dd235fe3a81 100644
--- a/doc/university/training/topics/merge_conflicts.md
+++ b/doc/university/training/topics/merge_conflicts.md
@@ -4,15 +4,11 @@ comments: false
# Merge conflicts
-----------
-
- Happen often
- Learning to fix conflicts is hard
- Practice makes perfect
- Force push after fixing conflicts. Be careful!
-----------
-
## Merge conflicts
1. Checkout a new branch and edit `conflicts.rb`. Add 'Line4' and 'Line5'.
@@ -26,11 +22,9 @@ comments: false
1. Force push the changes.
1. Finally continue with the Merge Request.
-----------
-
## Commands
-```
+```sh
git checkout -b conflicts_branch
# vi conflicts.rb
@@ -49,7 +43,7 @@ git push origin master
Create a merge request on the GitLab web UI. You'll see a conflict warning.
-```
+```sh
git checkout conflicts_branch
git fetch
git rebase master
@@ -65,7 +59,6 @@ git rebase --continue
# need to force push so that our remote branch is restructured
git push origin conflicts_branch -f
```
-----------
## Note
diff --git a/doc/university/training/topics/merge_requests.md b/doc/university/training/topics/merge_requests.md
index d7b771cd87b..b5bbe7b2e1e 100644
--- a/doc/university/training/topics/merge_requests.md
+++ b/doc/university/training/topics/merge_requests.md
@@ -4,8 +4,6 @@ comments: false
# Code review and collaboration with Merge Requests
-----------
-
- When you want feedback create a merge request
- Target is the default branch (usually master)
- Assign or mention the person you would like to review
@@ -14,8 +12,6 @@ comments: false
- Anyone can comment, not just the assignee
- Push corrections to the same branch
-----------
-
## Merge requests
**Create your first merge request**
@@ -25,8 +21,6 @@ comments: false
1. Push a new commit to the same branch
1. Review the changes again and notice the update
-----------
-
## Feedback and Collaboration
- Merge requests are a time for feedback and collaboration
@@ -36,8 +30,6 @@ comments: false
- Be as receptive as possible
- Feedback is about the best code, not the person. You are not your code
-----------
-
## Feedback and Collaboration
Review the Thoughtbot code-review guide for suggestions to follow when reviewing merge requests:
diff --git a/doc/university/training/topics/rollback_commits.md b/doc/university/training/topics/rollback_commits.md
index 96b89e3319a..1e6602deef2 100644
--- a/doc/university/training/topics/rollback_commits.md
+++ b/doc/university/training/topics/rollback_commits.md
@@ -4,40 +4,34 @@ comments: false
# Rollback Commits
-----------
-
## Undo Commits
- Undo last commit putting everything back into the staging area:
- ```
+ ```sh
git reset --soft HEAD^
```
- Add files and change message with:
- ```
+ ```sh
git commit --amend -m "New Message"
```
-----------
-
- Undo last and remove changes:
- ```
+ ```sh
git reset --hard HEAD^
```
- Same as last one but for two commits back:
- ```
+ ```sh
git reset --hard HEAD^^
```
** Don't reset after pushing **
-----------
-
## Reset Workflow
1. Edit file again 'edit_this_file.rb'
@@ -51,11 +45,9 @@ comments: false
1. Pull for updates
1. Push changes
-----------
-
## Commands
-```
+```sh
# Change file edit_this_file.rb
git status
git commit -am "kjkfjkg"
@@ -68,15 +60,13 @@ git pull origin master
git push origin master
```
-----------
-
## Note
- git revert vs git reset
- Reset removes the commit while revert removes the changes but leaves the commit
- Revert is safer considering we can revert a revert
-```
+```sh
# Changed file
git commit -am "bug introduced"
git revert HEAD
diff --git a/doc/university/training/topics/stash.md b/doc/university/training/topics/stash.md
index dfd28fbcbc9..02b2f610266 100644
--- a/doc/university/training/topics/stash.md
+++ b/doc/university/training/topics/stash.md
@@ -4,14 +4,12 @@ comments: false
# Git Stash
-----------
-
We use git stash to store our changes when they are not ready to be committed
and we need to change to a different branch.
- Stash:
- ```
+ ```sh
git stash save
# or
git stash
@@ -21,18 +19,16 @@ and we need to change to a different branch.
- Apply stash to keep working on it:
- ```
+ ```sh
git stash apply
# or apply a specific one from out stack
git stash apply stash@{3}
```
-----------
-
- Every time we save a stash it gets stacked so by using list we can see all our
stashes.
- ```
+ ```sh
git stash list
# or for more information (log methods)
git stash list --stat
@@ -40,7 +36,7 @@ and we need to change to a different branch.
- To clean our stack we need to manually remove them:
- ```
+ ```sh
# drop top stash
git stash drop
# or
@@ -49,19 +45,15 @@ and we need to change to a different branch.
git stash clear
```
-----------
-
- Apply and drop on one command:
- ```
+ ```sh
git stash pop
```
- If we meet conflicts we need to either reset or commit our changes.
- Conflicts through `pop` will not drop a stash afterwards.
-----------
-
## Git Stash
1. Modify a file
@@ -72,11 +64,9 @@ and we need to change to a different branch.
1. Apply with pop
1. View list to confirm changes
-----------
-
## Commands
-```
+```sh
# Modify edit_this_file.rb file
git add .
diff --git a/doc/university/training/topics/subtree.md b/doc/university/training/topics/subtree.md
index ba7c3394938..981918d4758 100644
--- a/doc/university/training/topics/subtree.md
+++ b/doc/university/training/topics/subtree.md
@@ -9,19 +9,15 @@ comments: false
- For these cases we need a dependency control system.
- Command are painfully long so aliases are necessary.
-----------
-
## Subtree Aliases
-- Add: git subtree add --prefix <target-folder> <url> <branch> --squash.
-- Pull: git subtree add --prefix <target-folder> <url> <branch> --squash.
-- Push: git subtree add --prefix <target-folder> <url> <branch>.
-- Ex: git config alias.sbp 'subtree pull --prefix st /
- git@gitlab.com:balameb/subtree-nested-example.git master --squash'.
-
-----------
+- Add: `git subtree add --prefix <target-folder> <url> <branch> --squash`.
+- Pull: `git subtree add --prefix <target-folder> <url> <branch> --squash`.
+- Push: `git subtree add --prefix <target-folder> <url> <branch>`.
+- Ex: `git config alias.sbp 'subtree pull --prefix st /
+ git@gitlab.com:balameb/subtree-nested-example.git master --squash'`.
-```
+```sh
# Add an alias
# Add
git config alias.sba 'subtree add --prefix st /
@@ -41,9 +37,7 @@ comments: false
```
-----------
-
-```
+```sh
# Adding, or committing won't change the sub repo at remote
# even if we push
git add -A
diff --git a/doc/university/training/topics/tags.md b/doc/university/training/topics/tags.md
index 14c39457838..cdbb8a2da7c 100644
--- a/doc/university/training/topics/tags.md
+++ b/doc/university/training/topics/tags.md
@@ -1,19 +1,16 @@
---
comments: false
+type: reference
---
# Tags
-----------
-
- Useful for marking deployments and releases
- Annotated tags are an unchangeable part of Git history
- Soft/lightweight tags can be set and removed at will
- Many projects combine an annotated release tag with a stable branch
- Consider setting deployment/release tags automatically
-----------
-
# Tags
- Create a lightweight tag
@@ -24,11 +21,9 @@ comments: false
<https://git-scm.com/book/en/Git-Basics-Tagging>
-----------
-
# Commands
-```
+```sh
git checkout master
# Lightweight tag
@@ -40,3 +35,15 @@ git tag
git push origin --tags
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/university/training/topics/unstage.md b/doc/university/training/topics/unstage.md
index da36a3218e5..af16afdc5d1 100644
--- a/doc/university/training/topics/unstage.md
+++ b/doc/university/training/topics/unstage.md
@@ -4,33 +4,29 @@ comments: false
# Unstage
-----------
-
## Unstage
-- To remove files from stage use reset HEAD. Where HEAD is the last commit of the current branch.
+- To remove files from stage use reset HEAD where HEAD is the last commit of the current branch. This will unstage the file but maintain the modifications.
```bash
git reset HEAD <file>
```
-- This will unstage the file but maintain the modifications. To revert the file back to the state it was in before the changes we can use:
+- To revert the file back to the state it was in before the changes we can use:
```bash
git checkout -- <file>
```
-----------
-
- To remove a file from disk and repo use 'git rm' and to rm a dir use the '-r' flag:
- ```
+ ```sh
git rm '*.txt'
git rm -r <dirname>
```
- If we want to remove a file from the repository but keep it on disk, say we forgot to add it to our `.gitignore` file then use `--cache`:
- ```
+ ```sh
git rm <filename> --cache
```
diff --git a/doc/university/training/user_training.md b/doc/university/training/user_training.md
index ca3f777f403..e440652edfc 100644
--- a/doc/university/training/user_training.md
+++ b/doc/university/training/user_training.md
@@ -1,11 +1,10 @@
---
comments: false
+type: reference
---
# GitLab Git Workshop
----
-
## Agenda
1. Brief history of Git.
@@ -13,8 +12,6 @@ comments: false
1. Configure your environment.
1. Workshop.
----
-
## Git introduction
<https://git-scm.com/about>
@@ -26,8 +23,6 @@ comments: false
- Adapts to nearly any workflow.
- Fast, reliable and stable file format.
----
-
## Help!
Use the tools at your disposal when you get stuck.
@@ -36,14 +31,10 @@ Use the tools at your disposal when you get stuck.
- Use Google.
- Read documentation at <https://git-scm.com>.
----
-
## GitLab Walkthrough
![fit](logo.png)
----
-
## Configure your environment
- Windows: Install 'Git for Windows'
@@ -56,8 +47,6 @@ Use the tools at your disposal when you get stuck.
- Debian: '`sudo apt-get install git-all`' or Red Hat '`sudo yum install git-all`'
----
-
## Git Workshop
### Overview
@@ -70,8 +59,6 @@ Use the tools at your disposal when you get stuck.
1. Merge requests.
1. Feedback and Collaboration.
----
-
## Configure Git
One-time configuration of the Git client:
@@ -81,8 +68,6 @@ git config --global user.name "Your Name"
git config --global user.email you@example.com
```
----
-
## Configure SSH Key
```sh
@@ -111,8 +96,6 @@ cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQEL17Ufacg8cDhlQMS5NhV8z3GHZdhCrZbl4gz you@example.com
```
----
-
## Create a project
- Create a project in your user namespace.
@@ -120,8 +103,6 @@ ssh-rsa AAAAB3NzaC1yc2EAAAADAQEL17Ufacg8cDhlQMS5NhV8z3GHZdhCrZbl4gz you@example.
- Create a '`development`' or '`workspace`' directory in your home directory.
- Clone the '`training-examples`' project.
----
-
## Commands (project)
```sh
@@ -137,8 +118,6 @@ git clone git@gitlab.example.com:<username>/training-examples.git
cd training-examples
```
----
-
## Git concepts
### Untracked files
@@ -153,8 +132,6 @@ Files that have been modified but are not committed.
Modified files that have been marked to go in the next commit.
----
-
## Committing
1. Edit '`edit_this_file.rb`' in '`training-examples`'.
@@ -165,8 +142,6 @@ Modified files that have been marked to go in the next commit.
1. Push the commit to the remote.
1. View the git log.
----
-
## Commands (committing)
```sh
@@ -179,8 +154,6 @@ git push origin master
git log
```
----
-
## Feature branching
- Efficient parallel workflow for teams.
@@ -190,8 +163,6 @@ git log
- Push branches to the server frequently.
- Hint: This is a cheap backup for your work-in-progress code.
----
-
## Feature branching steps
1. Create a new feature branch called 'squash_some_bugs'.
@@ -199,8 +170,6 @@ git log
1. Commit.
1. Push.
----
-
## Commands (feature branching)
```sh
@@ -212,8 +181,6 @@ git commit -m 'Fix some buggy code'
git push origin squash_some_bugs
```
----
-
## Merge requests
- When you want feedback create a merge request.
@@ -224,8 +191,6 @@ git push origin squash_some_bugs
- Anyone can comment, not just the assignee.
- Push corrections to the same branch.
----
-
## Merge requests steps
Create your first merge request:
@@ -235,8 +200,6 @@ Create your first merge request:
1. Push a new commit to the same branch.
1. Review the changes again and notice the update.
----
-
## Feedback and Collaboration
- Merge requests are a time for feedback and collaboration.
@@ -246,8 +209,6 @@ Create your first merge request:
- Be as receptive as possible.
- Feedback is about the best code, not the person. You are not your code.
----
-
## Feedback and Collaboration resources
Review the Thoughtbot code-review guide for suggestions to follow when reviewing merge requests:
@@ -255,8 +216,6 @@ Review the Thoughtbot code-review guide for suggestions to follow when reviewing
See GitLab merge requests for examples: <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests>.
----
-
## Explore GitLab projects
![fit](logo.png)
@@ -269,8 +228,6 @@ See GitLab merge requests for examples: <https://gitlab.com/gitlab-org/gitlab-ce
- Manage project members
- Project settings
----
-
## Tags
- Useful for marking deployments and releases.
@@ -279,8 +236,6 @@ See GitLab merge requests for examples: <https://gitlab.com/gitlab-org/gitlab-ce
- Many projects combine an annotated release tag with a stable branch.
- Consider setting deployment/release tags automatically.
----
-
## Tags steps
1. Create a lightweight tag.
@@ -289,8 +244,6 @@ See GitLab merge requests for examples: <https://gitlab.com/gitlab-org/gitlab-ce
Additional resources: <http://git-scm.com/book/en/Git-Basics-Tagging>.
----
-
## Commands (tags)
```sh
@@ -306,8 +259,6 @@ git tag
git push origin --tags
```
----
-
## Merge conflicts
- Happen often.
@@ -315,8 +266,6 @@ git push origin --tags
- Practice makes perfect.
- Force push after fixing conflicts. Be careful!
----
-
## Merge conflicts steps
1. Checkout a new branch and edit `conflicts.rb`. Add 'Line4' and 'Line5'.
@@ -325,8 +274,6 @@ git push origin --tags
1. Commit and push to master.
1. Create a merge request.
----
-
## Merge conflicts commands
After creating a merge request you should notice that conflicts exist. Resolve
@@ -343,8 +290,6 @@ git rebase --continue
git push origin <branch> -f
```
----
-
## Rebase with squash
You may end up with a commit log that looks like this:
@@ -361,8 +306,6 @@ Does this work?
Squash these in to meaningful commits using an interactive rebase.
----
-
## Rebase with squash commands
Squash the commits on the same branch we used for the merge conflicts step.
@@ -373,8 +316,6 @@ git rebase -i master
In the editor, leave the first commit as 'pick' and set others to 'fixup'.
----
-
## Questions?
![fit](logo.png)
@@ -383,9 +324,16 @@ Thank you for your hard work!
## Additional Resources
-- GitLab Documentation: <http://docs.gitlab.com/>.
-- GUI Clients: <http://git-scm.com/downloads/guis>.
-- Pro Git book: <http://git-scm.com/book>.
-- Platzi Course: <https://courses.platzi.com/courses/git-gitlab/>.
-- Code School tutorial: <http://try.github.io/>.
-- Contact us at `subscribers@gitlab.com`.
+See [additional resources](index.md#additional-resources).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index c2dff21b028..f2df4277ca8 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -108,7 +108,7 @@ sudo -u git -H make
### 8. Install/Update `gitlab-elasticsearch-indexer` (optional) **[STARTER ONLY]**
-If you're interested in using GitLab's new [elasticsearch repository indexer](https://docs.gitlab.com/ee/integration/elasticsearch.html#elasticsearch-repository-indexer-beta) (currently in beta)
+If you're interested in using GitLab's new [elasticsearch repository indexer](../integration/elasticsearch.md#elasticsearch-repository-indexer-beta) (currently in beta)
please follow the instructions on the document linked above and enable the
indexer usage in the GitLab admin settings.
diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md
index e74a5c00f7e..428377adb19 100644
--- a/doc/update/upgrading_from_ce_to_ee.md
+++ b/doc/update/upgrading_from_ce_to_ee.md
@@ -75,7 +75,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
### 4. Install `gitlab-elasticsearch-indexer` (optional) **[STARTER ONLY]**
If you're interested in using GitLab's new [elasticsearch repository
-indexer][indexer-beta] (currently in beta) please follow the instructions on the
+indexer](../integration/elasticsearch.md) (currently in beta) please follow the instructions on the
document linked above and enable the indexer usage in the GitLab admin settings.
### 5. Start application
@@ -133,4 +133,3 @@ Additional instructions here.
[support@gitlab.com]: mailto:support@gitlab.com
[old-ee-upgrade-docs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/11-8-stable-ee/doc/update
-[indexer-beta]: https://docs.gitlab.com/ee/integration/elasticsearch.html
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index 82a86f3f343..bc8e5fed774 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -107,14 +107,54 @@ Download and install Go:
# Remove former Go installation folder
sudo rm -rf /usr/local/go
-curl --remote-name --progress https://dl.google.com/go/go1.10.5.linux-amd64.tar.gz
-echo 'a035d9beda8341b645d3f45a1b620cf2d8fb0c5eb409be36b389c0fd384ecc3a go1.10.5.linux-amd64.tar.gz' | shasum -a256 -c - && \
- sudo tar -C /usr/local -xzf go1.10.5.linux-amd64.tar.gz
+curl --remote-name --progress https://dl.google.com/go/go1.11.10.linux-amd64.tar.gz
+echo 'aefaa228b68641e266d1f23f1d95dba33f17552ba132878b65bb798ffa37e6d0 go1.11.10.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.11.10.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
-rm go1.10.5.linux-amd64.tar.gz
+rm go1.11.10.linux-amd64.tar.gz
```
-### 6. Get latest code
+### 6. Update git
+
+NOTE: **Note:**
+GitLab 11.11 and higher only supports Git 2.21.x and newer, and
+[dropped support for older versions](https://gitlab.com/gitlab-org/gitlab-ce/issues/54255).
+Be sure to upgrade your installation if necessary.
+
+```bash
+# Make sure Git is version 2.21.0 or higher
+git --version
+
+# Remove packaged Git
+sudo apt-get remove git-core
+
+# Install dependencies
+sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev build-essential
+
+# Download and compile pcre2 from source
+curl --silent --show-error --location https://ftp.pcre.org/pub/pcre/pcre2-10.33.tar.gz --output pcre2.tar.gz
+tar -xzf pcre2.tar.gz
+cd pcre2-10.33
+chmod +x configure
+./configure --prefix=/usr --enable-jit
+make
+make install
+
+# Download and compile from source
+cd /tmp
+curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.21.0.tar.gz
+echo '85eca51c7404da75e353eba587f87fea9481ba41e162206a6f70ad8118147bee git-2.21.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.21.0.tar.gz
+cd git-2.21.0/
+./configure --with-libpcre
+make prefix=/usr/local all
+
+# Install into /usr/local/bin
+sudo make prefix=/usr/local install
+
+# You should edit config/gitlab.yml, change the git -> bin_path to /usr/local/bin/git
+```
+
+### 7. Get latest code
```bash
cd /home/git/gitlab
@@ -142,7 +182,7 @@ cd /home/git/gitlab
sudo -u git -H git checkout BRANCH-ee
```
-### 7. Update gitlab-shell
+### 8. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
@@ -152,7 +192,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
sudo -u git -H bin/compile
```
-### 8. Update gitlab-workhorse
+### 9. Update gitlab-workhorse
Install and compile gitlab-workhorse. GitLab-Workhorse uses
[GNU Make](https://www.gnu.org/software/make/).
@@ -167,7 +207,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
sudo -u git -H make
```
-### 9. Update Gitaly
+### 10. Update Gitaly
#### Compile Gitaly
@@ -178,7 +218,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
-### 10. Update gitlab-pages
+### 11. Update gitlab-pages
#### Only needed if you use GitLab Pages
@@ -195,7 +235,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
sudo -u git -H make
```
-### 11. Update MySQL permissions
+### 12. Update MySQL permissions
If you are using MySQL you need to grant the GitLab user the necessary
permissions on the database:
@@ -217,7 +257,7 @@ You can make this setting permanent by adding it to your `my.cnf`:
log_bin_trust_function_creators=1
```
-### 12. Update configuration files
+### 13. Update configuration files
#### New configuration options for `gitlab.yml`
@@ -291,7 +331,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
```
-### 13. Install libs, migrations, etc.
+### 14. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -323,14 +363,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
**MySQL installations**: Run through the `MySQL strings limits` and `Tables and
data conversion to utf8mb4` [tasks](../install/database_mysql.md).
-### 14. Start application
+### 15. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
-### 15. Check application status
+### 16. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/user/admin_area/diff_limits.md b/doc/user/admin_area/diff_limits.md
index 9205860ef1f..4063c40a751 100644
--- a/doc/user/admin_area/diff_limits.md
+++ b/doc/user/admin_area/diff_limits.md
@@ -1,21 +1,40 @@
+---
+type: reference
+---
+
# Diff limits administration
+You can set a maximum size for display of diff files (patches).
+
+## Maximum diff patch size
+
+Diff files which exceed this value will be presented as 'too large' and won't
+be expandable. Instead of an expandable view, a link to the blob view will be
+shown.
+
+Patches greater than 10% of this size will be automatically collapsed, and a
+link to expand the diff will be presented.
+
NOTE: **Note:**
Merge requests and branch comparison views will be affected.
CAUTION: **Caution:**
-These settings are currently under experimental state. They'll
-increase the resource consumption of your instance and should
-be edited mindfully.
+This setting is experimental. An increased maximum will increase resource
+consumption of your instance. Keep this in mind when adjusting the maximum.
-1. Access **Admin area > Settings > General**
-1. Expand **Diff limits**
+1. Go to **Admin area > Settings > General**.
+1. Expand **Diff limits**.
+1. Enter a value for **Maximum diff patch size**, measured in bytes.
+1. Click on **Save changes**.
-### Maximum diff patch size
+<!-- ## Troubleshooting
-This is the content size each diff file (patch) is allowed to reach before
-it's collapsed, without the possibility of being expanded. A link redirecting
-to the blob view will be presented for the patches that surpass this limit.
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-Patches surpassing 10% of this content size will be automatically collapsed,
-but expandable (a link to expand the diff will be presented).
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/geo_nodes.md b/doc/user/admin_area/geo_nodes.md
index 776ab139c64..d99b87cbc5c 100644
--- a/doc/user/admin_area/geo_nodes.md
+++ b/doc/user/admin_area/geo_nodes.md
@@ -1,9 +1,13 @@
+---
+type: howto
+---
+
# Geo nodes admin area **[PREMIUM ONLY]**
-For more information about setting up GitLab Geo, read the
-[Geo documentation](https://docs.gitlab.com/ee/administration/geo/replication/index.html).
+You can configure various settings for GitLab Geo nodes. For more information, see
+[Geo documentation](../../administration/geo/replication/index.md).
-When you're done, you can navigate to **Admin area > Geo** (`/admin/geo/nodes`).
+On the primary node, go to **Admin area > Geo**. On secondary nodes, go to **Admin area > Geo > Nodes**.
## Common settings
@@ -25,7 +29,7 @@ changes on the **primary** node!
| Setting | Description |
|---------------------------|-------------|
-| Selective synchronization | Enable Geo [selective sync](https://docs.gitlab.com/ee/administration/geo/replication/configuration.html#selective-synchronization) for this **secondary** node. |
+| Selective synchronization | Enable Geo [selective sync](../../administration/geo/replication/configuration.md#selective-synchronization) for this **secondary** node. |
| Repository sync capacity | Number of concurrent requests this **secondary** node will make to the **primary** node when backfilling repositories. |
| File sync capacity | Number of concurrent requests this **secondary** node will make to the **primary** node when backfilling files. |
@@ -68,3 +72,15 @@ a unique `name` is set for each Geo node. The `gitlab.rb` setting
The load balancer must use sticky sessions in order to avoid authentication
failures and cross site request errors.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
diff --git a/doc/user/admin_area/img/index_runners_search_or_filter.png b/doc/user/admin_area/img/index_runners_search_or_filter.png
new file mode 100644
index 00000000000..5176a1a39bf
--- /dev/null
+++ b/doc/user/admin_area/img/index_runners_search_or_filter.png
Binary files differ
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index d2995d48833..91c771764f8 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# GitLab Admin Area **[CORE ONLY]**
The Admin Area provides a web UI for administering some features of GitLab self-managed instances.
@@ -16,14 +20,14 @@ The Admin Area is made up of the following sections:
| Section | Description |
|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------|
-| Overview | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administer-projects), users, groups, jobs, runners, and Gitaly servers. |
+| Overview | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administer-projects), [users](#administer-users), [groups](#administer-groups), [jobs](#administer-jobs), [Runners](#administer-runners), and [Gitaly servers](#administer-gitaly-servers). |
| Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. |
| 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. |
| Abuse Reports | Manage [abuse reports](abuse_reports.md) submitted by your users. |
| License **[STARTER ONLY]** | Upload, display, and remove [licenses](license.md). |
-| Push Rules **[STARTER]** | Configure pre-defined git [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html) for projects. |
+| Push Rules **[STARTER]** | Configure pre-defined git [push rules](../../push_rules/push_rules.md) for projects. |
| Geo **[PREMIUM ONLY]** | Configure and maintain [Geo nodes](geo_nodes.md). |
| Deploy Keys | Create instance-wide [SSH deploy keys](../../ssh/README.md#deploy-keys). |
| Service Templates | Create [service templates](../project/integrations/services_templates.md) for projects. |
@@ -90,3 +94,134 @@ You can combine the filter options. For example, to list only public projects wi
1. Click the **Public** tab.
1. Enter `score` in the **Filter by name...** input box.
+
+## Administer Users
+
+You can administer all users in the GitLab instance from the Admin Area's Users page.
+
+To access the Users page, go to **Admin Area > Overview > Users**.
+
+Click the **Active**, **Admins**, **2FA Enabled**, or **2FA Disabled**, **External**, or
+**Without projects** tab to list only users of that criteria.
+
+For each user, their username, email address, are listed, also the date their account was
+created and the date of last activity. To edit a user, click the **Edit** button in that user's
+row. To delete the user, or delete the user and their contributions, click the cog dropdown in
+that user's row, and select the desired option.
+
+To change the sort order:
+
+1. Click the sort dropdown.
+1. Select the desired order.
+
+By default the sort dropdown shows **Name**.
+
+To search for users, enter your criteria in the search field. The user search is case
+insensitive, and applies partial matching to name and username. To search for an email address,
+you must provide the complete email address.
+
+## Administer Groups
+
+You can administer all groups in the GitLab instance from the Admin Area's Groups page.
+
+To access the Groups page, go to **Admin Area > Overview > Groups**.
+
+For each group, the page displays their name, description, size, number of projects in the group,
+number of members, and whether the group is private, internal, or public. To edit a group, click
+the **Edit** button in that group's row. To delete the group, click the **Delete** button in
+that group's row.
+
+To change the sort order, click the sort dropdown and select the desired order. The default
+sort order is by **Last created**.
+
+To search for groups by name, enter your criteria in the search field. The group search is case
+insensitive, and applies partial matching.
+
+To [Create a new group](../group/index.md#create-a-new-group) click **New group**.
+
+## Administer Jobs
+
+You can administer all jobs in the GitLab instance from the Admin Area's Jobs page.
+
+To access the Jobs page, go to **Admin Area > Overview > Jobs**.
+
+All jobs are listed, in descending order of job ID.
+
+Click the **All** tab to list all jobs. Click the **Pending**, **Running**, or **Finished** tab to list only jobs of that status.
+
+For each job, the following details are listed:
+
+| Field | Description |
+|--------- | ----------- |
+| Status | Job status, either **passed**, **skipped**, or **failed**. |
+| Job | Includes links to the job, branch, and the commit that started the job. |
+| Pipeline | Includes a link to the specific pipeline. |
+| Project | Name of the project, and organization, to which the job belongs. |
+| Runner | Name of the CI runner assigned to execute the job. |
+| Stage | Stage that the job is declared in a `.gitlab-ci.yml` file. |
+| Name | Name of the job specified in a `.gitlab-ci.yml` file. |
+| Timing | Duration of the job, and how long ago the job completed. |
+| Coverage | Percentage of tests coverage. |
+
+## Administer Runners
+
+You can adminster all Runners in the GitLab instance from the Admin Area's **Runners** page. See
+[GitLab Runner](https://docs.gitlab.com/runner/) for more information on Runner itself.
+
+To access the **Runners** page, go to **Admin Area > Overview > Runners**.
+
+The **Runners** page features:
+
+- A description of Runners, and their possible states.
+- Instructions on installing a Runner.
+- A list of all registered Runners.
+
+Runners are listed in descending order by the date they were created, by default. You can change
+the sort order to *Last Contacted* from the dropdown beside the search field.
+
+To search Runners' descriptions:
+
+1. In the **Search or filter results...** field, type the description of the Runner you want to
+find.
+1. Press Enter.
+
+You can also filter Runners by status, type, and tag. To filter:
+
+1. Click in the **Search or filter results...** field.
+1. Select **status:**, **type:**, or **tag:**.
+1. Select or enter your search criteria.
+
+![Attributes of a Runner, with the **Search or filter results...** field active](img/index_runners_search_or_filter.png)
+
+For each Runner, the following attributes are listed:
+
+| Attribute | Description |
+| ------------ | ----------- |
+| Type | One or more of the following states: shared, group, specific, locked, or paused |
+| Runner token | Token used to identify the Runner, and which the Runner uses to communicate with the GitLab instance |
+| Description | Description given to the Runner when it was created |
+| Version | GitLab Runner version |
+| IP address | IP address of the host on which the Runner is registered |
+| Projects | Projects to which the Runner is assigned |
+| Jobs | Total of jobs run by the Runner |
+| Tags | Tags associated with the Runner |
+| Last contact | Timestamp indicating when the GitLab instance last contacted the Runner |
+
+You can also edit, pause, or remove each Runner.
+
+## Administer Gitaly servers
+
+You can list all Gitaly servers in the GitLab instance from the Admin Area's **Gitaly Servers**
+page. For more details, see [Gitaly](../../administration/gitaly/index.md).
+
+To access the **Gitaly Servers** page, go to **Admin Area > Overview > Gitaly Servers**.
+
+For each Gitaly server, the following details are listed:
+
+| Field | Description |
+| -------------- | ----------- |
+| Storage | Repository storage |
+| Address | Network address on which the Gitaly server is listening |
+| 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. |
diff --git a/doc/user/admin_area/labels.md b/doc/user/admin_area/labels.md
index e383142c33e..eba27548f86 100644
--- a/doc/user/admin_area/labels.md
+++ b/doc/user/admin_area/labels.md
@@ -1,9 +1,25 @@
+---
+type: reference
+---
+
# Labels administration **[CORE ONLY]**
-## Default Labels
+In the Admin Area, you can manage labels for the GitLab instance. For more details, see [Labels](../project/labels.md).
-### Define your own default Label Set
+## Default Labels
-Labels that are created within the Labels view on the Admin Dashboard will be automatically added to each new project.
+Labels created in the Admin Area become available to each _new_ project.
![Default label set](img/admin_labels.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/license.md b/doc/user/admin_area/license.md
index 49959a9daef..8ddb9c3d707 100644
--- a/doc/user/admin_area/license.md
+++ b/doc/user/admin_area/license.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Activate all GitLab Enterprise Edition functionality with a license **[STARTER ONLY]**
To activate all GitLab Enterprise Edition (EE) functionality, you need to upload
@@ -45,16 +49,10 @@ Otherwise, you can:
## Add your license at install time
-The license may be automatically injected during installation using one of
-two methods.
-
-The first requires a license file named `Gitlab.gitlab-release`.
+A license can be automatically imported at install time, by placing a file named
+`Gitlab.gitlab-license` in `/etc/gitlab/` for Omnibus, or `config/` for source installations.
-Place it in the `config/` directory if installing from source or in the
-`/etc/gitlab/` directory if installing Omnibus.
-
-The second allows the administrator to configure the location and
-filename of the license.
+It is also possible to specify a custom location and filename for the license.
Source installations should set the `GITLAB_LICENSE_FILE` environment
variable with the path to a valid GitLab Enterprise Edition license.
@@ -108,3 +106,15 @@ but only the latest license will be used as the active license.
[free trial]: https://about.gitlab.com/free-trial/
[pricing]: https://about.gitlab.com/pricing/
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md
index e183898dfb1..f80d4e9d2aa 100644
--- a/doc/user/admin_area/monitoring/health_check.md
+++ b/doc/user/admin_area/monitoring/health_check.md
@@ -1,12 +1,14 @@
-# Health Check
+---
+type: concepts, howto
+---
-> **Notes:**
+# Health Check
-> - Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1.
-> - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and was
-> be deprecated in GitLab 9.1.
-> - [Access token](#access-token-deprecated) has been deprecated in GitLab 9.4
-> in favor of [IP whitelist](#ip-whitelist)
+> - Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1.
+> - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and was
+> deprecated in GitLab 9.1.
+> - [Access token](#access-token-deprecated) has been deprecated in GitLab 9.4
+> in favor of [IP whitelist](#ip-whitelist).
GitLab provides liveness and readiness probes to indicate service health and
reachability to required services. These probes report on the status of the
@@ -17,8 +19,7 @@ traffic until the system is ready or restart the container as needed.
## IP whitelist
To access monitoring resources, the requesting client IP needs to be included in a whitelist.
-
-[Read how to add IPs to a whitelist for the monitoring endpoints][admin].
+For details, see [how to add IPs to a whitelist for the monitoring endpoints](../../../administration/monitoring/ip_whitelist.md).
## Using the endpoints
@@ -30,7 +31,7 @@ With default whitelist settings, the probes can be accessed from localhost using
The first endpoint, `health`, only checks whether the application server is running. It does not verify the database or other services are running. A successful response will return a 200 status code with the following message:
-```
+```text
GitLab OK
```
@@ -87,9 +88,8 @@ will return a valid successful HTTP status code, and a `success` message.
## Access token (Deprecated)
->**Note:**
-Access token has been deprecated in GitLab 9.4
-in favor of [IP whitelist](#ip-whitelist)
+> NOTE: **Note:**
+> Access token has been deprecated in GitLab 9.4 in favor of [IP whitelist](#ip-whitelist).
An access token needs to be provided while accessing the probe endpoints. The current
accepted token can be found under the **Admin area ➔ Monitoring ➔ Health check**
@@ -99,14 +99,25 @@ accepted token can be found under the **Admin area ➔ Monitoring ➔ Health che
The access token can be passed as a URL parameter:
-```
+```text
https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN
```
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10416
[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888
[pingdom]: https://www.pingdom.com
[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html
[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring
[kubernetes]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
-[admin]: ../../../administration/monitoring/ip_whitelist.md
diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md
index 4a5bfb6b677..001e4b6bf48 100644
--- a/doc/user/admin_area/settings/account_and_limit_settings.md
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -1,25 +1,29 @@
+---
+type: reference
+---
+
# Account and limit settings
## Repository size limit **[STARTER]**
-> [Introduced][ee-740] in [GitLab Enterprise Edition 8.12][ee-8.12].
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/740) in [GitLab Enterprise Edition 8.12](https://about.gitlab.com/2016/09/22/gitlab-8-12-released/#limit-project-size-ee).
+> Available in [GitLab Starter](https://about.gitlab.com/pricing/).
Repositories within your GitLab instance can grow quickly, especially if you are
-using LFS. Their size can grow exponentially and eat up your storage device quite
-fast.
+using LFS. Their size can grow exponentially, rapidly consuming available storage.
-In order to avoid this from happening, you can set a hard limit for your
-repositories' size. This limit can be set globally, per group, or per project,
-with per project limits taking the highest priority.
+To avoid this from happening, you can set a hard limit for your repositories' size.
+This limit can be set globally, per group, or per project, with per project limits
+taking the highest priority.
-There are numerous cases where you'll need to set up a limit for repository size.
+There are numerous use cases where you might set up a limit for repository size.
For instance, consider the following workflow:
-1. Your team develops apps which demand large files to be stored in
+1. Your team develops apps which require large files to be stored in
the application repository.
-1. Although you have enabled [Git LFS](../../../workflow/lfs/manage_large_binaries_with_git_lfs.html#git-lfs)
+1. Although you have enabled [Git LFS](../../../workflow/lfs/manage_large_binaries_with_git_lfs.md#git-lfs)
to your project, your storage has grown significantly.
-1. Before you blow your storage limit up, you set up a limit of 10 GB
+1. Before you exceed available storage, you set up a limit of 10 GB
per repository.
### How it works
@@ -36,18 +40,23 @@ These settings can be found within:
- **Admin Area > Settings > General**.
- The path `/admin/application_settings`.
-The very first push of a new project cannot be checked for size as of now, so
-the first push will allow you to upload more than the limit dictates, but every
-subsequent push will be denied. LFS objects, however, can be checked on first
-push and **will** be rejected if the sum of their sizes exceeds the maximum
-allowed repository size.
+The first push of a new project, including LFS objects, will be checked for size
+and **will** be rejected if the sum of their sizes exceeds the maximum allowed
+repository size.
+
+For details on manually purging files, see [reducing the repository size using Git](../../project/repository/reducing_the_repo_size_using_git.md).
+
+NOTE: **Note:**
+For GitLab.com, the repository size limit is 10 GB.
-For more manually purging the files, read the docs on
-[reducing the repository size using Git][repo-size].
+<!-- ## Troubleshooting
-> **Note:**
-> For GitLab.com, the repository size limit is 10 GB.
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-[ee-740]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/740
-[repo-size]: ../../project/repository/reducing_the_repo_size_using_git.md
-[ee-8.12]: https://about.gitlab.com/2016/09/22/gitlab-8-12-released/#limit-project-size-ee
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 9dd476656ed..6c4abce83c2 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Continuous Integration and Deployment Admin settings **[CORE ONLY]**
In this area, you will find settings for Auto DevOps, Runners and job artifacts.
@@ -145,3 +149,15 @@ To set the duration for which the jobs will be considered as old and expired:
Once that time passes, the jobs will be archived and no longer able to be
retried. Make it empty to never expire jobs. It has to be no less than 1 day,
for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md
index 01a98cf15dc..9555a695b13 100644
--- a/doc/user/admin_area/settings/email.md
+++ b/doc/user/admin_area/settings/email.md
@@ -1,12 +1,18 @@
+---
+type: reference
+---
+
# Email
+You can customize some of the content in emails sent from your GitLab instance.
+
## Custom logo
The logo in the header of some emails can be customized, see the [logo customization section](../../../customization/branded_page_and_email_header.md).
## Custom additional text **[PREMIUM ONLY]**
->[Introduced][ee-5031] in [GitLab Premium][eep] 10.7.
+> [Introduced][ee-5031] in [GitLab Premium][eep] 10.7.
The additional text will appear at the bottom of any email and can be used for
legal/auditing/compliance reasons.
@@ -24,8 +30,8 @@ legal/auditing/compliance reasons.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22560) in GitLab 11.5.
-This configuration option sets the email hostname for [private commit emails](../../profile/index.md#private-commit-email),
-and it's, by default, set to `users.noreply.YOUR_CONFIGURED_HOSTNAME`.
+This configuration option sets the email hostname for [private commit emails](../../profile/index.md#private-commit-email).
+ By default it is set to `users.noreply.YOUR_CONFIGURED_HOSTNAME`.
In order to change this option:
@@ -34,5 +40,17 @@ In order to change this option:
1. Hit **Save** for the changes to take effect.
NOTE: **Note**: Once the hostname gets configured, every private commit email using the previous hostname, will not get
-recognized by GitLab. This can directly conflict with certain [Push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html) such as
+recognized by GitLab. This can directly conflict with certain [Push rules](../../../push_rules/push_rules.md) such as
`Check whether author is a GitLab user` and `Check whether committer is the current authenticated user`.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
diff --git a/doc/user/admin_area/settings/external_authorization.md b/doc/user/admin_area/settings/external_authorization.md
index 06e00e02f3d..11c0867da17 100644
--- a/doc/user/admin_area/settings/external_authorization.md
+++ b/doc/user/admin_area/settings/external_authorization.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# External authorization control **[CORE ONLY]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4216) in
@@ -108,5 +112,17 @@ The label will be shown on all project pages in the upper right corner.
![classification label on project page](img/classification_label_on_project_page.png)
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[omnibus-ssl-docs]: https://docs.gitlab.com/omnibus/settings/ssl.html
[omnibus-log-docs]: https://docs.gitlab.com/omnibus/settings/logs.html
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 0e697b9e445..eed087ae52b 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -1,9 +1,13 @@
-# Admin area settings **[CORE ONLY]**
+---
+type: index
+---
-In the admin area settings, you can find various options for your GitLab
+# Admin Area settings **[CORE ONLY]**
+
+In the Admin Area **Settings** page, you can find various options for your GitLab
instance like sign-up restrictions, account limits and quota, metrics, etc.
-Navigate to it by going to **Admin area > Settings**. Some of the settings
+Navigate to it by going to **Admin Area > Settings**. Some of the settings
include:
- [Account and limit settings](account_and_limit_settings.md) **[STARTER]**
@@ -22,7 +26,7 @@ in the **Localization** section of **Admin area > Settings > Preferences**.
## GitLab.com admin area settings
-Most of the settings under the admin area change the behavior of the whole
+Most of the settings under the Admin Area change the behavior of the whole
GitLab instance. For GitLab.com, the admin settings are available only for the
GitLab.com administrators, and the parameters can be found on the
[GitLab.com settings](../../gitlab_com/index.md) documentation.
diff --git a/doc/user/admin_area/settings/instance_template_repository.md b/doc/user/admin_area/settings/instance_template_repository.md
index 4010008f694..91286a67c31 100644
--- a/doc/user/admin_area/settings/instance_template_repository.md
+++ b/doc/user/admin_area/settings/instance_template_repository.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Instance template repository **[PREMIUM ONLY]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5986) in
@@ -61,3 +65,15 @@ top of the list.
If this feature is disabled or no templates are present, there will be
no "Custom" section in the selection dropdown.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index d3ecfd42903..cebf36c7ec1 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Sign-up restrictions
You can block email addresses of specific domains, or whitelist only some
@@ -37,5 +41,17 @@ semicolon, comma, or a new line.
![Domain Blacklist](img/domain_blacklist.png)
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[ce-5259]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5259
[ce-598]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/598
diff --git a/doc/user/admin_area/settings/terms.md b/doc/user/admin_area/settings/terms.md
index e2290bf0598..a5f8d05f662 100644
--- a/doc/user/admin_area/settings/terms.md
+++ b/doc/user/admin_area/settings/terms.md
@@ -1,29 +1,35 @@
+---
+type: reference
+---
+
# Enforce accepting Terms of Service
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18570)
> in [GitLab Core](https://about.gitlab.com/pricing/) 10.8
+An admin can enforce acceptance of a terms of service and privacy policy. When this option is enabled, new and existing users must accept the terms.
+
## Configuration
-When it is required for all users of the GitLab instance to accept the
-Terms of Service, this can be configured by an admin on the settings
-page:
+To enforce acceptance of a Terms of Service and Privacy Policy:
-![Enable enforcing Terms of Service](img/enforce_terms.png).
+1. Log in to the GitLab instance as an admin user.
+1. Go to **Admin Area > Settings > General**.
+1. Expand the **Terms of Service and Privacy Policy** section.
+1. Check the **Require all users to accept Terms of Service and Privacy Policy when they access
+GitLab.** checkbox.
+1. Input the text of the **Terms of Service and Privacy Policy**. Markdown formatting can be used in this input box.
+1. Click **Save changes**.
+1. When you are presented with the **Terms of Service** statement, click **Accept terms**.
-The terms itself can be entered using Markdown. For each update to the
-terms, a new version is stored. When a user accepts or declines the
-terms, GitLab will keep track of which version they accepted or
-declined.
+![Enable enforcing Terms of Service](img/enforce_terms.png).
-When an admin enables this feature, they will automattically be
-directed to the page to accept the terms themselves. After they
-accept, they will be directed back to the settings page.
+For each update to the terms, a new version is stored. When a user accepts or declines the terms,
+GitLab will record which version they accepted or declined.
-## New registrations
+## New users
-When this feature is enabled, a checkbox will be available in the
-sign-up form.
+When this feature is enabled, a checkbox is added to the sign-up form.
![Sign up form](img/sign_up_terms.png)
@@ -49,3 +55,15 @@ If the user was already logged in when the feature was turned on,
they will be asked to accept the terms on their next interaction.
If a user declines the terms, they will be signed out.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/third_party_offers.md b/doc/user/admin_area/settings/third_party_offers.md
index 23311801790..d3c9cf7d8ff 100644
--- a/doc/user/admin_area/settings/third_party_offers.md
+++ b/doc/user/admin_area/settings/third_party_offers.md
@@ -1,9 +1,26 @@
+---
+type: reference
+---
+
# Third party offers
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20379)
> in [GitLab Core](https://about.gitlab.com/pricing/) 11.1
-Within GitLab, we inform users of available third-party offers they might find valuable in order to enhance the development of their projects.
-An example is the Google Cloud Platform free credit for using [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/).
+Within GitLab, we inform users of available third-party offers they might find valuable in order
+to enhance the development of their projects. An example is the Google Cloud Platform free credit
+for using [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/).
+
+The display of third-party offers can be toggled in the **Admin Area > Settings** page.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-The display of third-party offers can be toggled in the Admin area on the Settings page.
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index 8b5d80efb0d..652d6ad2cdd 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -1,10 +1,14 @@
+---
+type: reference
+---
+
# Usage statistics
GitLab Inc. will periodically collect information about your instance in order
to perform various actions.
All statistics are opt-out, you can enable/disable them from the admin panel
-under **Admin area > Settings > Usage statistics**.
+under **Admin area > Settings > Metrics and profiling > Usage statistics**.
## Version check **[CORE ONLY]**
@@ -83,6 +87,18 @@ of your instance to your users.
This can be restricted to admins by selecting "Only admins" in the Instance
Statistics visibility section under **Admin area > Settings > Usage statistics**.
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[ee-557]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/557
[ee-735]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/735
[ce-23361]: https://gitlab.com/gitlab-org/gitlab-ce/issues/23361
diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md
index 4d1b7d0f252..a1229484388 100644
--- a/doc/user/admin_area/settings/visibility_and_access_controls.md
+++ b/doc/user/admin_area/settings/visibility_and_access_controls.md
@@ -1,8 +1,25 @@
+---
+type: reference
+---
+
# Visibility and access controls
+GitLab allows admins to:
+
+- Control access and visibility to GitLab resources including branches and projects.
+- Select from which hosting sites code can be imported into GitLab.
+- Select the protocols permitted to access GitLab.
+- Enable or disable repository mirroring.
+
+To access the visibility and access control options:
+
+1. Log in to GitLab as an admin.
+1. Go to **Admin Area > Settings > General**.
+1. Expand the **Visibility and access controls** section.
+
## Import sources
-Choose from which hosting sites the users can
+Choose from which hosting sites users can
[import their projects](../../project/import/index.md).
![import sources](img/import_sources.png)
@@ -11,12 +28,10 @@ Choose from which hosting sites the users can
> [Introduced][ce-4696] in GitLab 8.10.
-With GitLab's Access restrictions you can choose which Git access protocols you
-want your users to use to communicate with GitLab. This feature can be enabled
-via the `Application Settings` in the Admin interface.
+With GitLab's access restrictions, you can select with which protocols users can communicate with
+GitLab.
-The setting is called `Enabled Git access protocols`, and it gives you the option
-to choose between:
+From the **Enabled Git access protocols** dropdown, select one of the following:
- Both SSH and HTTP(S)
- Only SSH
@@ -24,10 +39,9 @@ to choose between:
![Settings Overview](img/access_restrictions.png)
-When both SSH and HTTP(S) are enabled, GitLab will behave as usual, it will give
-your users the option to choose which protocol they would like to use.
+When both SSH and HTTP(S) are enabled, your users can choose either protocol.
-When you choose to allow only one of the protocols, a couple of things will happen:
+When only one protocol is enabled:
- The project page will only show the allowed protocol's URL, with no option to
change it.
@@ -54,10 +68,22 @@ application level.
> [Introduced][ee-3586] in GitLab 10.3.
This option is enabled by default. By disabling it, both pull and push mirroring will no longer
-work in every repository and can only be re-enabled on a per-project basis by an admin.
+work in every repository and can only be re-enabled by an admin on a per-project basis.
![Mirror settings](img/mirror_settings.png)
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[ce-4696]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4696
[ce-18021]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18021
[ee-3586]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3586
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 5c635b09503..a24374dff1d 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -12,7 +12,7 @@ two open source tools for Vulnerability Static Analysis for containers.
You can take advantage of Container Scanning by either [including the CI job](#including-the-provided-template) in
your existing `.gitlab-ci.yml` file or by implicitly using
-[Auto Container Scanning](../../../topics/autodevops/index.md#auto-container-scanning)
+[Auto Container Scanning](../../../topics/autodevops/index.md#auto-container-scanning-ultimate)
that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
GitLab checks the Container Scanning report, compares the found vulnerabilities
@@ -119,7 +119,7 @@ container_scanning:
variables:
DOCKER_DRIVER: overlay2
## Define two new variables based on GitLab's CI/CD predefined variables
- ## https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables
+ ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables
CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
CI_APPLICATION_TAG: $CI_COMMIT_SHA
CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
@@ -162,7 +162,7 @@ container_scanning:
variables:
DOCKER_DRIVER: overlay2
## Define two new variables based on GitLab's CI/CD predefined variables
- ## https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables
+ ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables
CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
CI_APPLICATION_TAG: $CI_COMMIT_SHA
CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index abc6e771b0f..028ff72a160 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -39,6 +39,8 @@ However, DAST can be [configured](#full-scan)
to also perform a so-called "active scan". That is, attack your application and produce a more extensive security report.
It can be very useful combined with [Review Apps](../../../ci/review_apps/index.md).
+The [`dast`](https://gitlab.com/gitlab-org/security-products/dast/container_registry) Docker image in GitLab container registry is updated on a weekly basis to have all [`owasp2docker-weekly`](https://hub.docker.com/r/owasp/zap2docker-weekly/) updates in it.
+
## Use cases
It helps you automatically find security vulnerabilities in your running web
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 5d2bb4e572b..d78cf778110 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -224,3 +224,20 @@ 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).
+
+## Dependency List
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075)
+in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+
+An additional benefit of Dependency Scanning is the ability to get a list of your project's dependencies with their versions.
+
+This list can be generated only for [supported languages and package managers](#supported-languages-and-package-managers).
+
+To see the generated dependency list, navigate to the Dependency List page under your project's left sidebar menu **Project > Dependency List**.
+
+## Contributing to the vulnerability database
+
+You can search the [gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) project
+to find a vulnerability in the Gemnasium database.
+You can also [submit new vulnerabilities](https://gitlab.com/gitlab-org/security-products/gemnasium-db/blob/master/CONTRIBUTING.md). \ No newline at end of file
diff --git a/doc/user/application_security/img/dismissed_info.png b/doc/user/application_security/img/dismissed_info.png
new file mode 100644
index 00000000000..64d5cf26ed2
--- /dev/null
+++ b/doc/user/application_security/img/dismissed_info.png
Binary files differ
diff --git a/doc/user/application_security/img/interactive_reports.png b/doc/user/application_security/img/interactive_reports.png
index 9f9812dc69d..373b39104db 100644
--- a/doc/user/application_security/img/interactive_reports.png
+++ b/doc/user/application_security/img/interactive_reports.png
Binary files differ
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 64e72fab198..679847b76d7 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -47,6 +47,16 @@ You can dismiss vulnerabilities by clicking the **Dismiss vulnerability** button
This will dismiss the vulnerability and re-render it to reflect its dismissed state.
If you wish to undo this dismissal, you can click the **Undo dismiss** button.
+#### Adding a dismissal reason
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.0.
+
+When dismissing a vulnerability, it's often helpful to provide a reason for doing so.
+If you press the comment button next to **Dismiss vulnerability** in the modal, a text box will appear, allowing you to add a comment with your dismissal.
+This comment can not currently be edited or removed, but [future versions](https://gitlab.com/gitlab-org/gitlab-ee/issues/11721) will add this functionality.
+
+![Dismissed vulnerability comment](img/dismissed_info.png)
+
### Creating an issue for a vulnerability
You can create an issue for a vulnerability by selecting the **Create issue**
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 02c115b7f22..db328262aba 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -63,7 +63,7 @@ The following table shows which languages, package managers and frameworks are s
| Javascript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8 |
| Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1 |
| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8 |
-| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/openstack/bandit) | 10.3 |
+| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3 |
| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3 |
| Scala ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Ant, Gradle, Maven) |
| Typescript | [TSLint config security](https://github.com/webschik/tslint-config-security/) | 11.9 |
diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
index 3294e59e943..f0dad6c54d0 100644
--- a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
+++ b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index ca31e15c65f..19eeb06a259 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -54,6 +54,7 @@ First, navigate to the Security Dashboard found under your group's
Once you're on the dashboard, at the top you should see a series of filters for:
- Severity
+- Confidence
- Report type
- Project
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
new file mode 100644
index 00000000000..b520c4fb579
--- /dev/null
+++ b/doc/user/clusters/applications.md
@@ -0,0 +1,290 @@
+# GitLab Managed Apps
+
+GitLab provides **GitLab Managed Apps**, a one-click install for various applications which can
+be added directly to your configured cluster. These applications are
+needed for [Review Apps](../../ci/review_apps/index.md) and
+[deployments](../../ci/environments.md) when using [Auto DevOps](../../topics/autodevops/index.md).
+You can install them after you
+[create a cluster](../project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+
+## Installing applications
+
+Applications managed by GitLab will be installed onto the `gitlab-managed-apps` namespace.
+This namespace:
+
+- Is different from the namespace used for project deployments.
+- Is created once.
+- Has a non-configurable name.
+
+To see a list of available applications to install:
+
+1. For a:
+ - Project-level cluster, navigate to your project's **Operations > Kubernetes**.
+ - Group-level cluster, navigate to your group's **Kubernetes** page.
+
+Install Helm first as it's used to install other applications.
+
+NOTE: **Note:**
+As of GitLab 11.6, Helm will be upgraded to the latest version supported
+by GitLab before installing any of the applications.
+
+The following applications can be installed:
+
+- [Helm](#helm)
+- [Ingress](#ingress)
+- [Cert-Manager](#cert-manager)
+- [Prometheus](#prometheus)
+- [GitLab Runner](#gitlab-runner)
+- [JupyterHub](#jupyterhub)
+- [Knative](#knative)
+
+With the exception of Knative, the applications will be installed in a dedicated
+namespace called `gitlab-managed-apps`.
+
+NOTE: **Note:**
+Some applications are installable only for a project-level cluster.
+Support for installing these applications in a group-level cluster is
+planned for future releases.
+For updates, see [the issue tracking
+progress](https://gitlab.com/gitlab-org/gitlab-ce/issues/51989).
+
+CAUTION: **Caution:**
+If you have an existing Kubernetes cluster with Helm already installed,
+you should be careful as GitLab cannot detect it. In this case, installing
+Helm via the applications will result in the cluster having it twice, which
+can lead to confusion during deployments.
+
+### Helm
+
+> - Available for project-level clusters since GitLab 10.2.
+> - Available for group-level clusters since GitLab 11.6.
+
+[Helm](https://docs.helm.sh/) is a package manager for Kubernetes and is
+required to install all the other applications. It is installed in its
+own pod inside the cluster which can run the `helm` CLI in a safe
+environment.
+
+### Cert-Manager
+
+> - Available for project-level clusters since GitLab 11.6.
+> - Available for group-level clusters since GitLab 11.6.
+
+[Cert-Manager](https://docs.cert-manager.io/en/latest/) is a native
+Kubernetes certificate management controller that helps with issuing
+certificates. Installing Cert-Manager on your cluster will issue a
+certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that
+certificates are valid and up-to-date.
+
+NOTE: **Note:**
+The
+[stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/cert_manager/values.yaml)
+file.
+
+### GitLab Runner
+
+> - Available for project-level clusters since GitLab 10.6.
+> - Available for group-level clusters since GitLab 11.10.
+
+[GitLab Runner](https://docs.gitlab.com/runner/) is the open source
+project that is used to run your jobs and send the results back to
+GitLab. It is used in conjunction with [GitLab
+CI/CD](../../ci/README.md), the open-source continuous integration
+service included with GitLab that coordinates the jobs. When installing
+the GitLab Runner via the applications, it will run in **privileged
+mode** by default. Make sure you read the [security
+implications](../project/clusters/index.md/#security-implications) before doing so.
+
+NOTE: **Note:**
+The
+[runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml)
+file.
+
+### Ingress
+
+> - Available for project-level clusters since GitLab 10.2.
+> - Available for group-level clusters since GitLab 11.6.
+
+[Ingress](https://kubernetes.github.io/ingress-nginx/) can provide load
+balancing, SSL termination, and name-based virtual hosting. It acts as a
+web proxy for your applications and is useful if you want to use [Auto
+DevOps] or deploy your own web apps.
+
+NOTE: **Note:**
+The
+[stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/ingress/values.yaml)
+file.
+
+### JupyterHub
+
+> Available for project-level clusters since GitLab 11.0.
+
+[JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a
+multi-user service for managing notebooks across a team. [Jupyter
+Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a
+web-based interactive programming environment used for data analysis,
+visualization, and machine learning.
+
+Authentication will be enabled only for [project
+members](../project/members/index.md) with [Developer or
+higher](../permissions.md) access to the project.
+
+We use a [custom Jupyter
+image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile)
+that installs additional useful packages on top of the base Jupyter. You
+will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix).
+
+More information on
+creating executable runbooks can be found in [our Runbooks
+documentation](../project/clusters/runbooks/index.md#executable-runbooks). Note that
+Ingress must be installed and have an IP address assigned before
+JupyterHub can be installed.
+
+NOTE: **Note:**
+The
+[jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/jupyter/values.yaml)
+file.
+
+#### Jupyter Git Integration
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28783) in GitLab 12 for project-level clusters.
+
+When installing JupyterHub onto your Kubernetes cluster, [JupyterLab's Git extension](https://github.com/jupyterlab/jupyterlab-git)
+is automatically provisioned and configured using the authenticated user's:
+
+- Name
+- Email
+- Newly created access token
+
+JupyterLab's Git extension enables full version control of your notebooks as well as issuance of Git commands within Jupyter.
+Git commands can be issued via the **Git** tab on the left panel or via Jupyter's command line prompt.
+
+NOTE: **Note:**
+JupyterLab's Git extension stores the user token in the JupyterHub DB in encrypted format
+and in the single user Jupyter instance as plain text. This is because [Git requires storing
+credentials as plain text](https://git-scm.com/docs/git-credential-store). Potentially, if
+a nefarious user finds a way to read from the file system in the single user Jupyter instance
+they could retrieve the token.
+
+![Jupyter's Git Extension](img/jupyter-git-extension.gif)
+
+You can clone repositories from the files tab in Jupyter:
+
+![Jupyter clone repository](img/jupyter-gitclone.png)
+
+### Knative
+
+> Available for project-level clusters since GitLab 11.5.
+
+[Knative](https://cloud.google.com/knative) provides a platform to
+create, deploy, and manage serverless workloads from a Kubernetes
+cluster. It is used in conjunction with, and includes
+[Istio](https://istio.io) to provide an external IP address for all
+programs hosted by Knative.
+
+You will be prompted to enter a wildcard
+domain where your applications will be exposed. Configure your DNS
+server to use the external IP address for that domain. For any
+application created and installed, they will be accessible as
+`<program_name>.<kubernetes_namespace>.<domain_name>`. This will require
+your kubernetes cluster to have [RBAC
+enabled](../project/clusters/index.md#rbac-cluster-resources).
+
+NOTE: **Note:**
+The
+[knative/knative](https://storage.googleapis.com/triggermesh-charts)
+chart is used to install this application.
+
+### Prometheus
+
+> - Available for project-level clusters since GitLab 10.4.
+> - Available for group-level clusters since GitLab 11.11.
+
+[Prometheus](https://prometheus.io/docs/introduction/overview/) is an
+open-source monitoring and alerting system useful to supervise your
+deployed applications.
+
+NOTE: **Note:**
+The
+[stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus)
+chart is used to install this application with a
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/prometheus/values.yaml)
+file.
+
+## Upgrading applications
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789)
+in GitLab 11.8.
+
+The applications below can be upgraded.
+
+| Application | GitLab version |
+| ----------- | -------------- |
+| Runner | 11.8+ |
+
+To upgrade an application:
+
+1. For a:
+ - Project-level cluster, navigate to your project's **Operations > Kubernetes**.
+ - Group-level cluster, navigate to your group's **Kubernetes** page.
+1. Select your cluster.
+1. If an upgrade is available, the **Upgrade** button is displayed. Click the button to upgrade.
+
+NOTE: **Note:**
+Upgrades will reset values back to the values built into the `runner`
+chart plus the values set by
+[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml)
+
+## Uninstalling applications
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/60665) in
+> GitLab 11.11.
+
+The applications below can be uninstalled.
+
+| Application | GitLab version | Notes |
+| ----------- | -------------- | ----- |
+| Prometheus | 11.11+ | All data will be deleted and cannot be restored. |
+
+To uninstall an application:
+
+1. For a:
+ - Project-level cluster, navigate to your project's **Operations > Kubernetes**.
+ - Group-level cluster, navigate to your group's **Kubernetes** page.
+1. Select your cluster.
+1. Click the **Uninstall** button for the application.
+
+Support for uninstalling all applications is planned for progressive rollout.
+To follow progress, see [the relevant
+epic](https://gitlab.com/groups/gitlab-org/-/epics/1201).
+
+## Troubleshooting applications
+
+Applications can fail with the following error:
+
+```text
+Error: remote error: tls: bad certificate
+```
+
+To avoid installation errors:
+
+- Before starting the installation of applications, make sure that time is synchronized
+ between your GitLab server and your Kubernetes cluster.
+- Ensure certificates are not out of sync. When installing applications, GitLab expects a new cluster with no previous installation of Helm.
+
+ You can confirm that the certificates match via `kubectl`:
+
+ ```sh
+ kubectl get configmaps/values-content-configuration-ingress -n gitlab-managed-apps -o \
+ "jsonpath={.data['cert\.pem']}" | base64 -d > a.pem
+ 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/clusters/img/jupyter-git-extension.gif b/doc/user/clusters/img/jupyter-git-extension.gif
new file mode 100644
index 00000000000..13a88d97425
--- /dev/null
+++ b/doc/user/clusters/img/jupyter-git-extension.gif
Binary files differ
diff --git a/doc/user/clusters/img/jupyter-gitclone.png b/doc/user/clusters/img/jupyter-gitclone.png
new file mode 100644
index 00000000000..41d467f806a
--- /dev/null
+++ b/doc/user/clusters/img/jupyter-gitclone.png
Binary files differ
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index ff6aa4f5930..3c5e820c1ca 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Group-level Kubernetes clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) in GitLab 11.6.
@@ -12,33 +16,10 @@ your group, enabling you to use the same cluster across multiple projects.
## Installing applications
-GitLab provides a one-click install for various applications that can be
-added directly to your cluster.
-
-NOTE: **Note:**
-Applications will be installed in a dedicated namespace called
-`gitlab-managed-apps`. If you have added an existing Kubernetes cluster
-with Tiller already installed, you should be careful as GitLab cannot
-detect it. In this event, installing Tiller via the applications will
-result in the cluster having it twice. This can lead to confusion during
-deployments.
-
-| Application | GitLab version | Description | Helm Chart |
-| ----------- | -------------- | ----------- | ---------- |
-| [Helm Tiller](https://docs.helm.sh) | 11.6+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
-| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress) | 11.6+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
-| [Cert-Manager](https://docs.cert-manager.io/en/latest/) | 11.6+ | Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
-| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 11.11+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
-| [GitLab Runner](https://docs.gitlab.com/runner/) | 11.10+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](../../../ci/README.md), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](../../project/clusters/index.md#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
-
-NOTE: **Note:**
-Some [cluster
-applications](../../project/clusters/index.md#installing-applications)
-are installable only for a project-level cluster. Support for installing these
-applications in a group-level cluster is planned for future releases. For updates, see:
-
-- Support installing [JupyterHub in group-level
- clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/51989)
+GitLab can install and manage some applications in your group-level
+cluster. For more information on installing, upgrading, uninstalling,
+and troubleshooting applications for your group cluster, see
+[Gitlab Managed Apps](../../clusters/applications.md).
## RBAC compatibility
@@ -71,7 +52,7 @@ Add another cluster similar to the first one and make sure to
[set an environment scope](#environment-scopes-premium) that will
differentiate the new cluster from the rest.
-## Gitlab-managed clusters
+## GitLab-managed clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011) in GitLab 11.5.
> Became [optional](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26565) in GitLab 11.11.
@@ -110,7 +91,7 @@ The domain should have a wildcard DNS configured to the Ingress IP address.
When adding more than one Kubernetes cluster to your project, you need to differentiate
them with an environment scope. The environment scope associates clusters with
[environments](../../../ci/environments.md) similar to how the
-[environment-specific variables](https://docs.gitlab.com/ee/ci/variables/#limiting-environment-scopes-of-environment-variables-premium)
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium)
work.
While evaluating which environment matches the environment scope of a
@@ -168,3 +149,15 @@ The following features are not currently available for group-level clusters:
1. Terminals (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55487)).
1. Pod logs (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)).
1. Deployment boards (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55489)).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/contribution_analytics/index.md b/doc/user/group/contribution_analytics/index.md
index bc88eff9ed2..7e6cb24a51e 100644
--- a/doc/user/group/contribution_analytics/index.md
+++ b/doc/user/group/contribution_analytics/index.md
@@ -1,46 +1,58 @@
-# Contribution Analytics **[STARTER]**
+---
+type: reference
+---
->**Note:**
-Introduced in [GitLab Starter][ee] 8.3.
+# Contribution Analytics **[STARTER]**
-Track your team members' activity across your organization.
+> Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
## Overview
-With Contribution Analytics you can get an overview of the activity of
-issues, merge requests, and push events of your organization and its members.
+With Contribution Analytics you can get an overview of the following activity in your
+group:
-The analytics page is located at **Group > Contribution Analytics**
-under the URL `/groups/<groupname>/analytics`.
+- Issues
+- Merge requests
+- Push events
+
+To view the Contribution Analytics, go to your group's **Overview > Contribution Analytics**
+page.
## Use cases
-- Analyze your team's contributions over a period of time and offer a bonus for the top contributors
-- Identify opportunities for improvement with group members who may benefit from additional support
+- Analyze your team's contributions over a period of time, and offer a bonus for the top
+contributors.
+- Identify opportunities for improvement with group members who may benefit from additional
+support.
## Using Contribution Analytics
-There are three main bar graphs that are deducted from the number of
-contributions per group member. These contributions include push events, merge
-requests and closed issues. Hovering on each bar you can see the number of
-events for a specific member.
+There are three main bar graphs that illustrate the number of contributions per group
+member for the following:
+
+- Push events
+- Merge requests
+- Closed issues
+
+Hover over each bar to display the number of events for a specific group member.
![Contribution analytics bar graphs](img/group_stats_graph.png)
## Changing the period time
-There are three periods you can choose from, 'Last week', 'Last month' and
-'Last three months'. The default is 'Last week'.
+You can choose from the following three periods:
+
+- Last week (default)
+- Last month
+- Last three months
-You can choose which period to display by using the dropdown calendar menu in
-the upper right corner.
+Select the desired period from the calendar dropdown.
![Contribution analytics choose period](img/group_stats_cal.png)
## Sorting by different factors
-Apart from the bar graphs you can also see the contributions per group member
-which are depicted in a table that can be sorted by:
+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
@@ -52,4 +64,14 @@ which are depicted in a table that can be sorted by:
![Contribution analytics contributions table](img/group_stats_table.png)
-[ee]: https://about.gitlab.com/pricing/
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/custom_project_templates.md b/doc/user/group/custom_project_templates.md
index f67325272a6..aa088d2fcdb 100644
--- a/doc/user/group/custom_project_templates.md
+++ b/doc/user/group/custom_project_templates.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Custom group-level project templates **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6861) in [GitLab Premium](https://about.gitlab.com/pricing) 11.6.
@@ -24,3 +28,15 @@ Projects of nested subgroups of a selected template source cannot be used.
Repository and database information that are copied over to each new project are
identical to the data exported with [GitLab's Project Import/Export](../project/settings/import_export.md).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index 6035f0c7326..2e4106f55e5 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Epics **[ULTIMATE]**
> Introduced in [GitLab Ultimate][ee] 10.2.
@@ -215,3 +219,15 @@ Once you wrote your comment, you can either:
## Notifications
- [Receive notifications](../../../workflow/notifications.md) for epic events.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 7493e65e237..b123a2b917b 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -1,7 +1,15 @@
+---
+type: reference, howto
+---
+
# Groups
-With GitLab Groups you can assemble related projects together
-and grant members access to several projects at once.
+With GitLab Groups, you can:
+
+- Assemble related projects together.
+- Grant members access to several projects at once.
+
+For a video introduction to GitLab Groups, see [GitLab University: Repositories, Projects and Groups](https://www.youtube.com/watch?v=4TWfh1aKHHw).
Groups can also be nested in [subgroups](subgroups/index.md).
@@ -9,17 +17,21 @@ Find your groups by clicking **Groups > Your Groups** in the top navigation.
![GitLab Groups](img/groups.png)
-> The groups dropdown in the top navigation was [introduced][ce-36234] in [GitLab 11.1](https://about.gitlab.com/2018/07/22/gitlab-11-1-released/#groups-dropdown-in-navigation).
+> The **Groups** dropdown in the top navigation was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/36234) in [GitLab 11.1](https://about.gitlab.com/2018/07/22/gitlab-11-1-released/#groups-dropdown-in-navigation).
-The Groups page displays:
+The **Groups** page displays:
-- All groups you are a member of.
-- How many projects each group contains.
-- How many members a group has.
-- The group visibility.
-- A link to the group settings if you have sufficient permissions.
+- All groups you are a member of, when **Your groups** is selected.
+- A list of public groups, when **Explore public groups** is selected.
-By clicking the last button, you can leave that group.
+Each group on the **Groups** page is listed with:
+
+- How many subgroups it has.
+- How many projects it contains.
+- How many members the group has, not including members inherited from parent groups.
+- The group's visibility.
+- A link to the group's settings, if you have sufficient permissions.
+- A link to leave the group, if you are a member.
## Use cases
@@ -101,6 +113,8 @@ Add the following information:
1. Optionally, choose an avatar for your group.
1. Choose the [visibility level](../../public_access/public_access.md).
+For more details on creating groups, watch the video [GitLab Namespaces (users, groups and subgroups)](https://youtu.be/r0sJgjR2f5A).
+
## Add users to a group
A benefit of putting multiple projects in one group is that you can
@@ -203,10 +217,8 @@ Get an overview of the vulnerabilities of all the projects in a group and its su
## Insights **[ULTIMATE]**
-> Introduced in [GitLab Ultimate][ee] 11.9 behind the `insights` feature flag.
-
-Configure the Insights that matter for your groups or projects, allowing users to explore data
-such as:
+Configure the Insights that matter for your groups or projects, allowing users
+to explore data such as:
- Triage hygiene
- Issues created/closed per a given period
@@ -296,7 +308,7 @@ To enable this feature, navigate to the group settings page. Select
Member lock lets a group owner prevent any new project membership to all of the
projects within a group, allowing tighter control over project membership.
-For example, if you want to lock the group for an [Audit Event](https://docs.gitlab.com/ee/administration/audit_events.html),
+For example, if you want to lock the group for an [Audit Event](../../administration/audit_events.md),
enable Member lock to guarantee that project membership cannot be modified during that audit.
To enable this feature:
@@ -315,7 +327,7 @@ request to add a new user to a project through API will not be possible.
Group file templates allow you to share a set of templates for common file
types with every project in a group. It is analogous to the
-[instance template repository](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html)
+[instance template repository](../admin_area/settings/instance_template_repository.md)
feature, and the selected project should follow the same naming conventions as
are documented on that page.
@@ -346,7 +358,7 @@ Define project templates at a group level by setting a group as the template sou
access each project's settings, and remove any project, all from the same screen.
- **Webhooks**: Configure [webhooks](../project/integrations/webhooks.md) for your group.
- **Kubernetes cluster integration**: Connect your GitLab group with [Kubernetes clusters](clusters/index.md).
-- **Audit Events**: View [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html)
+- **Audit Events**: View [Audit Events](../../administration/audit_events.md)
for the group. **[STARTER ONLY]**
- **Pipelines quota**: Keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group.
@@ -364,5 +376,17 @@ With [GitLab Issues Analytics](issues_analytics/index.md), you can see a bar cha
Use GitLab as a [dependency proxy](dependency_proxy/index.md) for upstream Docker images.
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
[ee]: https://about.gitlab.com/pricing/
[ee-2534]: https://gitlab.com/gitlab-org/gitlab-ee/issues/2534
diff --git a/doc/user/group/insights/index.md b/doc/user/group/insights/index.md
index 20f76c54ae7..1aba5d0986b 100644
--- a/doc/user/group/insights/index.md
+++ b/doc/user/group/insights/index.md
@@ -1,11 +1,11 @@
+---
+type: reference, howto
+---
+
# Insights **[ULTIMATE]**
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.9 behind the `insights` feature flag.
-
-CAUTION: **Beta:**
-Insights is considered beta, and is not ready for production use.
-Follow [gitlab-org/quality/team-tasks#137](https://gitlab.com/gitlab-org/quality/team-tasks/issues/137#general-availability)
-for updates.
+> **Generally Available** (GA) 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
@@ -28,10 +28,10 @@ the project that holds your `.gitlab/insights.yml` configuration file:
![group insights configuration](img/insights_group_configuration.png)
If no configuration was set, a [default configuration file](
-https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/fixtures/insights/ee/fixtures/insights/default.yml)
+https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/fixtures/insights/default.yml)
will be used.
-See the [Project's Insights documentation](https://docs.gitlab.com/ee/user/project/insights/index.html) for
+See the [Project's Insights documentation](../../project/insights/index.md) for
more details about the `.gitlab/insights.yml` configuration file.
## Permissions
@@ -44,3 +44,15 @@ access to the project they belong to, or because they are confidential) are
filtered out of the Insights charts.
You may also consult the [group permissions table](../../permissions.md#group-members-permissions).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/issues_analytics/index.md b/doc/user/group/issues_analytics/index.md
index cf53d7423a6..46d5c1e2e09 100644
--- a/doc/user/group/issues_analytics/index.md
+++ b/doc/user/group/issues_analytics/index.md
@@ -1,16 +1,43 @@
+---
+type: reference
+---
+
# Issues Analytics **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7478) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.5.
-GitLab by default displays a bar chart of the number of issues created each month, for the
-current month, and 12 months prior, for a total of 13 months.
+Issues Analytics is a bar graph which illustrates the number of issues created each month.
+The default timespan is 13 months, which includes the current month, and the 12 months
+prior.
-You can change the total number of months displayed by setting a URL parameter.
-For example, `https://gitlab.com/groups/gitlab-org/-/issues_analytics?months_back=15`
-would show a total of 15 months for the chart in the GitLab.org group.
+To access the chart, navigate to a group's sidebar and select **Issues > Analytics**.
-The **Search or filter results...** field can be used for filtering the issues by any attribute. For example, labels, assignee, milestone, and author.
+Hover over each bar to see the total number of issues.
-To access the chart, navigate to a group's sidebar and select **Issues > Analytics**.
+To narrow the scope of issues included in the graph, enter your criteria in the
+**Search or filter results...** field. Criteria from the following list can be typed in or selected from a menu:
+
+- Author
+- Assignee
+- Milestone
+- Label
+- My reaction
+- Weight
+
+You can change the total number of months displayed by setting a URL parameter.
+For example, `https://gitlab.com/groups/gitlab-org/-/issues_analytics?months_back=15`
+shows a total of 15 months for the chart in the GitLab.org group.
![Issues created per month](img/issues_created_per_month.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/roadmap/index.md b/doc/user/group/roadmap/index.md
index 310c4bb88d0..683c715c8d5 100644
--- a/doc/user/group/roadmap/index.md
+++ b/doc/user/group/roadmap/index.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Roadmap **[ULTIMATE]**
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.5.
@@ -19,13 +23,20 @@ Epics in the view can be sorted by:
- **Start date**
- **Due date**
-Each option contains a button that can toggle the order between **ascending** and **descending**. The sort option and order will be persisted to be used wherever epics are browsed including the [epics list view](../epics/index.md).
+Each option contains a button that toggles the sort order between **ascending** and **descending**. The sort option and order will be persisted when browsing Epics,
+including the [epics list view](../epics/index.md).
Roadmaps can also be [visualized inside an epic](../epics/index.md#roadmap-in-epics).
## Timeline duration
-Starting with [GitLab Ultimate][ee] 11.0, Roadmap supports three different date ranges; Quarters, Months (Default) and Weeks.
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.0.
+
+Roadmap supports the following date ranges:
+
+- Quarters
+- Months (Default)
+- Weeks
### Quarters
@@ -62,3 +73,15 @@ and due date. If an epic doesn't have a due date, the timeline bar fades
away towards the future. Similarly, if an epic doesn't have a start date, the
timeline bar becomes more visible as it approaches the epic's due date on the
timeline.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 53116606201..fcfd638f185 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# SAML SSO for GitLab.com Groups **[SILVER ONLY]**
> Introduced in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.0.
@@ -5,17 +9,17 @@
NOTE: **Note:**
This topic is for SAML on GitLab.com Silver tier and above. For SAML on self-managed GitLab instances, see [SAML OmniAuth Provider](../../../integration/saml.md).
-Currently SAML on GitLab.com can be used to automatically add users to a group, and does not yet sign users into GitLab.com. Users should already have an account on the GitLab instance, or can create one when logging in for the first time.
+SAML on GitLab.com allows users to be automatically added to a group, and then allows those users to sign into GitLab.com. Users should already have an account on the GitLab instance, or can create one when logging in for the first time.
User synchronization for GitLab.com is partially supported using [SCIM](scim_setup.md).
NOTE: **Note:**
-SAML SSO for groups is used only as a convenient way to add users and does not sync users between providers without using SCIM. If a group is not using SCIM, group Owners will still need to manage user accounts, such as removing users when necessary.
+SAML SSO for GitLab.com groups does not sync users between providers without using SCIM. If a group is not using SCIM, group Owners will still need to manage user accounts (for example, removing users when necessary).
## Configuring your Identity Provider
1. Navigate to the group and click **Settings > SAML SSO**.
-1. Configure your SAML server using the **Assertion consumer service URL** and **Issuer**. See [your identity provider's documentation](#providers) for more details.
+1. Configure your SAML server using the **Assertion consumer service URL** and **Identifier**. Alternatively GitLab provides [metadata XML configuration](#metadata-configuration). See [your identity provider's documentation](#providers) for more details.
1. Configure the SAML response to include a NameID that uniquely identifies each user.
1. Configure required assertions using the [table below](#assertions).
1. Once the identity provider is set up, move on to [configuring GitLab](#configuring-gitlab).
@@ -43,12 +47,20 @@ GitLab.com uses the SAML NameID to identify users. The NameID element:
### Assertions
-| Field | Supported keys | Notes |
-|-|----------------|-------------|
-| Email | `email`, `mail` | (required) |
-| Full Name | `name` | |
-| First Name | `first_name`, `firstname`, `firstName` | |
-| Last Name | `last_name`, `lastname`, `lastName` | |
+| Field | Supported keys |
+|-------|----------------|
+| Email (required)| `email`, `mail` |
+| Full Name | `name` |
+| First Name | `first_name`, `firstname`, `firstName` |
+| Last Name | `last_name`, `lastname`, `lastName` |
+
+## Metadata configuration
+
+GitLab provides metadata XML that can be used to configure your Identity Provider.
+
+1. Navigate to the group and click **Settings > SAML SSO**.
+1. Copy the provided **GitLab metadata URL**
+1. Follow your Identity Provider's documentation and paste the metadata URL when it is requested.
## Configuring GitLab
@@ -75,6 +87,23 @@ Once you've set up your identity provider to work with GitLab, you'll need to co
| OneLogin | [Use the OneLogin SAML Test Connector](https://onelogin.service-now.com/support?id=kb_article&sys_id=93f95543db109700d5505eea4b96198f) |
| Ping Identity | [Add and configure a new SAML application](https://docs.pingidentity.com/bundle/p1_enterpriseConfigSsoSaml_cas/page/enableAppWithoutURL.html) |
+## Linking SAML to your existing GitLab.com account
+
+To link SAML to your existing GitLab.com account:
+
+1. Sign in to your GitLab.com account.
+1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page.
+1. Visit the SSO URL and click **Authorize**.
+1. Enter your credentials on the Identity Provider if prompted.
+1. You will be redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com.
+
+## Signing in to GitLab.com with SAML
+
+1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on a group's **Settings > SAML SSO** page. If configured, it might also be possible to sign in to GitLab starting from your Identity Provider.
+1. Visit the SSO URL and click the **Sign in with Single Sign-On** button.
+1. Enter your credentials on the Identity Provider if prompted.
+1. You will be signed in to GitLab.com and redirected to the group.
+
## Unlinking accounts
Users can unlink SAML for a group from their profile page. This can be helpful if:
@@ -97,3 +126,15 @@ For example, to unlink the `MyOrg` account, the following **Disconnect** button
| Assertion consumer service URL | The callback on GitLab where users will be redirected after successfully authenticating with the identity provider. |
| Issuer | How GitLab identifies itself to the identity provider. Also known as a "Relying party trust identifier". |
| Certificate fingerprint | Used to confirm that communications over SAML are secure by checking that the server is signing communications with the correct certificate. Also known as a certificate thumbprint. |
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md
index ec27ec3e336..96cc523f4ec 100644
--- a/doc/user/group/saml_sso/scim_setup.md
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -1,8 +1,16 @@
+---
+type: howto, reference
+---
+
# SCIM provisioning using SAML SSO for Groups **[SILVER ONLY]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9388) in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.10.
-GitLab's [SCIM API](https://docs.gitlab.com/ee/api/scim.html) implements part of [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
+System for Cross-domain Identity Management (SCIM), is an open standard that enables the
+automation of user provisioning. When SCIM is provisioned for a GitLab group, membership of
+that group is synchronized between GitLab and the identity provider.
+
+GitLab's [SCIM API](../../../api/scim.md) implements part of [the RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
Currently, the following actions are available:
@@ -100,3 +108,15 @@ the `Provisioning Status` to `ON`.
Once enabled, the synchronization details and any errors will appear on the
bottom of the **Provisioning** screen, together with a link to the audit logs.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/group/security_dashboard/index.md b/doc/user/group/security_dashboard/index.md
index 43e910b29fe..c59198df081 100644
--- a/doc/user/group/security_dashboard/index.md
+++ b/doc/user/group/security_dashboard/index.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html'
+redirect_to: '../../application_security/security_dashboard/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html).
+This document was moved to [another location](../../application_security/security_dashboard/index.md).
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 4e81e28a45a..79ae94dd9ef 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -1,11 +1,17 @@
+---
+type: reference, howto, concepts
+---
+
# Subgroups
NOTE: **Note:**
[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2772) in GitLab 9.0. Not available when using MySQL as external
database (support removed in GitLab 9.3 [due to performance reasons](https://gitlab.com/gitlab-org/gitlab-ce/issues/30472#note_27747600)).
-With subgroups (aka nested groups or hierarchical groups) you can have
-up to 20 levels of nested groups, which among other things can help you to:
+Subgroups, also known as nested groups or hierarchical groups, allow you to have up to 20
+levels of groups.
+
+By using subgroups you can do the following:
- **Separate internal / external organizations.** Since every group
can have its own visibility level, you are able to host groups for different
@@ -17,8 +23,9 @@ up to 20 levels of nested groups, which among other things can help you to:
## Database Requirements
-Nested groups are only supported when you use PostgreSQL. Supporting nested
-groups on MySQL in an efficient way is not possible due to MySQL's limitations.
+Subgroups are only supported when you use PostgreSQL. Supporting subgroups on MySQL in an
+efficient way is not possible due to MySQL's limitations.
+
See the following links for more information:
- <https://gitlab.com/gitlab-org/gitlab-ce/issues/30472>
@@ -37,7 +44,7 @@ only 1 parent group. It resembles a directory behavior or a nested items list:
- Group 1.2.2.1
In a real world example, imagine maintaining a GNU/Linux distribution with the
-first group being the name of the distro and subsequent groups split like:
+first group being the name of the distribution, and subsequent groups split as follows:
- Organization Group - GNU/Linux distro
- Category Subgroup - Packages
@@ -69,22 +76,22 @@ Another example of GitLab as a company would be the following:
---
-The maximum nested groups a group can have, including the first one in the
+The maximum subgroups a group can have, including the first one in the
hierarchy, is 21.
-Things like transferring or importing a project inside nested groups, work like
+Actions such as transferring or importing a project inside subgroups, work like
when performing these actions the traditional way with the `group/project`
structure.
## Creating a subgroup
NOTE: **Note:**
-You need to be an Owner of a group in order to be able to create a subgroup. For
+You must be an Owner of a group to create a subgroup. For
more information check the [permissions table](../../permissions.md#group-members-permissions).
For a list of words that are not allowed to be used as group names see the
[reserved names](../../reserved_names.md).
Users can always create subgroups if they are explicitly added as an Owner to
-a parent group even if group creation is disabled by an administrator in their
+a parent group, even if group creation is disabled by an administrator in their
settings.
To create a subgroup:
@@ -111,7 +118,7 @@ When you add a member to a subgroup, they inherit the membership and permission
level from the parent group. This model allows access to nested groups if you
have membership in one of its parents.
-The group permissions for a member can be changed only by Owners and only on
+The group permissions for a member can be changed only by Owners, and only on
the **Members** page of the group the member was added.
You can tell if a member has inherited the permissions from a parent group by
@@ -119,24 +126,24 @@ looking at the group's **Members** page.
![Group members page](img/group_members.png)
-From the image above, we can deduct the following things:
+From the image above, we can deduce the following things:
-- There are 5 members that have access to the group `four`
+- There are 5 members that have access to the group `four`.
- User0 is a Reporter and has inherited their permissions from group `one`
- which is above the hierarchy of group `four`
+ which is above the hierarchy of group `four`.
- User1 is a Developer and has inherited their permissions from group
- `one/two` which is above the hierarchy of group `four`
+ `one/two` which is above the hierarchy of group `four`.
- User2 is a Developer and has inherited their permissions from group
- `one/two/three` which is above the hierarchy of group `four`
+ `one/two/three` which is above the hierarchy of group `four`.
- For User3 there is no indication of a parent group, therefore they belong to
- group `four`, the one we're inspecting
+ group `four`, the one we're inspecting.
- Administrator is the Owner and member of **all** subgroups and for that reason,
- same as User3, there is no indication of an ancestor group
+ as with User3, there is no indication of an ancestor group.
### Overriding the ancestor group membership
NOTE: **Note:**
-You need to be an Owner of a group in order to be able to add members to it.
+You must be an Owner of a group to be able to add members to it.
NOTE: **Note:**
A user's permissions in a subgroup cannot be lower than in any of its ancestor groups.
@@ -154,7 +161,7 @@ the permissions will fallback to those of the ancestor group.
## Mentioning subgroups
Mentioning groups (`@group`) in issues, commits and merge requests, would
-notify all members of that group. Now with subgroups, there is a more granular
+notify all members of that group. Now with subgroups, there is more granular
support if you want to split your group's structure. Mentioning works as before
and you can choose the group of people to be notified.
@@ -179,3 +186,15 @@ Here's a list of what you can't do with subgroups:
[permissions]: ../../permissions.md#group-members-permissions
[reserved]: ../../reserved_names.md
[issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30472#note_27747600
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/img/markdown_inline_diffs_tags_rendered.png b/doc/user/img/markdown_inline_diffs_tags_rendered.png
new file mode 100644
index 00000000000..4279a20b5a0
--- /dev/null
+++ b/doc/user/img/markdown_inline_diffs_tags_rendered.png
Binary files differ
diff --git a/doc/user/index.md b/doc/user/index.md
index 67d5cbf06ec..1fc4e4c43cf 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -49,22 +49,22 @@ GitLab is a Git-based platform that integrates a great number of essential tools
With GitLab Enterprise Edition, you can also:
-- Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html).
+- Provide support with [Service Desk](project/service_desk.md).
- Improve collaboration with
- [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals-starter),
- [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
+ [Merge Request Approvals](project/merge_requests/index.md#merge-request-approvals-starter),
+ [Multiple Assignees for Issues](project/issues/multiple_assignees_for_issues.md),
and [Multiple Issue Boards](project/issue_board.md#multiple-issue-boards-starter).
-- Create formal relationships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html).
-- Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software.
-- Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](search/advanced_global_search.md) and [Advanced Syntax Search](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance.
-- [Authenticate users with Kerberos](https://docs.gitlab.com/ee/integration/kerberos.html).
+- Create formal relationships between issues with [Related Issues](project/issues/related_issues.md).
+- Use [Burndown Charts](project/milestones/burndown_charts.md) to track progress during a sprint or while working on a new version of their software.
+- Leverage [Elasticsearch](../integration/elasticsearch.md) with [Advanced Global Search](search/advanced_global_search.md) and [Advanced Syntax Search](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance.
+- [Authenticate users with Kerberos](../integration/kerberos.md).
- [Mirror a repository](../workflow/repository_mirroring.md) from elsewhere on your local server.
-- [Export issues as CSV](https://docs.gitlab.com/ee/user/project/issues/csv_export.html).
-- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html).
-- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts.
-- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html).
-- Leverage continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html).
-- Scan your code for vulnerabilities and [display them in merge requests](https://docs.gitlab.com/ee/user/application_security/sast/index.html).
+- [Export issues as CSV](project/issues/csv_export.md).
+- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](../ci/multi_project_pipeline_graphs.md).
+- [Lock files](project/file_lock.md) to prevent conflicts.
+- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](project/deploy_boards.md).
+- 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.
diff --git a/doc/user/instance_statistics/convdev.md b/doc/user/instance_statistics/convdev.md
index 2c9e0ecbf65..d486ecc39a4 100644
--- a/doc/user/instance_statistics/convdev.md
+++ b/doc/user/instance_statistics/convdev.md
@@ -5,7 +5,7 @@
NOTE: **Note:**
Your GitLab instance's [usage ping](../admin_area/settings/usage_statistics.md#usage-ping-core-only) must be activated in order to use this feature.
-The Conversational Development Index (ConvDev Index) gives you an overview of your entire
+The [Conversational Development](http://conversationaldevelopment.com/2017/04/16/what-is-conversational-development/) Index (ConvDev Index) gives you an overview of your entire
instance's adoption of [Concurrent DevOps](https://about.gitlab.com/concurrent-devops/)
from planning to monitoring.
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 5dad9621802..31c8093ced7 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -246,7 +246,7 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#inline-
With inline diffs tags you can display {+ additions +} or [- deletions -].
-The wrapping tags can be either curly braces or square brackets: [+ additions +] or {- deletions -}.
+The wrapping tags can be either curly braces or square brackets.
Examples:
@@ -257,6 +257,10 @@ Examples:
- [- deletions -]
```
+becomes:
+
+![inline diffs tags rendered](img/markdown_inline_diffs_tags_rendered.png)
+
However the wrapping tags cannot be mixed as such:
```
@@ -1027,7 +1031,7 @@ A link can be constructed relative to the current wiki page using `./<page>`,
it would link to `<your_wiki>/documentation/related`:
```markdown
- [Link to Related Page](./related)
+ [Link to Related Page](related)
```
- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`,
@@ -1041,7 +1045,7 @@ A link can be constructed relative to the current wiki page using `./<page>`,
it would link to `<your_wiki>/documentation/related.md`:
```markdown
- [Link to Related Page](./related.md)
+ [Link to Related Page](related.md)
```
- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`,
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 318053fdabb..a6e2f187090 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -36,91 +36,96 @@ In GitLab 11.0, the Master role was renamed to Maintainer.
The following table depicts the various user permission levels in a project.
-| Action | Guest | Reporter | Developer |Maintainer| Owner |
-|---------------------------------------|---------|------------|-------------|----------|--------|
-| Create new issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Create confidential issue | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View confidential issues | (✓) [^2] | ✓ | ✓ | ✓ | ✓ |
-| Leave comments | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| See related issues | ✓ | ✓ | ✓ | ✓ | ✓ |
-| See a list of jobs | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
-| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
-| Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
-| View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Create and edit wiki pages | | | ✓ | ✓ | ✓ |
-| Delete wiki pages | | | | ✓ | ✓ |
-| View license management reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View Security reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| View project code | [^1] | ✓ | ✓ | ✓ | ✓ |
-| Pull project code | [^1] | ✓ | ✓ | ✓ | ✓ |
-| Download project | [^1] | ✓ | ✓ | ✓ | ✓ |
-| Assign issues | | ✓ | ✓ | ✓ | ✓ |
-| Assign merge requests | | | ✓ | ✓ | ✓ |
-| Label issues | | ✓ | ✓ | ✓ | ✓ |
-| Label merge requests | | | ✓ | ✓ | ✓ |
-| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
-| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
-| Manage labels | | ✓ | ✓ | ✓ | ✓ |
-| See a commit status | | ✓ | ✓ | ✓ | ✓ |
-| See a container registry | | ✓ | ✓ | ✓ | ✓ |
-| See environments | | ✓ | ✓ | ✓ | ✓ |
-| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ |
-| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ |
-| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
-| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
-| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
-| Pull from [Maven repository](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html) or [NPM registry](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html) **[PREMIUM]** | | ✓ | ✓ | ✓ | ✓ |
-| Publish to [Maven repository](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html) or [NPM registry](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html) **[PREMIUM]** | | | ✓ | ✓ | ✓ |
-| Lock merge request discussions | | | ✓ | ✓ | ✓ |
-| Create new environments | | | ✓ | ✓ | ✓ |
-| Stop environments | | | ✓ | ✓ | ✓ |
-| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
-| Create new merge request | | | ✓ | ✓ | ✓ |
-| Create new branches | | | ✓ | ✓ | ✓ |
-| Push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Remove non-protected branches | | | ✓ | ✓ | ✓ |
-| Add tags | | | ✓ | ✓ | ✓ |
-| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
-| Create or update commit status | | | ✓ | ✓ | ✓ |
-| Update a container registry | | | ✓ | ✓ | ✓ |
-| Remove a container registry image | | | ✓ | ✓ | ✓ |
-| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
+| Action | Guest | Reporter | Developer |Maintainer| Owner |
+|---------------------------------------------------|---------|------------|-------------|----------|--------|
+| Download project | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| Leave comments | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
| View approved/blacklisted licenses **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
-| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
-| Apply code change suggestions | | | ✓ | ✓ | ✓ |
-| Use environment terminals | | | | ✓ | ✓ |
-| Run Web IDE's Interactive Web Terminals **[ULTIMATE ONLY]** | | | | ✓ | ✓ |
-| Add new team members | | | | ✓ | ✓ |
-| Push to protected branches | | | | ✓ | ✓ |
-| Enable/disable branch protection | | | | ✓ | ✓ |
-| Turn on/off protected branch push for devs| | | | ✓ | ✓ |
-| Enable/disable tag protections | | | | ✓ | ✓ |
-| Rewrite/remove Git tags | | | | ✓ | ✓ |
-| Edit project | | | | ✓ | ✓ |
-| Add deploy keys to project | | | | ✓ | ✓ |
-| Configure project hooks | | | | ✓ | ✓ |
-| Manage Runners | | | | ✓ | ✓ |
-| Manage job triggers | | | | ✓ | ✓ |
-| Manage variables | | | | ✓ | ✓ |
-| Manage GitLab Pages | | | | ✓ | ✓ |
-| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
-| Remove GitLab Pages | | | | ✓ | ✓ |
+| View license management reports **[ULTIMATE]** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View Security reports **[ULTIMATE]** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core-only) | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Manage clusters | | | | ✓ | ✓ |
-| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ |
-| Edit comments (posted by any user) | | | | ✓ | ✓ |
-| Manage Error Tracking | | | | ✓ | ✓ |
-| Switch visibility level | | | | | ✓ |
-| Transfer project to another namespace | | | | | ✓ |
-| Remove project | | | | | ✓ |
-| Delete issues | | | | | ✓ |
-| Force push to protected branches [^4] | | | | | |
-| Remove protected branches [^4] | | | | | |
-| View project Audit Events | | | | ✓ | ✓ |
-| View project statistics | | ✓ | ✓ | ✓ | ✓ |
-| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View wiki pages | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| See a list of jobs | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
+| See a job log | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
+| Download and browse job artifacts | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
+| Create new issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| See related issues | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Create confidential issue | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View confidential issues | (*2*) | ✓ | ✓ | ✓ | ✓ |
+| Assign issues | | ✓ | ✓ | ✓ | ✓ |
+| Label issues | | ✓ | ✓ | ✓ | ✓ |
+| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
+| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
+| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ |
+| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
+| Manage labels | | ✓ | ✓ | ✓ | ✓ |
+| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
+| See a commit status | | ✓ | ✓ | ✓ | ✓ |
+| See a container registry | | ✓ | ✓ | ✓ | ✓ |
+| See environments | | ✓ | ✓ | ✓ | ✓ |
+| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ |
+| View project statistics | | ✓ | ✓ | ✓ | ✓ |
+| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
+| Pull from [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **[PREMIUM]** | | ✓ | ✓ | ✓ | ✓ |
+| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **[PREMIUM]** | | | ✓ | ✓ | ✓ ||
+| Create new branches | | | ✓ | ✓ | ✓ |
+| Push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Remove non-protected branches | | | ✓ | ✓ | ✓ |
+| Create new merge request | | | ✓ | ✓ | ✓ |
+| Assign merge requests | | | ✓ | ✓ | ✓ |
+| Label merge requests | | | ✓ | ✓ | ✓ |
+| Lock merge request discussions | | | ✓ | ✓ | ✓ |
+| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
+| Create new environments | | | ✓ | ✓ | ✓ |
+| Stop environments | | | ✓ | ✓ | ✓ |
+| Add tags | | | ✓ | ✓ | ✓ |
+| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
+| Create or update commit status | | | ✓ | ✓ | ✓ |
+| Update a container registry | | | ✓ | ✓ | ✓ |
+| Remove a container registry image | | | ✓ | ✓ | ✓ |
+| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
+| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
+| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
+| Apply code change suggestions | | | ✓ | ✓ | ✓ |
+| Create and edit wiki pages | | | ✓ | ✓ | ✓ |
+| Use environment terminals | | | | ✓ | ✓ |
+| Run Web IDE's Interactive Web Terminals **[ULTIMATE ONLY]** | | | | ✓ | ✓ |
+| Add new team members | | | | ✓ | ✓ |
+| Enable/disable branch protection | | | | ✓ | ✓ |
+| Push to protected branches | | | | ✓ | ✓ |
+| Turn on/off protected branch push for devs | | | | ✓ | ✓ |
+| Enable/disable tag protections | | | | ✓ | ✓ |
+| Rewrite/remove Git tags | | | | ✓ | ✓ |
+| Edit project | | | | ✓ | ✓ |
+| Add deploy keys to project | | | | ✓ | ✓ |
+| Configure project hooks | | | | ✓ | ✓ |
+| Manage Runners | | | | ✓ | ✓ |
+| Manage job triggers | | | | ✓ | ✓ |
+| Manage variables | | | | ✓ | ✓ |
+| Manage GitLab Pages | | | | ✓ | ✓ |
+| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
+| Remove GitLab Pages | | | | ✓ | ✓ |
+| Manage clusters | | | | ✓ | ✓ |
+| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ |
+| Edit comments (posted by any user) | | | | ✓ | ✓ |
+| Manage Error Tracking | | | | ✓ | ✓ |
+| Delete wiki pages | | | | ✓ | ✓ |
+| View project Audit Events | | | | ✓ | ✓ |
+| Switch visibility level | | | | | ✓ |
+| Transfer project to another namespace | | | | | ✓ |
+| Remove project | | | | | ✓ |
+| Delete issues | | | | | ✓ |
+| Force push to protected branches [^4] | | | | | |
+| Remove protected branches [^4] | | | | | |
+
+- (*1*): All users are able to perform this action on public and internal projects, but not private projects.
+- (*2*): Guest users can only view the confidential issues they created themselves
+- (*3*): If **Public pipelines** is enabled in **Project Settings > CI/CD**
+- (*4*): Not allowed for Guest, Reporter, Developer, Maintainer, or Owner
## Project features permissions
@@ -163,7 +168,7 @@ to learn more.
The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located.
-Read through the documentation on [permissions for File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html#permissions-on-file-locking) to learn more.
+Read through the documentation on [permissions for File Locking](project/file_lock.md#permissions-on-file-locking) to learn more.
### Confidential Issues permissions
@@ -191,21 +196,21 @@ Any user can remove themselves from a group, unless they are the last Owner of
the group. The following table depicts the various user permission levels in a
group.
-| Action | Guest | Reporter | Developer | Maintainer | Owner |
-|-------------------------|-------|----------|-----------|--------|-------|
-| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Edit group | | | | | ✓ |
-| Create subgroup | | | | | ✓ |
-| Create project in group | | | ✓ | ✓ | ✓ |
-| Manage group members | | | | | ✓ |
-| Remove group | | | | | ✓ |
-| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
-| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
-| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
-| Delete group epic **[ULTIMATE]** | | | | | ✓ |
-| View group Audit Events | | | | | ✓ |
-| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Action | Guest | Reporter | Developer | Maintainer | Owner |
+|---------------------------------------|-------|----------|-----------|------------|-------|
+| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View Insights charts **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
+| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
+| Create project in group | | | ✓ | ✓ | ✓ |
+| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
+| Edit group | | | | | ✓ |
+| Create subgroup | | | | | ✓ |
+| Manage group members | | | | | ✓ |
+| Remove group | | | | | ✓ |
+| Delete group epic **[ULTIMATE]** | | | | | ✓ |
+| View group Audit Events | | | | | ✓ |
### Subgroup permissions
@@ -257,15 +262,15 @@ Please be aware that this regex could lead to a DOS attack, [see](https://en.wik
## Auditor users **[PREMIUM ONLY]**
->[Introduced][ee-998] in [GitLab Premium][eep] 8.17.
+>[Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998) in [GitLab Premium](https://about.gitlab.com/pricing/) 8.17.
Auditor users are given read-only access to all projects, groups, and other
resources on the GitLab instance.
An Auditor user should be able to access all projects and groups of a GitLab instance
-with the permissions described on the documentation on [auditor users permissions](https://docs.gitlab.com/ee/administration/auditor_users.html#permissions-and-restrictions-of-an-auditor-user).
+with the permissions described on the documentation on [auditor users permissions](../administration/auditor_users.md#permissions-and-restrictions-of-an-auditor-user).
-[Read more about Auditor users.](https://docs.gitlab.com/ee/administration/auditor_users.html)
+[Read more about Auditor users.](../administration/auditor_users.md)
## Project features
@@ -298,7 +303,7 @@ instance and project. In addition, all admins can use the admin interface under
|---------------------------------------|-----------------|-------------|----------|--------|
| See commits and jobs | ✓ | ✓ | ✓ | ✓ |
| Retry or cancel job | | ✓ | ✓ | ✓ |
-| Erase job artifacts and trace | | ✓ [^5] | ✓ | ✓ |
+| Erase job artifacts and trace | | ✓ (*1*) | ✓ | ✓ |
| Remove project | | | ✓ | ✓ |
| Create project | | | ✓ | ✓ |
| Change project configuration | | | ✓ | ✓ |
@@ -307,6 +312,8 @@ instance and project. In addition, all admins can use the admin interface under
| See events in the system | | | | ✓ |
| Admin interface | | | | ✓ |
+- *1*: Only if the job was triggered by the user
+
### Job permissions
NOTE: **Note:**
@@ -314,25 +321,28 @@ In GitLab 11.0, the Master role was renamed to Maintainer.
>**Note:**
GitLab 8.12 has a completely redesigned job permissions system.
-Read all about the [new model and its implications][new-mod].
+Read all about the [new model and its implications](project/new_ci_build_permissions_model.md).
This table shows granted privileges for jobs triggered by specific types of
users:
-| Action | Guest, Reporter | Developer |Maintainer| Admin |
-|---------------------------------------------|-----------------|-------------|----------|--------|
-| Run CI job | | ✓ | ✓ | ✓ |
-| Clone source and LFS from current project | | ✓ | ✓ | ✓ |
-| Clone source and LFS from public projects | | ✓ | ✓ | ✓ |
-| Clone source and LFS from internal projects | | ✓ [^6] | ✓ [^6] | ✓ |
-| Clone source and LFS from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] |
-| Push source and LFS | | | | |
-| Pull container images from current project | | ✓ | ✓ | ✓ |
-| Pull container images from public projects | | ✓ | ✓ | ✓ |
-| Pull container images from internal projects| | ✓ [^6] | ✓ [^6] | ✓ |
-| Pull container images from private projects | | ✓ [^7] | ✓ [^7] | ✓ [^7] |
-| Push container images to current project | | ✓ | ✓ | ✓ |
-| Push container images to other projects | | | | |
+| Action | Guest, Reporter | Developer |Maintainer| Admin |
+|---------------------------------------------|-----------------|-------------|----------|---------|
+| Run CI job | | ✓ | ✓ | ✓ |
+| Clone source and LFS from current project | | ✓ | ✓ | ✓ |
+| Clone source and LFS from public projects | | ✓ | ✓ | ✓ |
+| Clone source and LFS from internal projects | | ✓ (*1*) | ✓ (*1*) | ✓ |
+| Clone source and LFS from private projects | | ✓ (*2*) | ✓ (*2*) | ✓ (*2*) |
+| Pull container images from current project | | ✓ | ✓ | ✓ |
+| Pull container images from public projects | | ✓ | ✓ | ✓ |
+| Pull container images from internal projects| | ✓ (*1*) | ✓ (*1*) | ✓ |
+| Pull container images from private projects | | ✓ (*2*) | ✓ (*2*) | ✓ (*2*) |
+| Push container images to current project | | ✓ | ✓ | ✓ |
+| Push container images to other projects | | | | |
+| Push source and LFS | | | | |
+
+- *1*: Only if the user is not an external one
+- *2*: Only if the user is a member of the project
### New CI job permissions model
@@ -350,17 +360,4 @@ for details about the pipelines security model.
## LDAP users permissions
Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user.
-Read through the documentation on [LDAP users permissions](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html) to learn more.
-
-[^1]: On public and internal projects, all users are able to perform this action
-[^2]: Guest users can only view the confidential issues they created themselves
-[^3]: If **Public pipelines** is enabled in **Project Settings > CI/CD**
-[^4]: Not allowed for Guest, Reporter, Developer, Maintainer, or Owner
-[^5]: Only if the job was triggered by the user
-[^6]: Only if user is not external one
-[^7]: Only if user is a member of the project
-
-[ce-18994]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18994
-[new-mod]: project/new_ci_build_permissions_model.md
-[ee-998]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998
-[eep]: https://about.gitlab.com/pricing/
+Read through the documentation on [LDAP users permissions](../administration/auth/how_to_configure_ldap_gitlab_ee/index.html) to learn more.
diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index 0d7bbb0af79..304a7984191 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -23,7 +23,7 @@ Here's a list of things that will **not** be deleted:
- Award emoji that the user created
Instead of being deleted, these records will be moved to a system-wide
-"Ghost User", whose sole purpose is to act as a container for such records.
+user with the username "Ghost User", whose sole purpose is to act as a container for such records. Any commits made by a deleted user will still display the username of the original user.
When a user is deleted from an [abuse report](../../admin_area/abuse_reports.md) or spam log, these associated
records are not ghosted and will be removed, along with any groups the user
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 4085f3b678c..0b224fc7e01 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -37,19 +37,18 @@ Personal access tokens can be created with one or more scopes that allow various
actions that a given token can perform. The available scopes are depicted in
the following table.
-| Scope | Description |
-| ----- | ----------- |
-|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). |
-| `api` | Grants complete access to the API and Container Registry (read/write) ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) in GitLab 8.15). |
-| `read_registry` | Allows to read (pull) [container registry] images if a project is private and authorization is required ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) in GitLab 9.3). |
-| `sudo` | Allows performing API actions as any user in the system (if the authenticated user is an admin) ([introduced][ce-14838] in GitLab 10.2). |
-| `read_repository` | Allows read-only access (pull) to the repository through git clone. |
-| `write_repository` | Allows read-write access (pull, push) to the repository through git clone. Required for accessing Git repositories over HTTP when 2FA is enabled. |
+| Scope | Introduced in | Description |
+| ------------------ | ------------- | ----------- |
+| `read_user` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed. |
+| `api` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Grants complete access to the API and Container Registry (read/write). |
+| `read_registry` | [GitLab 9.3](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) | Allows to read (pull) [container registry] images if a project is private and authorization is required. |
+| `sudo` | [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838) | Allows performing API actions as any user in the system (if the authenticated user is an admin). |
+| `read_repository` | [GitLab 10.7](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894) | Allows read-only access (pull) to the repository through git clone. |
+| `write_repository` | [GitLab 11.11](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26021) | Allows read-write access (pull, push) to the repository through git clone. Required for accessing Git repositories over HTTP when 2FA is enabled. |
[2fa]: ../account/two_factor_authentication.md
[api]: ../../api/README.md
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
-[ce-14838]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838
[container registry]: ../project/container_registry.md
[users]: ../../api/users.md
[usage]: ../../api/README.md#personal-access-tokens
diff --git a/doc/user/project/ci_cd_for_external_repo.md b/doc/user/project/ci_cd_for_external_repo.md
index 51b86a68c7b..a92d3a2c308 100644
--- a/doc/user/project/ci_cd_for_external_repo.md
+++ b/doc/user/project/ci_cd_for_external_repo.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html'
+redirect_to: '../../ci/ci_cd_for_external_repos/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html).
+This document was moved to [another location](../../ci/ci_cd_for_external_repos/index.md).
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 3bc3beb2055..dc21db603d6 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -71,7 +71,7 @@ new Kubernetes cluster to your project:
- **Number of nodes** - Enter the number of nodes you wish the cluster to have.
- **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
of the Virtual Machine instance that the cluster will be based on.
- - **RBAC-enabled cluster** - Leave this checked if using default GKE creation options, see the [RBAC section](#role-based-access-control-rbac) for more information.
+ - **RBAC-enabled cluster** - Leave this checked if using default GKE creation options, see the [RBAC section](#rbac-cluster-resources) for more information.
- **GitLab-managed cluster** - Leave this checked if you want GitLab to manage namespaces and service accounts for this cluster. See the [Managed clusters section](#gitlab-managed-clusters) for more information.
1. Finally, click the **Create Kubernetes cluster** button.
@@ -263,65 +263,66 @@ you can either:
## Access controls
-When creating a cluster in GitLab, you will be asked if you would like to create an
-[Attribute-based access control (ABAC)](https://kubernetes.io/docs/admin/authorization/abac/) cluster, or
-a [Role-based access control (RBAC)](https://kubernetes.io/docs/admin/authorization/rbac/) one.
+When creating a cluster in GitLab, you will be asked if you would like to create either:
-NOTE: **Note:**
-[RBAC](#role-based-access-control-rbac) is recommended and the GitLab default.
+- An [Attribute-based access control (ABAC)](https://kubernetes.io/docs/admin/authorization/abac/) cluster.
+- A [Role-based access control (RBAC)](https://kubernetes.io/docs/admin/authorization/rbac/) cluster.
-Whether [ABAC](#attribute-based-access-control-abac) or [RBAC](#role-based-access-control-rbac) is enabled,
-GitLab will create the necessary service accounts and privileges in order to install and run
-[GitLab managed applications](#installing-applications):
+NOTE: **Note:**
+[RBAC](#rbac-cluster-resources) is recommended and the GitLab default.
-- If GitLab is creating the cluster, a `gitlab` service account with
- `cluster-admin` privileges will be created in the `default` namespace,
- which will be used by GitLab to manage the newly created cluster.
+GitLab creates the necessary service accounts and privileges to install and run
+[GitLab managed applications](#installing-applications). When GitLab creates the cluster:
+- A `gitlab` service account with `cluster-admin` privileges is created in the `default` namespace
+ to manage the newly created cluster.
- A project service account with [`edit`
privileges](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
- will be created in the project namespace (also created by GitLab), which will
- be used in [deployment jobs](#deployment-variables).
+ is created in the GitLab-created project namespace for [deployment jobs](#deployment-variables).
NOTE: **Note:**
Restricted service account for deployment was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/51716) in GitLab 11.5.
-- When you install Helm into your cluster, the `tiller` service account
- will be created with `cluster-admin` privileges in the `gitlab-managed-apps`
- namespace. This service account will be added to the installed Helm Tiller and will
- be used by Helm to install and run [GitLab managed applications](#installing-applications).
- Helm will also create additional service accounts and other resources for each
- installed application. Consult the documentation of the Helm charts for each application
- for details.
+When you install Helm into your cluster, the `tiller` service account
+is created with `cluster-admin` privileges in the `gitlab-managed-apps`
+namespace. This service account will be added to the installed Helm Tiller and will
+be used by Helm to install and run [GitLab managed applications](#installing-applications).
+Helm will also create additional service accounts and other resources for each
+installed application. Consult the documentation of the Helm charts for each application
+for details.
If you are [adding an existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster),
ensure the token of the account has administrator privileges for the cluster.
-The following sections summarize which resources will be created on ABAC/RBAC clusters.
+The resources created by GitLab differ depending on the type of cluster.
+
+### ABAC cluster resources
-### Attribute-based access control (ABAC)
+GitLab creates the following resources for ABAC clusters.
-| Name | Kind | Details | Created when |
-| --- | --- | --- | --- |
-| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
-| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
-| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
-| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
-| Project namespace | `ServiceAccount` | Uses namespace of Project | Deploying to a cluster |
-| Project namespace | `Secret` | Token for project ServiceAccount | Deploying to a cluster |
+| Name | Type | Details | Created when |
+|:------------------|:---------------------|:----------------------------------|:---------------------------|
+| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
+| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| Project namespace | `ServiceAccount` | Uses namespace of Project | Deploying to a cluster |
+| Project namespace | `Secret` | Token for project ServiceAccount | Deploying to a cluster |
-### Role-based access control (RBAC)
+### RBAC cluster resources
-| Name | Kind | Details | Created when |
-| --- | --- | --- | --- |
-| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
-| `gitlab-admin` | `ClusterRoleBinding` | [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating a new GKE Cluster |
-| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
-| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
-| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
-| Project namespace | `ServiceAccount` | Uses namespace of Project | Deploying to a cluster |
-| Project namespace | `Secret` | Token for project ServiceAccount | Deploying to a cluster |
-| Project namespace | `RoleBinding` | [`edit`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Deploying to a cluster |
+GitLab creates the following resources for RBAC clusters.
+
+| Name | Type | Details | Created when |
+|:------------------|:---------------------|:-----------------------------------------------------------------------------------------------------------|:---------------------------|
+| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
+| `gitlab-admin` | `ClusterRoleBinding` | [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating a new GKE Cluster |
+| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| Project namespace | `ServiceAccount` | Uses namespace of Project | Deploying to a cluster |
+| Project namespace | `Secret` | Token for project ServiceAccount | Deploying to a cluster |
+| Project namespace | `RoleBinding` | [`edit`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Deploying to a cluster |
NOTE: **Note:**
Project-specific resources are only created if your cluster is [managed by GitLab](#gitlab-managed-clusters).
@@ -346,111 +347,10 @@ install it manually.
## Installing applications
-GitLab provides **GitLab Managed Apps**, a one-click install for various applications which can
-be added directly to your configured cluster. These applications are
-needed for [Review Apps](../../../ci/review_apps/index.md) and
-[deployments](../../../ci/environments.md) when using [Auto DevOps](../../../topics/autodevops/index.md).
-You can install them after you
-[create a cluster](#adding-and-creating-a-new-gke-cluster-via-gitlab).
-
-Applications managed by GitLab will be installed onto the `gitlab-managed-apps` namespace. This differrent
-from the namespace used for project deployments. It is only created once and its name is not configurable.
-
-To see a list of available applications to install:
-
-1. Navigate to your project's **Operations > Kubernetes**.
-1. Select your cluster.
-
-Install Helm first as it's used to install other applications.
-
-NOTE: **Note:**
-As of GitLab 11.6, Helm will be upgraded to the latest version supported
-by GitLab before installing any of the applications.
-
-| Application | GitLab version | Description | Helm Chart |
-| ----------- | :------------: | ----------- | --------------- |
-| [Helm](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
-| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
-| [Cert-Manager](https://docs.cert-manager.io/en/latest/) | 11.6+ | Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
-| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
-| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](../../../ci/README.md), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
-| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. Authentication will be enabled only for [project members](../members/index.md) with [Developer or higher](../../permissions.md) access to the project. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). Note that Ingress must be installed and have an IP address assigned before JupyterHub can be installed. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
-| [Knative](https://cloud.google.com/knative) | 11.5+ | Knative provides a platform to create, deploy, and manage serverless workloads from a Kubernetes cluster. It is used in conjunction with, and includes [Istio](https://istio.io) to provide an external IP address for all programs hosted by Knative. You will be prompted to enter a wildcard domain where your applications will be exposed. Configure your DNS server to use the external IP address for that domain. For any application created and installed, they will be accessible as `<program_name>.<kubernetes_namespace>.<domain_name>`. This will require your kubernetes cluster to have [RBAC enabled](#role-based-access-control-rbac). | [knative/knative](https://storage.googleapis.com/triggermesh-charts)
-
-With the exception of Knative, the applications will be installed in a dedicated
-namespace called `gitlab-managed-apps`.
-
-CAUTION: **Caution:**
-If you have an existing Kubernetes cluster with Helm already installed,
-you should be careful as GitLab cannot detect it. In this case, installing
-Helm via the applications will result in the cluster having it twice, which
-can lead to confusion during deployments.
-
-### Upgrading applications
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789)
-in GitLab 11.8.
-
-Users can perform a one-click upgrade for the GitLab Runner application,
-when there is an upgrade available.
-
-To upgrade the GitLab Runner application:
-
-1. Navigate to your project's **Operations > Kubernetes**.
-1. Select your cluster.
-1. Click the **Upgrade** button for the Runnner application.
-
-The **Upgrade** button will not be shown if there is no upgrade
-available.
-
-NOTE: **Note:**
-Upgrades will reset values back to the values built into the `runner`
-chart plus the values set by
-[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml)
-
-### Uninstalling applications
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/60665) in
-> GitLab 11.11.
-
-The applications below can be uninstalled.
-
-| Application | GitLab version | Notes |
-| ----------- | -------------- | ----- |
-| Prometheus | 11.11+ | All data will be deleted and cannot be restored. |
-
-To uninstall an application:
-
-1. Navigate to your project's **Operations > Kubernetes**.
-1. Select your cluster.
-1. Click the **Uninstall** button for the application.
-
-Support for uninstalling all applications is planned for progressive rollout.
-To follow progress, see [the relevant
-epic](https://gitlab.com/groups/gitlab-org/-/epics/1201).
-
-### Troubleshooting applications
-
-Applications can fail with the following error:
-
-```text
-Error: remote error: tls: bad certificate
-```
-
-To avoid installation errors:
-
-- Before starting the installation of applications, make sure that time is synchronized
- between your GitLab server and your Kubernetes cluster.
-- Ensure certificates are not out of sync. When installing applications, GitLab expects a new cluster with no previous installation of Helm.
-
- You can confirm that the certificates match via `kubectl`:
-
- ```sh
- kubectl get configmaps/values-content-configuration-ingress -n gitlab-managed-apps -o \
- "jsonpath={.data['cert\.pem']}" | base64 -d > a.pem
- kubectl get secrets/tiller-secret -n gitlab-managed-apps -o "jsonpath={.data['ca\.crt']}" | base64 -d > b.pem
- diff a.pem b.pem
- ```
+GitLab can install and manage some applications in your project-level
+cluster. For more information on installing, upgrading, uninstalling,
+and troubleshooting applications for your project cluster, see
+[Gitlab Managed Apps](../../clusters/applications.md).
## Getting the external endpoint
@@ -550,7 +450,7 @@ differentiate the new cluster with the rest.
When adding more than one Kubernetes cluster to your project, you need to differentiate
them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the
-[environment-specific variables](https://docs.gitlab.com/ee/ci/variables/index.html#limiting-environment-scopes-of-environment-variables-premium) work.
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) work.
The default environment scope is `*`, which means all jobs, regardless of their
environment, will use that cluster. Each scope can only be used by a single
@@ -688,7 +588,7 @@ displaying the status of the pods in the deployment. Developers and other
teammates can view the progress and status of a rollout, pod by pod, in the
workflow they already use without any need to access Kubernetes.
-[Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
+[Read more about Deploy Boards](../deploy_boards.md)
### Canary Deployments **[PREMIUM]**
@@ -696,7 +596,7 @@ Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cl
and visualize your canary deployments right inside the Deploy Board, without
the need to leave GitLab.
-[Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+[Read more about Canary Deployments](../canary_deployments.md)
### Pod logs **[ULTIMATE]**
diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md
index d5b60250860..368031070c1 100644
--- a/doc/user/project/clusters/kubernetes_pod_logs.md
+++ b/doc/user/project/clusters/kubernetes_pod_logs.md
@@ -7,10 +7,10 @@ By displaying the logs directly in GitLab, developers can avoid having to manage
## Overview
-[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab. Logs can be displayed by clicking on a specific pod from [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html):
+[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab. Logs can be displayed by clicking on a specific pod from [Deploy Boards](../deploy_boards.md):
1. Go to **Operations > Environments** and find the environment which contains the desired pod, like `production`.
-1. On the **Environments** page, you should see the status of the environment's pods with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html).
+1. On the **Environments** page, you should see the status of the environment's pods with [Deploy Boards](../deploy_boards.md).
1. When mousing over the list of pods, a tooltip will appear with the exact pod name and status.
![Deploy Boards pod list](img/pod_logs_deploy_board.png)
1. Click on the desired pod to bring up the logs view, which will contain the last 500 lines for that pod. Support for pods with multiple containers is coming [in a future release](https://gitlab.com/gitlab-org/gitlab-ee/issues/6502).
@@ -18,4 +18,4 @@ By displaying the logs directly in GitLab, developers can avoid having to manage
## Requirements
-[Enabling Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html#enabling-deploy-boards) is required in order to be able to use Pod Logs.
+[Enabling Deploy Boards](../deploy_boards.md#enabling-deploy-boards) is required in order to be able to use Pod Logs.
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index 54c475a1762..c67b12fb91a 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -17,13 +17,15 @@ Modern implementations have introduced the concept of an "executable
runbooks", where, along with a well-defined process, operators can execute
pre-written code blocks or database queries against a given environment.
-## Nurtch Executable Runbooks
+## Executable Runbooks
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45912) in GitLab 11.4.
The JupyterHub app offered via GitLab’s Kubernetes integration now ships
with Nurtch’s Rubix library, providing a simple way to create DevOps
-runbooks. A sample runbook is provided, showcasing common operations.
+runbooks. A sample runbook is provided, showcasing common operations. While Rubix makes it
+simple to create common Kubernetes and AWS workflows, you can also create them manually without
+Rubix.
**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
Watch this [video](https://www.youtube.com/watch?v=Q_OqHIIUPjE)
@@ -34,7 +36,7 @@ for an overview of how this is accomplished in GitLab!**
To create an executable runbook, you will need:
1. **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the applications.
- The simplest way to get started is to add a cluster using [GitLab's GKE integration](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab).
+ The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
1. **Helm Tiller** - Helm is a package manager for Kubernetes and is required to install
all the other applications. It is installed in its own pod inside the cluster which
can run the helm CLI in a safe environment.
@@ -59,7 +61,7 @@ the components outlined above and the preloaded demo runbook.
### 1. Add a Kubernetes cluster
-Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab)
+Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab)
to add a Kubernetes cluster to your project.
### 2. Install Helm Tiller, Ingress, and JupyterHub
@@ -90,7 +92,7 @@ The server will take a couple of seconds to start.
### 4. Configure access
In order for the runbook to access your GitLab project, you will need to enter a
-[GitLab Access Token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html)
+[GitLab Access Token](../../../profile/personal_access_tokens.md)
as well as your Project ID in the **Setup** section of the demo runbook.
Double-click the **DevOps-Runbook-Demo** folder located on the left panel.
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index dfbed0ec95f..2cda850f24e 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -216,7 +216,7 @@ The sample function can now be triggered from any HTTP client using a simple `PO
--header "Content-Type: application/json" \
--request POST \
--data '{"GitLab":"FaaS"}' \
- http://functions-echo.functions-1.functions.example.com
+ http://functions-echo.functions-1.functions.example.com/
```
2. Using a web-based tool (ie. postman, restlet, etc)
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index 83b268db967..58b7fe33906 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -53,7 +53,7 @@ If you visit the **Registry** link under your project's menu, you can see the
explicit instructions to login to the Container Registry using your GitLab
credentials.
-For example if the Registry's URL is `registry.example.com`, the you should be
+For example if the Registry's URL is `registry.example.com`, then you should be
able to login with:
```
@@ -204,7 +204,7 @@ at the communication between the client and the Registry.
The REST API between the Docker client and Registry is [described
here](https://docs.docker.com/registry/spec/api/). Normally, one would just
use Wireshark or tcpdump to capture the traffic and see where things went
-wrong. However, since all communication between Docker clients and servers
+wrong. However, since all communications between Docker clients and servers
are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even
if you know the private key. What can we do instead?
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
index 19ab911e39f..dc97a44fd68 100644
--- a/doc/user/project/cycle_analytics.md
+++ b/doc/user/project/cycle_analytics.md
@@ -1,10 +1,7 @@
# Cycle Analytics
-> [Introduced][ce-5986] in GitLab 8.12. Further features were added in GitLab
- 8.14.
-
-Cycle Analytics measures the time spent to go from an [idea to production] for
-each of your projects. Cycle Analytics displays the median time for an idea to
+Cycle Analytics measures the time spent to go from an [idea to production] - also known
+as cycle time - for each of your projects. Cycle Analytics displays the median time for an idea to
reach production, along with the time typically spent in each DevOps stage along the way.
Cycle Analytics is useful in order to quickly determine the velocity of a given
@@ -54,9 +51,9 @@ Below you can see in more detail what the various stages of Cycle Analytics mean
| Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list][board] created for it. |
| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit of the branch is the one that triggers the separation between **Plan** and **Code**, and at least one of the commits in the branch needs to contain the related issue number (e.g., `#42`). If none of the commits in the branch mention the related issue number, it is not considered to the measurement time of the stage. |
| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request (MR) related to that commit. The key to keep the process tracked is to include the [issue closing pattern] to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the issue closing pattern is not present in the merge request description, the MR is not considered to the measurement time of the stage. |
-| Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. `master` is not excluded. It does not attempt to track time for any particular stages. |
-| Review | Measures the median time taken to review the merge request, between its creation and until it's merged. |
-| Staging | Measures the median time between merging the merge request until the very first deployment to production. It's tracked by the [environment] set to `production` or matching `production/*` (case-sensitive, `Production` won't work) in your GitLab CI configuration. If there isn't a production environment, this is not tracked. |
+| Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. |
+| Review | Measures the median time taken to review the merge request that has closing issue pattern, between its creation and until it's merged. |
+| Staging | Measures the median time between merging the merge request with closing issue pattern until the very first deployment to production. It's tracked by the [environment] set to `production` or matching `production/*` (case-sensitive, `Production` won't work) in your GitLab CI configuration. If there isn't a production environment, this is not tracked. |
| Production| The sum of all time (medians) taken to run the entire process, from issue creation to deploying the code to production. |
---
@@ -77,8 +74,7 @@ To sum up, anything that doesn't follow the [GitLab flow] won't be tracked at al
So, the Cycle Analytics dashboard won't present any data:
- For merge requests that do not close an issue.
-- For issues not labeled with a label present in the Issue Board.
-- For issues not assigned a milestone.
+- For issues not labeled with a label present in the Issue Board or for issues not assigned a milestone.
- For staging and production stages, if the project has no `production` or `production/*`
environment.
@@ -86,7 +82,7 @@ So, the Cycle Analytics dashboard won't present any data:
Below is a simple fictional workflow of a single cycle that happens in a
single day passing through all seven stages. Note that if a stage does not have
-a start/stop mark, it is not measured and hence not calculated in the median
+a start and a stop mark, it is not measured and hence not calculated in the median
time. It is assumed that milestones are created and CI for testing and setting
environments is configured.
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
index 7688508c6ac..92a29b68a22 100644
--- a/doc/user/project/deploy_tokens/index.md
+++ b/doc/user/project/deploy_tokens/index.md
@@ -5,7 +5,7 @@
Deploy tokens allow to download (through `git clone`), or read the container registry images of a project without the need of having a user and a password.
Please note, that the expiration of deploy tokens happens on the date you define,
-at midnight UTC and that they can be only managed by [maintainers](https://docs.gitlab.com/ee/user/permissions.html).
+at midnight UTC and that they can be only managed by [maintainers](../../permissions.md).
## Creating a Deploy Token
diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md
index e230444fa67..05ad15476ab 100644
--- a/doc/user/project/description_templates.md
+++ b/doc/user/project/description_templates.md
@@ -86,7 +86,7 @@ pre-filled with the text you entered in the template(s).
We make use of Description Templates for Issues and Merge Requests within the GitLab Community Edition project. Please refer to the [`.gitlab` folder][gitlab-ce-templates] for some examples.
> **Tip:**
-It is possible to use [quick actions](./quick_actions.md) within description templates to quickly add labels, assignees, and milestones. The quick actions will only be executed if the user submitting the Issue or Merge Request has the permissions perform the relevant actions.
+It is possible to use [quick actions](quick_actions.md) within description templates to quickly add labels, assignees, and milestones. The quick actions will only be executed if the user submitting the Issue or Merge Request has the permissions perform the relevant actions.
Here is an example for a Bug report template:
diff --git a/doc/user/project/file_lock.md b/doc/user/project/file_lock.md
index 541023692ea..3386eb9d0d4 100644
--- a/doc/user/project/file_lock.md
+++ b/doc/user/project/file_lock.md
@@ -1,10 +1,8 @@
# File Locking **[PREMIUM]**
-> **Notes:**
-> - [Introduced][ee-440] in [GitLab Premium][ee] 8.9.
-> - This feature needs to have a license with the "File Lock" option enabled. If
-> you are using Premium but you don't see the "Lock" button,
-> ask your GitLab administrator.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/440) in [GitLab Premium](https://about.gitlab.com/pricing/) 8.9.
+> - This feature needs to have a license with the "File Lock" option enabled.
+> - If you are using Premium but you don't see the "Lock" button, ask your GitLab administrator.
File Locking helps you avoid merge conflicts and better manage your binary files.
Lock any file or directory, make your changes, and then unlock it so another
@@ -37,7 +35,7 @@ lies under them is also locked.
The user that locks a file or directory **is the only one** that can edit and
push their changes back to the repository where the locked objects are located.
-Locks can be created by any person who has [push access] to the repository; i.e.,
+Locks can be created by any person who has [push access](../../user/permissions.md) to the repository; i.e.,
Developer and higher level, and can be removed solely by their author and any
user with Maintainer permissions and above.
@@ -101,7 +99,3 @@ To view or manage every existing lock, navigate to the
locks and [remove the ones you have permission for](#permissions-on-file-locking).
![Locked Files](img/file_lock_list.png)
-
-[ee-440]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/440 "File Lock"
-[ee]: https://about.gitlab.com/pricing/
-[push access]: ../../user/permissions.md
diff --git a/doc/user/project/import/gemnasium.md b/doc/user/project/import/gemnasium.md
index dc5b3fcd0bb..7f79ebf6353 100644
--- a/doc/user/project/import/gemnasium.md
+++ b/doc/user/project/import/gemnasium.md
@@ -9,9 +9,9 @@ Gemnasium has been [acquired by GitLab](https://about.gitlab.com/press/releases/
in January 2018. Since May 15, 2018, the services provided by Gemnasium are no longer available.
The team behind Gemnasium has joined GitLab as the new Security Products team
and is working on a wider range of tools than just Dependency Scanning:
-[SAST](https://docs.gitlab.com/ee/user/application_security/sast/index.html),
-[DAST](https://docs.gitlab.com/ee/user/application_security/dast/index.html),
-[Container Scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html) and more.
+[SAST](../../application_security/sast/index.md),
+[DAST](../../application_security/dast/index.md),
+[Container Scanning](../../application_security/container_scanning/index.md) and more.
If you want to continue monitoring your dependencies, see the
[Migrating to GitLab](#migrating-to-gitlab) section below.
@@ -45,7 +45,7 @@ Security features are free for public (open-source) projects hosted on GitLab.co
You're almost set! If you're already using
[Auto DevOps](../../../topics/autodevops/), you are already covered.
Otherwise, you must configure your `.gitlab-ci.yml` according to the
-[dependency scanning page](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html).
+[dependency scanning page](../../application_security/dependency_scanning/index.md).
### If your project is hosted on GitHub (https://github.com / GitHub Enterprise)
@@ -81,7 +81,7 @@ back to both GitLab and GitHub when completed.
1. To set up the dependency scanning job, corresponding to what Gemnasium was
doing, you must create a `.gitlab-ci.yml` file, or update it according to
- the [dependency scanning docs](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html).
+ the [dependency scanning docs](../../application_security/dependency_scanning/index.md).
The mirroring is pull-only by default, so you may create or update the file on
GitHub:
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index 63b90dd76fd..e194d57e2e0 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -60,6 +60,8 @@ For additional technical details, you can refer to the
[GitHub Importer](../../../development/github_importer.md "Working with the GitHub importer")
developer documentation.
+For an overview of the import process, see the video [Migrating from GitHub to GitLab](https://youtu.be/VYOXuOg9tQI).
+
## Import your GitHub repository into GitLab
### Using the GitHub integration
@@ -119,9 +121,9 @@ Depending your GitLab tier, [project mirroring](../../../workflow/repository_mir
your imported project in sync with its GitHub copy.
Additionally, you can configure GitLab to send pipeline status updates back GitHub with the
-[GitHub Project Integration](https://docs.gitlab.com/ee/user/project/integrations/github.html). **[PREMIUM]**
+[GitHub Project Integration](../integrations/github.md). **[PREMIUM]**
-If you import your project using [CI/CD for external repo](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/), then both
+If you import your project using [CI/CD for external repo](../../../ci/ci_cd_for_external_repos/index.md), then both
of the above are automatically configured. **[PREMIUM]**
## Improving the speed of imports on self-hosted instances
diff --git a/doc/user/project/import/gitlab_com.md b/doc/user/project/import/gitlab_com.md
index 3b37da67a5b..f48a158e2d3 100644
--- a/doc/user/project/import/gitlab_com.md
+++ b/doc/user/project/import/gitlab_com.md
@@ -1,8 +1,9 @@
# Project importing from GitLab.com to your private GitLab instance
You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
-GitLab support is enabled on your GitLab instance.
-You can read more about GitLab support [here](http://docs.gitlab.com/ce/integration/gitlab.html)
+GitLab.com integration is enabled on your GitLab instance.
+[Read more about GitLab.com integration for self-managed GitLab instances](../../../integration/gitlab.md).
+
To get to the importer page you need to go to "New project" page.
>**Note:**
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index ebbc5ca133b..2b6927bd780 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -14,12 +14,13 @@
1. [From repo by URL](repo_by_url.md)
1. [By uploading a manifest file (AOSP)](manifest.md)
1. [From Gemnasium](gemnasium.md)
+1. [From Phabricator](phabricator.md)
In addition to the specific migration documentation above, you can import any
Git repository via HTTP from the New Project page. Be aware that if the
repository is too large the import can timeout.
-There is also the option of [connecting your external repository to get CI/CD benefits](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html). **[PREMIUM]**
+There is also the option of [connecting your external repository to get CI/CD benefits](../../../ci/ci_cd_for_external_repos/index.md). **[PREMIUM]**
## Migrating from self-hosted GitLab to GitLab.com
diff --git a/doc/user/project/import/phabricator.md b/doc/user/project/import/phabricator.md
new file mode 100644
index 00000000000..5c624e3aff6
--- /dev/null
+++ b/doc/user/project/import/phabricator.md
@@ -0,0 +1,29 @@
+# Import Phabricator tasks into a GitLab project
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/60562) in
+GitLab 12.0.
+
+GitLab allows you to import all tasks from a Phabricator instance into
+GitLab issues. The import creates a single project with the
+repository disabled.
+
+Currently, only the following basic fields are imported:
+
+- Title
+- Description
+- State (open or closed)
+- Created at
+- Closed at
+
+## Enabling this feature
+
+While this feature is incomplete, a feature flag is required to enable it so that
+we can gain early feedback before releasing it for everyone. To enable it:
+
+1. Run the following command in a Rails console:
+
+ ```ruby
+ Feature.enable(:phabricator_import)
+ ```
+
+1. Enable Phabricator as an [import source](../../admin_area/settings/visibility_and_access_controls.md#import-sources) in the Admin area.
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 6b3b40bf9f8..587b4121e4e 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -37,7 +37,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Multiple Issue Boards](issue_board.md#multiple-issue-boards-starter): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
- [Merge Requests](merge_requests/index.md): Apply your branching
strategy and get reviewed by your team
- - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html): Ask for approval before
+ - [Merge Request Approvals](merge_requests/merge_request_approvals.md): Ask for approval before
implementing a change **[STARTER]**
- [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md):
Your Git diff tool right from GitLab's UI
@@ -74,7 +74,7 @@ When you create a project in GitLab, you'll have access to a large number of
timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project
with a Kubernetes cluster
- - [Feature Flags](https://docs.gitlab.com/ee/user/project/operations/feature_flags.html): Feature flags allow you to ship a project in
+ - [Feature Flags](operations/feature_flags.md): Feature flags allow you to ship a project in
different flavors by dynamically toggling certain functionality **[PREMIUM]**
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
website with GitLab Pages
@@ -84,6 +84,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Wiki](wiki/index.md): document your GitLab project in an integrated Wiki.
- [Snippets](../snippets.md): store, share and collaborate on code snippets.
- [Cycle Analytics](cycle_analytics.md): review your development lifecycle.
+- [Insights](insights/index.md): configure the Insights that matter for your projects. **[ULTIMATE]**
- [Security Dashboard](security_dashboard.md): Security Dashboard. **[ULTIMATE]**
- [Syntax highlighting](highlighting.md): an alternative to customize
your code blocks, overriding GitLab's default choice of language.
@@ -91,10 +92,10 @@ When you create a project in GitLab, you'll have access to a large number of
- [Releases](releases/index.md): a way to track deliverables in your project as snapshot in time of
the source, build output, and other metadata or artifacts
associated with a released version of your code.
-- [Maven packages](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html): your private Maven repository in GitLab. **[PREMIUM]**
-- [NPM packages](https://docs.gitlab.com/ee/user/project/packages/npm_registry.html): your private NPM package registry in GitLab. **[PREMIUM]**
+- [Maven packages](packages/maven_repository.md): your private Maven repository in GitLab. **[PREMIUM]**
+- [NPM packages](packages/npm_registry.md): your private NPM package registry in GitLab. **[PREMIUM]**
- [Code owners](code_owners.md): specify code owners for certain files **[STARTER]**
-- [License Management](https://docs.gitlab.com/ee/user/application_security/license_management/index.html): approve and blacklist licenses for projects. **[ULTIMATE]**
+- [License Management](../application_security/license_management/index.md): approve and blacklist licenses for projects. **[ULTIMATE]**
### Project integrations
@@ -135,7 +136,7 @@ Read through the documentation on [project settings](settings/index.md).
Instead of importing a repository directly to GitLab, you can connect your repository
as a CI/CD project.
-Read through the documentation on [CI/CD for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html).
+Read through the documentation on [CI/CD for external repositories](../../ci/ci_cd_for_external_repos/index.md).
## Project members
diff --git a/doc/user/project/insights/index.md b/doc/user/project/insights/index.md
index b6cc1862cc2..2e2a27f112e 100644
--- a/doc/user/project/insights/index.md
+++ b/doc/user/project/insights/index.md
@@ -1,11 +1,7 @@
# Insights **[ULTIMATE]**
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.9 behind the `insights` feature flag.
-
-CAUTION: **Beta:**
-Insights is considered beta, and is not ready for production use.
-Follow [gitlab-org/quality/team-tasks#137](https://gitlab.com/gitlab-org/quality/team-tasks/issues/137#general-availability)
-for updates.
+> **Generally Available** (GA) 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
@@ -14,7 +10,7 @@ requests to be merged and much more.
![Insights example bar chart](img/project_insights.png)
NOTE: **Note:**
-This feature is [also available at the group level](https://docs.gitlab.com/ee/user/group/insights/index.html).
+This feature is [also available at the group level](../../group/insights/index.md).
## View your project's Insights
@@ -33,7 +29,7 @@ for details about the content of this file.
NOTE: **Note:**
Once the configuration file is created, you can also
-[use it for your project's group](https://docs.gitlab.com/ee/user/group/insights/index.html#configure-your-insights).
+[use it for your project's group](../../group/insights/index.md#configure-your-insights).
NOTE: **Note:**
If the project doesn't have any configuration file, it'll try to use
@@ -179,7 +175,7 @@ Supported values are:
Filter by the state of the queried "issuable".
-If you omit it, no state filter will be applied.
+If you omit it, the `opened` state filter will be applied.
Supported values are:
@@ -187,6 +183,7 @@ Supported values are:
- `closed`: Closed Open issues / merge requests.
- `locked`: Issues / merge requests that have their discussion locked.
- `merged`: Merged merge requests.
+- `all`: Issues / merge requests in all states
#### `query.filter_labels`
diff --git a/doc/user/project/integrations/img/jira_add_user_to_group.png b/doc/user/project/integrations/img/jira_add_user_to_group.png
index 27dac49260c..d8cf541a81e 100644
--- a/doc/user/project/integrations/img/jira_add_user_to_group.png
+++ b/doc/user/project/integrations/img/jira_add_user_to_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_added_user_to_group.png b/doc/user/project/integrations/img/jira_added_user_to_group.png
new file mode 100644
index 00000000000..b3e29a65d6e
--- /dev/null
+++ b/doc/user/project/integrations/img/jira_added_user_to_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_create_new_group.png b/doc/user/project/integrations/img/jira_create_new_group.png
index 06c4e84fc61..84be3a94a45 100644
--- a/doc/user/project/integrations/img/jira_create_new_group.png
+++ b/doc/user/project/integrations/img/jira_create_new_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_create_new_user.png b/doc/user/project/integrations/img/jira_create_new_user.png
index e9c03ed770d..8460dc98ef9 100644
--- a/doc/user/project/integrations/img/jira_create_new_user.png
+++ b/doc/user/project/integrations/img/jira_create_new_user.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_group_access.png b/doc/user/project/integrations/img/jira_group_access.png
index 448cc55504d..58cf114bd55 100644
--- a/doc/user/project/integrations/img/jira_group_access.png
+++ b/doc/user/project/integrations/img/jira_group_access.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_user_management_link.png b/doc/user/project/integrations/img/jira_user_management_link.png
index 5eb9d031c3e..43ef18da6c8 100644
--- a/doc/user/project/integrations/img/jira_user_management_link.png
+++ b/doc/user/project/integrations/img/jira_user_management_link.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index a90167b9767..c652149052e 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -47,11 +47,11 @@ project in Jira and then enter the correct values in GitLab.
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 an 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:
-- [Setting up an user in JIRA cloud](jira_cloud_configuration.md)
+- [Setting up a user in JIRA cloud](jira_cloud_configuration.md)
### Configuring GitLab
diff --git a/doc/user/project/integrations/jira_server_configuration.md b/doc/user/project/integrations/jira_server_configuration.md
index 20036183187..13d65c4d8e4 100644
--- a/doc/user/project/integrations/jira_server_configuration.md
+++ b/doc/user/project/integrations/jira_server_configuration.md
@@ -1,18 +1,16 @@
# 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. Login to your Jira instance as admin and under
-*Administration*, go to *User Management* and create a new user.
+need to integrate with GitLab.
As an example, we'll create a user named `gitlab` and add it to the `Jira-developers`
group.
NOTE: **Note**
-It is important that the user `gitlab` has 'write' access to projects in Jira.
+It is important that the Jira user created for the integration is given 'write'
+access to your Jira projects. This is covered in the process below.
-We have split this stage in steps so it is easier to follow.
-
-1. Log in to your Jira instance as an administrator and under **Administration**
+1. Log in to your Jira instance as an administrator and under **Jira Administration**
go to **User Management** to create a new user.
![Jira user management link](img/jira_user_management_link.png)
@@ -27,27 +25,34 @@ We have split this stage in steps so it is easier to follow.
![Jira create new user](img/jira_create_new_user.png)
-1. Create a `gitlab-developers` group which will have write access
- to projects in Jira. Go to the **Groups** tab and select **Create group**.
+1. Create a `gitlab-developers` group. (We will give this group write access to Jira
+ projects in a later step). Go to the **Groups** tab on the left, and select **Add group**.
![Jira create new user](img/jira_create_new_group.png)
- Give it an optional description and click **Create group**.
+ Give it a name and click **Add group**.
- ![Jira create new group](img/jira_create_new_group_name.png)
+1. Add the `gitlab` user to the `gitlab-developers` group by clicking **Edit members**.
+ The `gitlab-developers` group should be listed in the leftmost box as a selected group.
+ Under **Add members to selected group(s)**, enter `gitlab`.
-1. To give the newly-created group 'write' access, go to
- **Application access > View configuration** and add the `gitlab-developers`
- group to Jira Core.
+ ![Jira add user to group](img/jira_add_user_to_group.png)
+
+ Click **Add selected users** and `gitlab` should appear in the **Group member(s)** box.
+ This membership is saved automatically.
+
+ ![Jira added user to group](img/jira_added_user_to_group.png)
+
+1. To give the newly-created group 'write' access, you need to create a **Permission Scheme**.
+ To do this, click the gear icon and select **Issues**. Then click **Permission Schemes**.
+ Click **Add Permission Scheme** and enter a **Name** and, optionally, a **Description**.
+
+1. Once your permission scheme is created, you'll be taken back to the permissions scheme list.
+ Locate your new permissions scheme and click **Permissions**. Next to **Administer Projects**,
+ click **Edit**. In the resulting dialog box, select **Group** and select `gitlab-developers`
+ from the dropdown.
![Jira group access](img/jira_group_access.png)
-1. Add the `gitlab` user to the `gitlab-developers` group by going to
- **Users > GitLab user > Add group** and selecting the `gitlab-developers`
- group from the dropdown menu. Notice that the group says _Access_, which is
- intended as part of this process.
-
- ![Jira add user to group](img/jira_add_user_to_group.png)
-
The Jira configuration is complete. Write down the new Jira username and its
password as they will be needed when [configuring GitLab in the next section](jira.md#configuring-gitlab).
diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md
index f560de427c5..0bfee3bac99 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -39,7 +39,7 @@ Click on the service links to see further configuration instructions and details
| [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 |
-| [Jenkins](https://docs.gitlab.com/ee/integration/jenkins.html) **[STARTER]** | An extendable open source continuous integration server |
+| [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 |
| [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost |
diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md
index 436129f1dbc..8b1cf1a251a 100644
--- a/doc/user/project/integrations/prometheus_library/kubernetes.md
+++ b/doc/user/project/integrations/prometheus_library/kubernetes.md
@@ -27,7 +27,7 @@ integration services must be enabled.
Prometheus needs to be deployed into the cluster and configured properly in order to gather Kubernetes metrics. GitLab supports two methods for doing so:
-- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.html#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics.
+- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.md#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics.
- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/).
## Specifying the Environment
@@ -40,7 +40,7 @@ Instead, the [Deployment](https://kubernetes.io/docs/concepts/workloads/controll
> Introduced in [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15201).
-GitLab also gathers Kubernetes metrics for [canary deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html), allowing easy comparison between the current deployed version and the canary.
+GitLab also gathers Kubernetes metrics for [canary deployments](../../canary_deployments.md), allowing easy comparison between the current deployed version and the canary.
These metrics expect the [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) name to begin with `$CI_ENVIRONMENT_SLUG-canary`, to isolate the canary metrics.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 10eb3a4f3b7..d5f28ddabc1 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -653,7 +653,33 @@ X-Gitlab-Event: Note Hook
"description": "test",
"milestone_id": null,
"state": "closed",
- "iid": 17
+ "iid": 17,
+ "labels": [
+ {
+ "id": 25,
+ "title": "Afterpod",
+ "color": "#3e8068",
+ "project_id": null,
+ "created_at": "2019-06-05T14:32:20.211Z",
+ "updated_at": "2019-06-05T14:32:20.211Z",
+ "template": false,
+ "description": null,
+ "type": "GroupLabel",
+ "group_id": 4
+ },
+ {
+ "id": 86,
+ "title": "Element",
+ "color": "#231afe",
+ "project_id": 4,
+ "created_at": "2019-06-05T14:32:20.637Z",
+ "updated_at": "2019-06-05T14:32:20.637Z",
+ "template": false,
+ "description": null,
+ "type": "ProjectLabel",
+ "group_id": null
+ }
+ ],
}
}
```
@@ -1016,7 +1042,7 @@ X-Gitlab-Event: Pipeline Hook
"email": "user@gitlab.com"
}
},
- "jobs":[
+ "builds":[
{
"id": 380,
"stage": "deploy",
diff --git a/doc/user/project/integrations/youtrack.md b/doc/user/project/integrations/youtrack.md
index a2a468b6fe4..81c148e41fd 100644
--- a/doc/user/project/integrations/youtrack.md
+++ b/doc/user/project/integrations/youtrack.md
@@ -31,8 +31,8 @@ To disable the internal issue tracker in a project:
## Referencing YouTrack issues in GitLab
Issues in YouTrack can be referenced as `<PROJECT>-<ID>`. `<PROJECT>`
-must start with a capital letter and can then be followed by capital or lower case
-letters, numbers or underscores. `<ID>` is a number. An example reference is `YT-101` or `Api_32-143`.
+must start with a letter and is followed by letters, numbers, or underscores.
+`<ID>` is a number. An example reference is `YT-101`, `Api_32-143` or `gl-030`.
References to `<PROJECT>-<ID>` in merge requests, commits, or comments are automatically linked to the YouTrack issue URL.
For more information, see the [External Issue Tracker](../../../integration/external-issue-tracker.md) documentation.
diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md
index 5e05846b77f..c2916c79876 100644
--- a/doc/user/project/issues/create_new_issue.md
+++ b/doc/user/project/issues/create_new_issue.md
@@ -67,7 +67,7 @@ or contacts to continue working._
## New issue via Service Desk **[PREMIUM]**
-Enable [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) to your project and offer email support.
+Enable [Service Desk](../service_desk.md) to your project and offer email support.
By doing so, when your customer sends a new email, a new issue can be created in
the appropriate project and followed up from there.
diff --git a/doc/user/project/issues/img/comment-or-discussion.png b/doc/user/project/issues/img/comment-or-discussion.png
new file mode 100644
index 00000000000..ccecc9fa39f
--- /dev/null
+++ b/doc/user/project/issues/img/comment-or-discussion.png
Binary files differ
diff --git a/doc/user/project/issues/img/create_mr_from_issue.png b/doc/user/project/issues/img/create_mr_from_issue.png
new file mode 100644
index 00000000000..680c42b5df0
--- /dev/null
+++ b/doc/user/project/issues/img/create_mr_from_issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/issues_main_view_numbered.jpg b/doc/user/project/issues/img/issues_main_view_numbered.jpg
deleted file mode 100644
index b4b68476d24..00000000000
--- a/doc/user/project/issues/img/issues_main_view_numbered.jpg
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/issues/img/issues_main_view_numbered.png b/doc/user/project/issues/img/issues_main_view_numbered.png
new file mode 100644
index 00000000000..16cb6b497b0
--- /dev/null
+++ b/doc/user/project/issues/img/issues_main_view_numbered.png
Binary files differ
diff --git a/doc/user/project/issues/img/reopen-issue.png b/doc/user/project/issues/img/reopen-issue.png
new file mode 100644
index 00000000000..1749be31239
--- /dev/null
+++ b/doc/user/project/issues/img/reopen-issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/report-abuse.png b/doc/user/project/issues/img/report-abuse.png
new file mode 100644
index 00000000000..f189cbf1d36
--- /dev/null
+++ b/doc/user/project/issues/img/report-abuse.png
Binary files differ
diff --git a/doc/user/project/issues/img/show-all-activity.png b/doc/user/project/issues/img/show-all-activity.png
new file mode 100644
index 00000000000..c43ba75ce25
--- /dev/null
+++ b/doc/user/project/issues/img/show-all-activity.png
Binary files differ
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index c82b7f100d2..76dc6e49bce 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -86,7 +86,9 @@ For more information, see the [Issue Data and Actions](issue_data_and_actions.md
On the Issues List, you can view all issues in the current project, or from multiple projects when opening the Issues List from the higher-level group context. Filter the issue list by [any search query](../../search/index.md#issues-and-merge-requests-per-project) and/or specific metadata, such as label(s), assignees(s), status, and more. From this view, you can also make certain changes [in bulk](../bulk_editing.md) to the displayed issues.
-For more information, see the [Issue Data and Actions](issue_data_and_actions.md) page.
+For more information on interacting with Issues, see the [Issue Data and Actions](issue_data_and_actions.md) page.
+
+For sorting by issue priority, see [Label Priority](../labels.md#label-priority).
### Issue boards
@@ -102,13 +104,13 @@ For more information, see the [Issue Boards](../issue_board.md) page.
Epics let you manage your portfolio of projects more efficiently and with less effort by tracking groups of issues that share a theme, across projects and milestones.
-For more information, see the [Epics](https://docs.gitlab.com/ee/user/group/epics/) page.
+For more information, see the [Epics](../../group/epics/index.md) page.
### Related issues **[STARTER]**
You can mark two issues as related, so that when viewing each one, the other is always listed in its Related Issues section. This can help display important context, such as past work, dependencies, or duplicates.
-For more information, see [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html).
+For more information, see [Related Issues](related_issues.md).
### Crosslinking issues
@@ -123,13 +125,13 @@ For more information, see [Crosslinking issues](crosslinking_issues.md).
- [Close an issue](closing_issues.md)
- [Move an issue](moving_issues.md)
- [Delete an issue](deleting_issues.md)
-- [Create a merge request from an issue](issue_data_and_actions.md#18-new-merge-request)
+- [Create a merge request from an issue](issue_data_and_actions.md#22-create-merge-request)
## Advanced issue management
- [Bulk edit issues](../bulk_editing.md) - From the Issues List, select multiple issues in order to change their status, assignee, milestone, or labels in bulk.
- [Import issues](csv_import.md)
-- [Export issues](https://docs.gitlab.com/ee/user/project/issues/csv_export.html) **[STARTER]**
+- [Export issues](csv_export.md) **[STARTER]**
- [Issues API](../../../api/issues.md)
- Configure an [external issue tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine,
or Bugzilla.
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index ef9fcaec3e6..da585022263 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -4,11 +4,13 @@ Please read through the [GitLab Issue Documentation](index.md) for an overview o
## Parts of an Issue
-The image below illustrates what an issue looks like:
+The image below illustrates what an issue may look like. Note that certain parts will
+look slightly different or will be absent, depending on the version of GitLab being used
+and the permissions of the user viewing the issue.
-![Issue view](img/issues_main_view_numbered.jpg)
+![Issue view](img/issues_main_view_numbered.png)
-You can find all the information on that issue on one screen.
+You can find all the information for that issue on one screen.
### Issue screen
@@ -16,157 +18,215 @@ An issue starts with its status (open or closed), followed by its author,
and includes many other functionalities, numbered in the image above to
explain what they mean, one by one.
-Many of the elements of the issue screen refresh automatically, such as the title and description, when they are changed by another user.
-Comments and system notes also appear automatically in response to various actions and content updates.
+Many of the elements of the issue screen refresh automatically, such as the title and
+description, when they are changed by another user. Comments and system notes also
+update automatically in response to various actions and content updates.
-#### 1. New Issue, close issue, edit
+#### 1. New Issue, close issue (reopen issue, report issue)
-- New issue: create a new issue in the same project
-- Close issue: close this issue
-- Edit: edit the same fields available when you create an issue.
+Clicking on **New issue** will open a new window to create a new issue in the same project.
+Clicking on **Close issue** will close this issue, but it will not be deleted. If the
+issue is already closed, you can still access it and the button will show **Reopen issue**, as shown below,
+which you can click to reopen the issue. A reopened issue is no different from any
+other issue.
+
+![Reopen Issue](img/reopen-issue.png)
+
+If you do not have rights to modify the issue, the **close issue** button will be
+replaced with **report issue**, which you can click to [submit an abuse report](../../abuse_reports.md)
+about the issue. It will also appear if you have rights to modify the issue, but only
+after it is closed.
+
+![Report Abuse](img/report-abuse.png)
#### 2. Todos
-- Add todo: add that issue to your [GitLab Todo](../../../workflow/todos.md) list
-- Mark todo as done: mark that issue as done (reflects on the Todo list)
+You can click **add todo** to add the issue to your [GitLab Todo](../../../workflow/todos.md)
+list. If it is already on your todo list, the buttom will show **mark todo as done**,
+which you can click to mark that issue as done (which will be reflected in the Todo list).
#### 3. Assignee
-Whenever someone starts to work on an issue, it can be assigned
-to that person. The assignee can be changed as much as needed.
-The idea is that the assignee is responsible for that issue until
-it's reassigned to someone else to take it from there.
+An issue can be assigned to yourself, another person, or [many people](#31-multiple-assignees-STARTER).
+The assignee(s) can be changed as much as needed. The idea is that the assignees are
+responsible for that issue until it's reassigned to someone else to take it from there.
+When assigned to someone, it will appear in their assigned issues list.
-> **Tip:**
-if a user is not member of that project, it can only be
+TIP: **Tip:**
+If a user is not member of that project, it can only be
assigned to them if they created the issue themselves.
##### 3.1. Multiple Assignees **[STARTER]**
-Often multiple people work on the same issue together,
-which can be especially difficult to track in large teams
-where there is shared ownership of an issue.
+Often multiple people work on the same issue together, which can be especially difficult
+to track in large teams where there is shared ownership of an issue.
In [GitLab Starter](https://about.gitlab.com/pricing/), you can
-assign multiple people to an issue.
+[assign multiple people](multiple_assignees_for_issues.md) to an issue.
+
+#### 4. Epic **[ULTIMATE]**
+
+You can assign issues to an [Epic](../../group/epics/index.md), which allows better
+management of groups of related issues.
-Learn more in the [Multiple Assignees documentation](multiple_assignees_for_issues.md).
+#### 5. Milestone
-#### 4. Milestone
+Select a [milestone](../milestones/index.md) to attribute that issue to.
-- Select a [milestone](../milestones/index.md) to attribute that issue to.
+#### 6. Time Tracking
-#### 5. Time Tracking
+Use [GitLab Quick Actions](../quick_actions.md) to [track estimates and time spent on issues](../../../workflow/time_tracking.md).
+You can add an [estimate of the time it will take](../../../workflow/time_tracking.md#estimates)
+to resolve the issue, and also add [the time spent](../../../workflow/time_tracking.md#time-spent)
+on the resolution of the issue.
-- Estimate time: add an estimate of the time it will take to resolve the issue.
-- Spend: add the time spent on the resolution of the issue
+#### 7. Due date
-> **Note:**
-Both estimate and spend times are set via [GitLab Quick Actions](../quick_actions.md).
+When you work on a tight schedule, it's important to have a way to set a deadline for
+implementations and for solving problems. This can be done in the [due date](due_dates.md)
+element. Due dates can be changed as many times as needed.
-Learn more in the [Time Tracking documentation](../../../workflow/time_tracking.md).
+#### 8. Labels
-#### 6. Due date
+Categorize issues by giving them [labels](../labels.md). They help to organize workflows,
+and they enable you to work with the [GitLab Issue Board](index.md#issue-boards).
-When you work on a tight schedule, it's important to
-have a way to set a deadline for implementations and for solving
-problems. This can be done in the [due date](due_dates.md) element. Due dates
-can be changed as many times as needed.
+Group Labels, which allow you to use the same labels for all projects within the same
+group, can be also given to issues. They work exactly the same, but they are immediately
+available to all projects in the group.
-#### 7. Labels
+TIP: **Tip:**
+If a label doesn't exist yet, you can click **Edit**, and it opens a dropdown menu
+from which you can select **Create new label**.
-Categorize issues by giving them [labels](../labels.md). They help to
-organize workflows, and they enable you to work with the
-[GitLab Issue Board](index.md#issue-boards).
+#### 9. Weight **[STARTER]**
-Group Labels, which allow you to use the same labels for a
-group of projects, can be also given to issues. They work exactly the same,
-but they are immediately available to all projects in the group.
+[Assign a weight](../../../workflow/issue_weight.md) to an issue.
+Larger values are used to indicate more effort is required to complete the issue. Only
+positive values or zero are allowed.
-> **Tip:**
-If a label doesn't exist yet, you can click **Edit**, and it opens a dropdown menu from which you can select **Create new label**.
+#### 10. Confidentiality
-#### 8. Weight **[STARTER]**
+You can [set an issue to be confidential](confidential_issues.md). When set, unauthorized
+users will not be able to access the issue, and will not see it listed in project
+issue boards or the issue list.
-- Assign a weight. Larger values are used to indicate more effort is required to complete the issue. Only positive values or zero are allowed.
+#### 11. Lock issue
-Learn more in the [Issue Weight documentation](https://docs.gitlab.com/ee/workflow/issue_weight.html).
+You can [lock the discussions](../../discussions/index.md#lock-discussions) in the issue,
+to prevent further comments from being added.
-#### 9. Participants
+#### 12. Participants
-- People involved in that issue (mentioned in the description or in the [discussion](../../discussions/index.md)).
+All the users involved in that issue. Either they participated in the [discussion](../../discussions/index.md),
+or were mentioned in the description or discussions.
-#### 10. Notifications
+#### 13. Notifications
-- Subscribe: if you are not a participant of the discussion on that issue, but
- want to receive notifications on each new input, subscribe to it.
-- Unsubscribe: if you are receiving notifications on that issue but no
+Click on the icon to enable/disable [notifications](../../../workflow/notifications.md#issue--epics--merge-request-events)
+for the issue. This will automatically enable if you participate in the issue in any way.
+
+- **Enable**: If you are not a participant in the discussion on that issue, but
+ want to receive notifications on each update, subscribe to it.
+- **Disable**: If you are receiving notifications for updates to that issue but no
longer want to receive them, unsubscribe from it.
-Read more in the [notifications documentation](https://docs.gitlab.com/ee/workflow/notifications.html#issue--epics--merge-request-events).
+#### 14. Reference
+
+- A quick "copy to clipboard" button for that issue's reference, which looks like `foo/bar#xxx`,
+ where `foo` is the `username` or `groupname`, `bar` is the `project-name`, and
+ `xxx` is the issue number.
+
+#### 15. Edit
+
+Clicking this icon opens the issue for editing, and you will have access to all the
+same fields as when the issue was created. This icon will not display if the user
+does not have permission to edit the issue.
+
+#### 16. Description
+
+The plain text title and description of the issue fill the top center of the issue page.
+The description fully supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm),
+allowing many formatting options.
+
+#### 17. Mentions
+
+You can mention a user or a group present in your GitLab instance with `@username` or
+`@groupname` and they will be notified via todos and email, unless they have disabled
+all notifications in their profile settings. This is controlled in the
+[notification settings](../../../workflow/notifications.md).
+
+Mentions for yourself (the current logged in user), will be highlighted in a different
+color, allowing you to easily see which comments involve you, helping you focus on
+them quickly.
+
+TIP: **Tip:**
+Avoid mentioning `@all` in issues and merge requests, as it sends an email notification
+to all the members of that project's group, which can be interpreted as spam.
+
+#### 18. Related Issues **[STARTER]**
+
+Issues that were mentioned as [related issues](related_issues.md) are listed here.
+You can also click the `+` to add more related issues.
+
+#### 19. Related Merge Requests
-#### 11. Reference
+Merge requests that were mentioned in that issue's description or in the issue discussion
+thread are listed as [related merge requests](crosslinking_issues.md#from-merge-requests) here.
+Also, if the current issue was mentioned as related in another merge request, that
+merge request will be listed here.
-- A quick "copy to clipboard" button for that issue's reference, `foo/bar#xxx`, where `foo` is the `username` or `groupname`, `bar`
- is the `project-name`, and `xxx` is the issue number.
+#### 20. Award emoji
-#### 12. Title and description
+You can award an emoji to that issue. There are shortcuts to "thumbs_up" and "thumbs_down",
+or you can click on the light gray "face" to choose a different reaction from the
+dropdown list of available [GitLab Flavored Markdown Emoji](../../markdown.md#emoji).
-- Title: a plain text title for describing the subject of the issue.
-- Description: a large text field which fully supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm),
- to describe all the details of the issue.
+TIP: **Tip:**
+Posting "+1" as a comment in a thread spams all subscribed participants of that issue,
+clutters the discussion threads, and is not recommended. Awarding an emoji is a way
+to let them know your reaction without spamming them.
-#### 13. Mentions
+#### 21. Show all activity
-- You can mention a user or a group present in your GitLab instance with
- `@username` or `@groupname` and they will be notified via todos and email, unless
- they have disabled all notifications in their profile settings.
-- Mentions for yourself (the current logged in user), will be highlighted
- in a different color, allowing you to easily see which comments involve you,
- helping you focus on them quickly.
+You can filter what is displayed in the issue history by clicking on **Show all activity**
+and selecting either **Show comments only**, which only shows discussions and hides
+updates to the issue, or **Show history only**, which hides discussions and only shows updates.
-To change your [notification settings](../../../workflow/notifications.md), navigate to
-**Profile Settings** > **Notifications** > **Global notification level**
-and choose your preference from the dropdown menu.
+![Show all activity](img/show-all-activity.png)
-> **Tip:**
-Avoid mentioning `@all` in issues and merge requests,
-as it sends an email notification
-to all the members of that project's group, which can be
-interpreted as spam.
+#### 22. Create Merge Request
-#### 14. Related Merge Requests
+Create a new branch and [WIP merge request](../merge_requests/work_in_progress_merge_requests.md)
+in one action. The branch will be named `issuenumber-title` by default, but you can
+choose any name, and GitLab will verify that it is not already in use. The merge request
+will automatically inherit the milestone and labels of the issue, and will be set to
+close the issue when it is merged.
-- Any merge requests mentioned in that issue's description
- or in the issue discussion thread.
+![Create MR from issue](img/create_mr_from_issue.png)
-#### 15. Award emoji
+Optionally, you can choose to create a [new branch](../repository/web_editor.md#create-a-new-branch-from-an-issue)
+only, named after that issue.
-- Award an emoji to that issue.
+#### 23. Issue history
-> **Tip:**
-Posting "+1" as a comment in a thread spams all subscribed
-participants of that issue. Awarding an emoji is a way to let them
-know you like it without spamming them.
+All comments and updates to the issue are tracked and listed here, but this can be
+filtered, as shown above.
-#### 16. Thread
+#### 24. Comments
-- Comments: collaborate to that issue by posting comments in its thread.
- These text fields also fully support
- [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
+Collaborate in the issue by posting comments in its thread. This text field also fully
+supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
-#### 17. Comment, start a discussion, or comment and close
+#### 25. Submit Comment, start a discussion, or comment and close
-Once you write a comment, you can either:
+Once you write a comment, you can:
-- Click "Comment" and your comment will be published.
-- Click "Start discussion": start a thread within that issue's thread to discuss specific points.
-- Click "Comment and close issue": post your comment and close that issue in one click.
+- Click **Comment** and your comment will be published.
+- Choose **Start discussion** from the dropdown list and start a new [discussion thread](../../discussions/index.md#threaded-discussions)
+ within that issue's main thread to discuss specific points. This invites other participants
+ to reply directly to your discussion, keeping related comments grouped together.
-#### 18. New Merge Request
+![Comment or discussion](img/comment-or-discussion.png)
-- Create a new merge request (with a new source branch named after the issue) in one action.
- The merge request will automatically inherit the milestone and labels of the issue. The merge
- request will automatically close that issue when it is merged.
-- Optionally, you can just create a [new branch](../repository/web_editor.md#create-a-new-branch-from-an-issue)
- named after that issue.
+You can also close the issue from here, so you don't need to scroll to the top of the issue page.
diff --git a/doc/user/project/issues/moving_issues.md b/doc/user/project/issues/moving_issues.md
index 211a651b89e..8aac2c01444 100644
--- a/doc/user/project/issues/moving_issues.md
+++ b/doc/user/project/issues/moving_issues.md
@@ -8,3 +8,28 @@ There will also be a system note added to both issues indicating where it came f
You can move an issue with the "Move issue" button at the bottom of the right-sidebar when viewing the issue.
![move issue - button](img/sidebar_move_issue.png)
+
+## Troubleshooting
+
+### Moving Issues in Bulk
+
+If you have advanced technical skills you can also bulk move all the issues from one project to another in the rails console. The below script will move all the issues from one project to another that are not in status **closed**.
+
+To access rails console run `sudo gitlab-rails console` on the GitLab server and run the below script. Please be sure to change **project**, **admin_user** and **target_project** to your values. We do also recommend [creating a backup](https://docs.gitlab.com/ee/raketasks/backup_restore.html#creating-a-backup-of-the-gitlab-system) before attempting any changes in the console.
+
+```ruby
+project = Project.find_by_full_path('full path of the project where issues are moved from')
+issues = project.issues
+admin_user = User.find_by_username('username of admin user') # make sure user has permissions to move the issues
+target_project = Project.find_by_full_path('full path of target project where issues moved to')
+
+issues.each do |issue|
+ if issue.state != "closed" && issue.moved_to.nil?
+ Issues::MoveService.new(project, admin_user).execute(issue, target_project)
+ else
+ puts "issue with id: #{issue.id} and title: #{issue.title} was not moved"
+ end
+end; nil
+
+```
+
diff --git a/doc/user/project/issues/multiple_assignees_for_issues.md b/doc/user/project/issues/multiple_assignees_for_issues.md
index 8781ebdd5b0..d1db0790d69 100644
--- a/doc/user/project/issues/multiple_assignees_for_issues.md
+++ b/doc/user/project/issues/multiple_assignees_for_issues.md
@@ -22,7 +22,7 @@ Consider a team formed by frontend developers, backend developers,
UX designers, QA testers, and a product manager working together to bring an idea to
market.
-Multiple Assignees for Issues makes collaboration smother,
+Multiple Assignees for Issues makes collaboration smoother,
and allows shared responsibilities to be clearly displayed.
All assignees are shown across your team's workflows and receive notifications (as they
would as single assignees), simplifying communication and ownership.
diff --git a/doc/user/project/issues/related_issues.md b/doc/user/project/issues/related_issues.md
index db0ab65b442..e679ebf86e6 100644
--- a/doc/user/project/issues/related_issues.md
+++ b/doc/user/project/issues/related_issues.md
@@ -1,6 +1,6 @@
# Related issues **[STARTER]**
-> [Introduced][ee-1797] in [GitLab Starter][ee] 9.4.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1797) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.4.
Related issues are a bi-directional relationship between any two issues
and appear in a block below the issue description. Issues can be across groups
@@ -35,11 +35,6 @@ will no longer appear in either issue.
![Removing a related issue](img/related_issues_remove.png)
-Please access our [permissions] page for more information.
+Please access our [permissions](../../permissions.md) page for more information.
-Additionally, you are also able to manage related issues through [our API].
-
-[ee]: https://about.gitlab.com/pricing/
-[ee-1797]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1797
-[permissions]: ../../permissions.md
-[Our API]: https://docs.gitlab.com/ee/api/issue_links.html
+Additionally, you are also able to manage related issues through [our API](../../../api/issue_links.md).
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index 8e9e9aa79cf..e5f62a3bb8d 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -32,6 +32,14 @@ An issue, epic, or merge request cannot have two scoped labels with the same key
For example, if an issue is already labeled `priority::3` and you apply the label `priority::2` to it,
`priority::3` is automatically removed.
+### Labels with multiple colon pairs
+
+If labels have multiple instances of `::`, the longest path from left to right, until the last `::`, is considered the "key" or the "scope".
+
+For example, `nested::key1::value1` and `nested::key1::value2` cannot both exist on the same issue. Adding the latter label will automatically remove the former due to the shared scope of `nested::key1`.
+
+`nested::key1::value1` and `nested::key2::value1` can both exist on the same issue, as these are considered to use two different label scopes, `nested::key1` and `nested::key2`.
+
### Workflows with scoped labels **[PREMIUM]**
Suppose you wanted a custom field in issues to track the platform operating system
diff --git a/doc/user/project/maven_packages.md b/doc/user/project/maven_packages.md
index d32d6084b38..48835a2dac7 100644
--- a/doc/user/project/maven_packages.md
+++ b/doc/user/project/maven_packages.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/project/packages/maven_repository.html'
+redirect_to: 'packages/maven_repository.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/project/packages/maven_repository.html).
+This document was moved to [another location](packages/maven_repository.md).
diff --git a/doc/user/project/merge_requests/allow_collaboration.md b/doc/user/project/merge_requests/allow_collaboration.md
index da6e6b5fd3a..e94125e658d 100644
--- a/doc/user/project/merge_requests/allow_collaboration.md
+++ b/doc/user/project/merge_requests/allow_collaboration.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Allow collaboration on merge requests across forks
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395)
@@ -70,3 +74,15 @@ Here's how the process would look like:
Note the colon (`:`) between the two branches. The above command will push the
local branch `thedude-awesome-project-update-docs` to the
`update-docs` branch of the `git@gitlab.com:thedude/awesome-project.git` repository.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/authorization_for_merge_requests.md b/doc/user/project/merge_requests/authorization_for_merge_requests.md
index 79444ee5682..0579e3568da 100644
--- a/doc/user/project/merge_requests/authorization_for_merge_requests.md
+++ b/doc/user/project/merge_requests/authorization_for_merge_requests.md
@@ -1,8 +1,12 @@
+---
+type: concepts
+---
+
# Authorization for Merge requests
There are two main ways to have a merge request flow with GitLab:
-1. Working with [protected branches] in a single repository.
+1. Working with [protected branches](../protected_branches.md) in a single repository.
1. Working with forks of an authoritative project.
## Protected branch flow
@@ -53,4 +57,14 @@ forks.
- The project need to keep their forks up to date, which requires more advanced
Git skills (managing multiple remotes).
-[protected branches]: ../protected_branches.md
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/browser_performance_testing.md b/doc/user/project/merge_requests/browser_performance_testing.md
index 65ee2e128ae..f6c4f767e3c 100644
--- a/doc/user/project/merge_requests/browser_performance_testing.md
+++ b/doc/user/project/merge_requests/browser_performance_testing.md
@@ -1,14 +1,18 @@
+---
+type: reference, howto
+---
+
# Browser Performance Testing **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3507)
in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
-## Overview
-
If your application offers a web interface and you are using
[GitLab CI/CD](../../../ci/README.md), you can quickly determine the performance
impact of pending code changes.
+## Overview
+
GitLab uses [Sitespeed.io](https://www.sitespeed.io), a free and open source
tool for measuring the performance of web sites, and has built a simple
[Sitespeed plugin](https://gitlab.com/gitlab-org/gl-performance)
@@ -52,3 +56,15 @@ Consecutive merge requests will have something to compare to and the Performance
report will be shown properly.
![Performance Widget](img/browser_performance_testing.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md
index 06b3779668b..a5c191c150f 100644
--- a/doc/user/project/merge_requests/cherry_pick_changes.md
+++ b/doc/user/project/merge_requests/cherry_pick_changes.md
@@ -1,8 +1,11 @@
-# Cherry-pick changes
+---
+type: reference, concepts
+---
-> [Introduced][ce-3514] in GitLab 8.7.
+# Cherry-pick changes
-GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick]
+GitLab implements Git's powerful feature to
+[cherry-pick any commit](https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation")
with introducing a **Cherry-pick** button in merge requests and commit details.
## Cherry-picking a merge request
@@ -18,9 +21,9 @@ where you can choose to either:
- Cherry-pick the changes directly into the selected branch.
- Create a new merge request with the cherry-picked changes.
-## Cherry-picking a Commit
+## Cherry-picking a commit
-You can cherry-pick a Commit from the Commit details page:
+You can cherry-pick a commit from the commit details page:
![Cherry-pick commit](img/cherry_pick_changes_commit.png)
@@ -39,5 +42,14 @@ mainline:
git cherry-pick -m 2 7a39eb0
```
-[ce-3514]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 "Cherry-pick button Merge Request"
-[git-cherry-pick]: https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation"
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md
index e6811b5df5e..c3c2bdd94b3 100644
--- a/doc/user/project/merge_requests/code_quality.md
+++ b/doc/user/project/merge_requests/code_quality.md
@@ -1,14 +1,16 @@
+---
+type: reference, howto
+---
+
# Code Quality **[STARTER]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1984)
in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
-## Overview
-
-If you are using [GitLab CI/CD](../../../ci/README.md), you can analyze your
+With the help of [GitLab CI/CD](../../../ci/README.md), you can analyze your
source code quality using GitLab Code Quality.
Code Quality uses [Code Climate Engines](https://codeclimate.com), which are
-free and open source. Code Quality doesn’t require a Code Climate subscription.
+free and open source. Code Quality doesn't require a Code Climate subscription.
Going a step further, GitLab can show the Code Quality report right
in the merge request widget area:
@@ -19,7 +21,7 @@ in the merge request widget area:
For instance, consider the following workflow:
-1. Your backend team member starts a new implementation for making certain feature in your app faster
+1. Your backend team member starts a new implementation for making a certain feature in your app faster
1. With Code Quality reports, they analyze how their implementation is impacting the code quality
1. The metrics show that their code degrade the quality in 10 points
1. You ask a co-worker to help them with this modification
@@ -63,20 +65,30 @@ Example:
NOTE: **Note:**
Although the Code Climate spec supports more properties, those are ignored by GitLab.
-For more information on how the Code Quality job should look like, check the
+For more information on what the Code Quality job should look like, check the
example on [analyzing a project's code quality](../../../ci/examples/code_quality.md).
GitLab then checks this report, compares the metrics between the source and target
branches, and shows the information right on the merge request.
-CAUTION: **Caution:**
If multiple jobs in a pipeline generate a code quality artifact, only the artifact from
the last created job (the job with the largest job ID) is used. To avoid confusion,
configure only one job to generate a code quality artifact.
-NOTE: **Note:**
If the Code Quality report doesn't have anything to compare to, no information
will be displayed in the merge request area. That is the case when you add the
Code Quality job in your `.gitlab-ci.yml` for the very first time.
Consecutive merge requests will have something to compare to and the Code Quality
report will be shown properly.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/code_quality_diff.md b/doc/user/project/merge_requests/code_quality_diff.md
index 890058eec6f..ccc694672a6 100644
--- a/doc/user/project/merge_requests/code_quality_diff.md
+++ b/doc/user/project/merge_requests/code_quality_diff.md
@@ -1,5 +1,5 @@
---
-redirect_from: 'https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html'
+redirect_from: 'code_quality_diff.md'
redirect_to: 'code_quality.md'
---
diff --git a/doc/user/project/merge_requests/container_scanning.md b/doc/user/project/merge_requests/container_scanning.md
index 4d41e424f4a..a062731ea35 100644
--- a/doc/user/project/merge_requests/container_scanning.md
+++ b/doc/user/project/merge_requests/container_scanning.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html'
+redirect_to: '../../application_security/container_scanning/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html).
+This document was moved to [another location](../../application_security/container_scanning/index.md).
diff --git a/doc/user/project/merge_requests/dast.md b/doc/user/project/merge_requests/dast.md
index b676c661267..98a2906e560 100644
--- a/doc/user/project/merge_requests/dast.md
+++ b/doc/user/project/merge_requests/dast.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/dast/index.html'
+redirect_to: '../../application_security/dast/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/dast/index.html).
+This document was moved to [another location](../../application_security/dast/index.md).
diff --git a/doc/user/project/merge_requests/dependency_scanning.md b/doc/user/project/merge_requests/dependency_scanning.md
index 3a8b53b425c..bdc1c355016 100644
--- a/doc/user/project/merge_requests/dependency_scanning.md
+++ b/doc/user/project/merge_requests/dependency_scanning.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html'
+redirect_to: '../../application_security/dependency_scanning/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html).
+This document was moved to [another location](../../application_security/dependency_scanning/index.md).
diff --git a/doc/user/project/merge_requests/fast_forward_merge.md b/doc/user/project/merge_requests/fast_forward_merge.md
index 3cd91a185e3..4f3c68090e4 100644
--- a/doc/user/project/merge_requests/fast_forward_merge.md
+++ b/doc/user/project/merge_requests/fast_forward_merge.md
@@ -1,21 +1,24 @@
+---
+type: reference, concepts
+---
+
# Fast-forward merge requests
-Retain a linear Git history and a way to accept merge requests without
-creating merge commits.
+Sometimes, a workflow policy might mandate a clean commit history without
+merge commits. In such cases, the fast-forward merge is the perfect candidate.
+
+With fast-forward merge requests, you can retain a linear Git history and a way
+to accept merge requests without creating merge commits.
## Overview
-When the fast-forward merge ([`--ff-only`][ffonly]) setting is enabled, no merge
-commits will be created and all merges are fast-forwarded, which means that
-merging is only allowed if the branch could be fast-forwarded.
+When the fast-forward merge
+([`--ff-only`](https://git-scm.com/docs/git-merge#git-merge---ff-only)) setting
+is enabled, no merge commits will be created and all merges are fast-forwarded,
+which means that merging is only allowed if the branch could be fast-forwarded.
When a fast-forward merge is not possible, the user is given the option to rebase.
-## Use cases
-
-Sometimes, a workflow policy might mandate a clean commit history without
-merge commits. In such cases, the fast-forward merge is the perfect candidate.
-
## Enabling fast-forward merges
1. Navigate to your project's **Settings** and search for the 'Merge method'
@@ -32,4 +35,14 @@ source branch locally before you will be able to do a fast-forward merge.
![Fast forward merge rebase locally](img/ff_merge_rebase_locally.png)
-[ffonly]: https://git-scm.com/docs/git-merge#git-merge---ff-only
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 09736c7fc7e..447b338928c 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -1,7 +1,11 @@
+---
+type: index, reference, concepts
+---
+
# Merge requests
-Merge requests allow you to exchange changes you made to source code and
-collaborate with other people on the same project.
+Merge requests allow you to visualize and collaborate on the proposed changes
+to source code that exist as commits on a given Git branch.
![Merge request view](img/merge_request.png)
@@ -33,15 +37,15 @@ With GitLab merge requests, you can:
With **[GitLab Enterprise Edition][ee]**, you can also:
-- Prepare a full review and submit it once it's ready with [Merge Request Reviews](https://docs.gitlab.com/ee/user/discussions/index.md#merge-request-reviews-premium) **[PREMIUM]**
-- View the deployment process across projects with [Multi-Project Pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipelines.md) **[PREMIUM]**
+- Prepare a full review and submit it once it's ready with [Merge Request Reviews](../../discussions/index.md#merge-request-reviews-premium) **[PREMIUM]**
+- View the deployment process across projects with [Multi-Project Pipelines](../../../ci/multi_project_pipelines.md) **[PREMIUM]**
- Request [approvals](merge_request_approvals.md) from your managers **[STARTER]**
- Analyze the impact of your changes with [Code Quality reports](code_quality.md) **[STARTER]**
-- Manage the licenses of your dependencies with [License Management](https://docs.gitlab.com/ee/user/application_security/license_management/index.md) **[ULTIMATE]**
-- Analyze your source code for vulnerabilities with [Static Application Security Testing](https://docs.gitlab.com/ee/user/application_security/sast/index.md) **[ULTIMATE]**
-- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](https://docs.gitlab.com/ee/user/application_security/dast/index.md) **[ULTIMATE]**
-- Analyze your dependencies for vulnerabilities with [Dependency Scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.md) **[ULTIMATE]**
-- Analyze your Docker images for vulnerabilities with [Container Scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.md) **[ULTIMATE]**
+- Manage the licenses of your dependencies with [License Management](../../application_security/license_management/index.md) **[ULTIMATE]**
+- Analyze your source code for vulnerabilities with [Static Application Security Testing](../../application_security/sast/index.md) **[ULTIMATE]**
+- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](../../application_security/dast/index.md) **[ULTIMATE]**
+- Analyze your dependencies for vulnerabilities with [Dependency Scanning](../../application_security/dependency_scanning/index.md) **[ULTIMATE]**
+- Analyze your Docker images for vulnerabilities with [Container Scanning](../../application_security/container_scanning/index.md) **[ULTIMATE]**
- Determine the performance impact of changes with [Browser Performance Testing](#browser-performance-testing-premium) **[PREMIUM]**
## Use cases
@@ -174,7 +178,7 @@ Start a review in order to create multiple comments on a diff and publish them o
Starting a review allows you to get all your thoughts in order and ensure you haven't missed anything
before submitting all your comments.
-[Learn more about Merge Request Reviews](https://docs.gitlab.com/ee/user/discussions/index.html#merge-request-reviews-premium)
+[Learn more about Merge Request Reviews](../../discussions/index.md#merge-request-reviews-premium)
## Squash and merge
@@ -395,7 +399,7 @@ GitLab runs the [Sitespeed.io container][sitespeed-container] and displays the d
GitLab can scan and report any vulnerabilities found in your project.
-[Read more about security reports.](https://docs.gitlab.com/ee/user/application_security/index.html)
+[Read more about security reports.](../../application_security/index.md)
## JUnit test reports
@@ -511,7 +515,7 @@ seconds and the status will update automatically.
Merge Request pipeline statuses can't be retrieved when the following occurs:
-1. A Merge Requst is created
+1. A Merge Request is created
1. The Merge Request is closed
1. Changes are made in the project
1. The Merge Request is reopened
@@ -606,6 +610,9 @@ And to check out a particular merge request:
git checkout origin/merge-requests/1
```
+all the above can be done with [git-mr] script.
+
+[git-mr]: https://gitlab.com/glensc/git-mr
[products]: https://about.gitlab.com/products/ "GitLab products page"
[protected branches]: ../protected_branches.md
[ci]: ../../../ci/README.md
diff --git a/doc/user/project/merge_requests/license_management.md b/doc/user/project/merge_requests/license_management.md
index 08704425a75..93116ebd7c6 100644
--- a/doc/user/project/merge_requests/license_management.md
+++ b/doc/user/project/merge_requests/license_management.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/license_management/index.html'
+redirect_to: '../../application_security/license_management/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/license_management/index.html).
+This document was moved to [another location](../../application_security/license_management/index.md).
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index d0291c4cef5..fd151a6df45 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -1,9 +1,25 @@
+---
+type: reference, concepts
+---
+
# Merge request approvals **[STARTER]**
> Introduced in [GitLab Enterprise Edition 7.12](https://about.gitlab.com/2015/06/22/gitlab-7-12-released/#merge-request-approvers-ee-only).
+Merge request approvals enable enforced code review by requiring specified people
+to approve a merge request before it can be unblocked for merging.
+
+## Use cases
+
+1. Enforcing review of all code that gets merged into a repository.
+2. Specifying code maintainers for an entire repository.
+3. Specifying reviewers for a given proposed code change.
+4. Specifying categories of reviewers, such as BE, FE, QA, DB, etc., for all proposed code changes.
+
+## Enabling the new approvals interface
+
NOTE: **Note:**
-If you are running a self-managed instance, the new interface shown on
+Prior to 12.0, if you are running a self-managed instance, the new interface shown on
this page will not be available unless the feature flag
`approval_rules` is enabled, which can be done from the Rails console by
instance administrators.
@@ -21,20 +37,6 @@ sudo -u git -H bin/rails console RAILS_ENV=production
Then run `Feature.enable(:approval_rules)` to enable the feature flag.
-The documentation for the older interface can be accessed
-[here](/11.7/ee/user/project/merge_requests/merge_request_approvals.html).
-
-## Overview
-
-Merge request approvals enable enforced code review by requiring specified people to approve a merge request before it can be unblocked for merging.
-
-## Use cases
-
-1. Enforcing review of all code that gets merged into a repository.
-2. Specifying code maintainers for an entire repository.
-3. Specifying reviewers for a given proposed code change.
-4. Specifying categories of reviewers, such as BE, FE, QA, DB, etc., for all proposed code changes.
-
## Editing approvals
To edit the merge request approvals:
@@ -105,7 +107,7 @@ any [eligible approver](#eligible-approvers) may approve.
The following can approve merge requests:
- Users being added as approvers at project or merge request level.
-- [Code owners](https://docs.gitlab.com/ee/user/project/code_owners.html) related to the merge request ([introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7933) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.5).
+- [Code owners](../code_owners.md) related to the merge request ([introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7933) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.5).
An individual user can be added as an approver for a project if they are a member of:
@@ -168,7 +170,7 @@ or a [failed CI/CD pipeline](merge_when_pipeline_succeeds.md).
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4418) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.9.
It is possible to require at least one approval for each entry in the
-[`CODEOWNERS` file](https://docs.gitlab.com/ee/user/project/code_owners.html) that matches a file changed in
+[`CODEOWNERS` file](../code_owners.md) that matches a file changed in
the merge request. To enable this feature:
1. Navigate to your project's **Settings > General** and expand
@@ -328,3 +330,15 @@ To filter merge requests by an individual approver, you can type (or select from
the dropdown) `approver` and select the user.
![Filter MRs by an approver](img/filter_approver_merge_requests.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index 1477e35dca8..c93c7a5fe08 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -1,3 +1,7 @@
+---
+type: reference, concepts
+---
+
# Merge when pipeline succeeds
When reviewing a merge request that looks ready to merge but still has one or
@@ -7,6 +11,8 @@ finish and remember to merge the request manually.
![Enable](img/merge_when_pipeline_succeeds_enable.png)
+## How it works
+
When you hit the "Merge When Pipeline Succeeds" button, the status of the merge
request will be updated to represent the impending merge. If you cannot wait
for the pipeline to succeed and want to merge immediately, this option is
@@ -29,9 +35,6 @@ changes to be reviewed.
## Only allow merge requests to be merged if the pipeline succeeds
-> **Note:**
-You need to have jobs configured to enable this feature.
-
You can prevent merge requests from being merged if their pipeline did not succeed
or if there are discussions to be resolved.
@@ -39,9 +42,21 @@ Navigate to your project's settings page and expand the **Merge requests** secti
In the **Merge checks** subsection, select the **Pipelines must succeed** check
box and hit **Save** for the changes to take effect.
-![Pipelines must succeed settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png)
+![Pipelines must succeed settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png)
From now on, every time the pipeline fails you will not be able to merge the
merge request from the UI, until you make all relevant jobs pass.
![Only allow merge if pipeline succeeds message](img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/resolve_conflicts.md b/doc/user/project/merge_requests/resolve_conflicts.md
index ccef2853e3f..6a0f2d18629 100644
--- a/doc/user/project/merge_requests/resolve_conflicts.md
+++ b/doc/user/project/merge_requests/resolve_conflicts.md
@@ -1,3 +1,7 @@
+---
+type: reference, concepts
+---
+
# Merge request conflict resolution
Merge conflicts occur when two branches have different changes that cannot be
@@ -66,3 +70,15 @@ Additionally, GitLab does not detect conflicts in renames away from a path. For
example, this will not create a conflict: on branch `a`, doing `git mv file1
file2`; on branch `b`, doing `git mv file1 file3`. Instead, both files will be
present in the branch after the merge request is merged.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/revert_changes.md b/doc/user/project/merge_requests/revert_changes.md
index b9102798a49..1cbbcf45400 100644
--- a/doc/user/project/merge_requests/revert_changes.md
+++ b/doc/user/project/merge_requests/revert_changes.md
@@ -1,11 +1,13 @@
-# Reverting changes
+---
+type: reference, concepts
+---
-> [Introduced][ce-1990] in GitLab 8.5.
+# Reverting changes
-GitLab implements Git's powerful feature to [revert any commit][git-revert]
-with introducing a **Revert** button in merge requests and commit details.
+You can use Git's powerful feature to [revert any commit](https://git-scm.com/docs/git-revert "Git revert documentation")
+by clicking the **Revert** button in merge requests and commit details.
-## Reverting a Merge Request
+## Reverting a merge request
NOTE: **Note:**
The **Revert** button will only be available for merge requests
@@ -30,9 +32,9 @@ create a new merge request with the revert changes.
After the merge request has been reverted, the **Revert** button will not be
available anymore.
-## Reverting a Commit
+## Reverting a commit
-You can revert a Commit from the Commit details page:
+You can revert a commit from the commit details page:
![Revert commit](img/cherry_pick_changes_commit.png)
@@ -54,5 +56,14 @@ mainline:
git revert -m 2 7a39eb0
```
-[ce-1990]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1990 "Revert button Merge Request"
-[git-revert]: https://git-scm.com/docs/git-revert "Git revert documentation"
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/sast.md b/doc/user/project/merge_requests/sast.md
index 688cc79d0f6..165290eb114 100644
--- a/doc/user/project/merge_requests/sast.md
+++ b/doc/user/project/merge_requests/sast.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/sast/index.html'
+redirect_to: '../../application_security/sast/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/sast/index.html).
+This document was moved to [another location](../../application_security/sast/index.md).
diff --git a/doc/user/project/merge_requests/sast_docker.md b/doc/user/project/merge_requests/sast_docker.md
index 4d41e424f4a..a062731ea35 100644
--- a/doc/user/project/merge_requests/sast_docker.md
+++ b/doc/user/project/merge_requests/sast_docker.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html'
+redirect_to: '../../application_security/container_scanning/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html).
+This document was moved to [another location](../../application_security/container_scanning/index.md).
diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md
index 4ff8ec3a7e6..39fd2588811 100644
--- a/doc/user/project/merge_requests/squash_and_merge.md
+++ b/doc/user/project/merge_requests/squash_and_merge.md
@@ -1,8 +1,14 @@
+---
+type: reference, concepts
+---
+
# Squash and merge
-> [Introduced][ee-1024] in [GitLab Starter][ee] 8.17, and in [GitLab Core][ce] [11.0][ce-18956].
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1024) in [GitLab Starter](https://about.gitlab.com/pricing/) 8.17.
+> - [Ported](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18956) to GitLab Core 11.0.
-Combine all commits of your merge request into one and retain a clean history.
+With squash and merge you can combine all your merge request's commits into one
+and retain a clean history.
## Overview
@@ -12,11 +18,11 @@ and then merges that commit using the merge method set for the project.
In other words, squashing a merge request turns a long list of commits:
-![List of commits from a merge request][mr-commits]
+![List of commits from a merge request](img/squash_mr_commits.png)
Into a single commit on merge:
-![A squashed commit followed by a merge commit][squashed-commit]
+![A squashed commit followed by a merge commit](img/squash_squashed_commit.png)
The squashed commit's commit message will be either:
@@ -44,20 +50,18 @@ all you have to do is enable squashing before you press merge to join
the commits in the merge request into a single commit.
This way, the history of your base branch remains clean with
-meaningful commit messages and is simpler to [revert] if necessary.
+meaningful commit messages and is simpler to [revert](revert_changes.md) if necessary.
## Enabling squash for a merge request
Anyone who can create or edit a merge request can choose for it to be squashed
on the merge request form:
-![Squash commits checkbox on edit form][squash-edit-form]
-
----
+![Squash commits checkbox on edit form](img/squash_edit_form.png)
This can then be overridden at the time of accepting the merge request:
-![Squash commits checkbox on accept merge request form][squash-mr-widget]
+![Squash commits checkbox on accept merge request form](img/squash_mr_widget.png)
## Commit metadata for squashed commits
@@ -69,19 +73,20 @@ The squashed commit has the following metadata:
## Squash and fast-forward merge
-When a project has the [fast-forward merge setting enabled][ff-merge], the merge
+When a project has the [fast-forward merge setting enabled](fast_forward_merge.md#enabling-fast-forward-merges), the merge
request must be able to be fast-forwarded without squashing in order to squash
it. This is because squashing is only available when accepting a merge request,
so a merge request may need to be rebased before squashing, even though
squashing can itself be considered equivalent to rebasing.
-[ee-1024]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1024
-[ce-18956]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18956
-[mr-commits]: img/squash_mr_commits.png
-[squashed-commit]: img/squash_squashed_commit.png
-[squash-edit-form]: img/squash_edit_form.png
-[squash-mr-widget]: img/squash_mr_widget.png
-[ff-merge]: fast_forward_merge.md#enabling-fast-forward-merges
-[ce]: https://about.gitlab.com/pricing/
-[ee]: https://about.gitlab.com/pricing/
-[revert]: revert_changes.md
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
index 70bd1e60594..fbe216c3aed 100644
--- a/doc/user/project/merge_requests/versions.md
+++ b/doc/user/project/merge_requests/versions.md
@@ -1,13 +1,8 @@
-# Merge requests versions
+---
+type: reference, concepts
+---
-> **Notes:**
->
-> - [Introduced][ce-5467] in GitLab 8.12.
-> - Comments are disabled while viewing outdated merge versions or comparing to
-> versions other than base.
-> - Merge request versions are based on push not on commit. So, if you pushed 5
-> commits in a single push, it will be a single option in the dropdown. If you
-> pushed 5 times, that will count for 5 options.
+# Merge requests versions
Every time you push to a branch that is tied to a merge request, a new version
of merge request diff is created. When you visit a merge request that contains
@@ -16,25 +11,38 @@ request diffs.
![Merge request versions](img/versions.png)
----
+## Selecting a version
By default, the latest version of changes is shown. However, you
can select an older one from version dropdown.
![Merge request versions dropdown](img/versions_dropdown.png)
----
+Merge request versions are based on push not on commit. So, if you pushed 5
+commits in a single push, it will be a single option in the dropdown. If you
+pushed 5 times, that will count for 5 options.
You can also compare the merge request version with an older one to see what has
changed since then.
![Merge request versions compare](img/versions_compare.png)
----
+Comments are disabled while viewing outdated merge versions or comparing to
+versions other than base.
Every time you push new changes to the branch, a link to compare the last
changes appears as a system note.
![Merge request versions system note](img/versions_system_note.png)
-[ce-5467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5467
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
index 6f33eb9a482..9cd868067bc 100644
--- a/doc/user/project/merge_requests/work_in_progress_merge_requests.md
+++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
@@ -1,4 +1,8 @@
-# "Work In Progress" Merge Requests
+---
+type: reference, concepts
+---
+
+# "Work In Progress" merge requests
If a merge request is not yet ready to be merged, perhaps due to continued development
or open discussions, you can prevent it from being accepted before it's ready by flagging
@@ -7,7 +11,7 @@ being merged, and it will stay disabled until the "WIP" flag has been removed.
![Blocked Accept Button](img/wip_blocked_accept_button.png)
-## Adding the "Work In Progress" flag to a Merge Request
+## Adding the "Work In Progress" flag to a merge request
There are several ways to flag a merge request as a Work In Progress:
@@ -21,7 +25,7 @@ There are several ways to flag a merge request as a Work In Progress:
source branch. This is not a toggle, and doing it again in another commit will have
no effect.
-## Removing the "Work In Progress" flag from a Merge Request
+## Removing the "Work In Progress" flag from a merge request
Similar to above, when a Merge Request is ready to be merged, you can remove the
"Work in Progress" flag in several ways:
@@ -37,10 +41,22 @@ Similar to above, when a Merge Request is ready to be merged, you can remove the
Must have at least Developer level permissions on the project for the button to
be visible.
-## Including/Excluding WIP Merge Requests when searching
+## Including/excluding WIP merge requests when searching
When viewing/searching the merge requests list, you can choose to include or exclude
WIP merge requests by adding a "WIP" filter in the search box, and choosing "Yes"
(to include) or "No" (to exclude).
![Filter WIP MRs](img/filter_wip_merge_requests.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/milestones/burndown_charts.md b/doc/user/project/milestones/burndown_charts.md
index 0ad08da8ff5..7ffeb032d7f 100644
--- a/doc/user/project/milestones/burndown_charts.md
+++ b/doc/user/project/milestones/burndown_charts.md
@@ -52,7 +52,7 @@ and select a milestone from your current ones, while for group's, access the **G
select a group, and go through **Issues > Milestones** on the sidebar.
NOTE: **Note:**
-You're able to [promote project](https://docs.gitlab.com/ee/user/project/milestones/#promoting-project-milestones-to-group-milestones) to group milestones and still see the **Burndown Chart** for them, respecting license limitations.
+You're able to [promote project](index.md#promoting-project-milestones-to-group-milestones) to group milestones and still see the **Burndown Chart** for them, respecting license limitations.
The chart indicates the project's progress throughout that milestone (for issues assigned to it).
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 0d8ee0a6cd2..6cd866b5c0d 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -138,7 +138,7 @@ For group milestones in [GitLab Premium](https://about.gitlab.com/pricing), a [b
The milestone sidebar on the milestone view shows the following:
-- Percentage complete, which is calculated as number of closed issues plus number of closed/merged merge requests divided by total number issues and merge requests.
+- Percentage complete, which is calculated as number of closed issues divided by total number of issues.
- The start date and due date.
- The total time spent on all issues that have the milestone assigned.
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index d36312c9b8d..c07c4099f22 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -28,10 +28,10 @@ The reasons to do it like that are:
and maximizing security.
With the new behavior, any job that is triggered by the user, is also marked
-with their permissions. When a user does a `git push` or changes files through
+with their read permissions. When a user does a `git push` or changes files through
the web UI, a new pipeline will be usually created. This pipeline will be marked
as created be the pusher (local push or via the UI) and any job created in this
-pipeline will have the permissions of the pusher.
+pipeline will have the read permissions of the pusher but not write permissions.
This allows us to make it really easy to evaluate the access for all projects
that have [Git submodules][gitsub] or are using container images that the pusher
@@ -67,9 +67,10 @@ Let's consider the following scenario:
## Job token
-A unique job token is generated for each job and it allows the user to
+A unique job token is generated for each job and provides the user read
access all projects that would be normally accessible to the user creating that
-job.
+job. The unique job token does not have any write permissions, but there
+is a [proposal to add support](https://gitlab.com/gitlab-org/gitlab-ce/issues/18106).
We try to make sure that this token doesn't leak by:
diff --git a/doc/user/project/operations/img/external_dashboard_link.png b/doc/user/project/operations/img/external_dashboard_link.png
new file mode 100644
index 00000000000..4fb8bce7cd0
--- /dev/null
+++ b/doc/user/project/operations/img/external_dashboard_link.png
Binary files differ
diff --git a/doc/user/project/operations/img/external_dashboard_settings.png b/doc/user/project/operations/img/external_dashboard_settings.png
new file mode 100644
index 00000000000..8dc380f01e2
--- /dev/null
+++ b/doc/user/project/operations/img/external_dashboard_settings.png
Binary files differ
diff --git a/doc/user/project/operations/index.md b/doc/user/project/operations/index.md
index 0086c15c98a..84711d1146f 100644
--- a/doc/user/project/operations/index.md
+++ b/doc/user/project/operations/index.md
@@ -9,3 +9,4 @@ your applications:
- Discover and view errors generated by your applications with [Error Tracking](error_tracking.md).
- Create, toggle, and remove [Feature Flags](feature_flags.md). **[PREMIUM]**
- [Trace](tracing.md) the performance and health of a deployed application. **[ULTIMATE]**
+- Add a [button to the Monitoring dashboard](linking_to_an_external_dashboard.md) linking directly to your existing external dashboards.
diff --git a/doc/user/project/operations/linking_to_an_external_dashboard.md b/doc/user/project/operations/linking_to_an_external_dashboard.md
new file mode 100644
index 00000000000..0371a40a217
--- /dev/null
+++ b/doc/user/project/operations/linking_to_an_external_dashboard.md
@@ -0,0 +1,19 @@
+# Linking to an external dashboard
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/57171) in GitLab 12.0.
+
+You can add a button to the Monitoring dashboard linking directly to your existing external dashboards.
+
+## Enabling the external dashboard link
+
+1. Go to **Settings > Operations** and scroll to the section titled **External dashboard**.
+
+1. Fill in the URL to your external dashboard and click **Save changes**.
+
+ ![External Dashboard Settings](img/external_dashboard_settings.png)
+
+1. There should now be a button on your
+ [Monitoring dashboard](../../../ci/environments.md#monitoring-environments) which
+ will open the URL you entered in the above step.
+
+ ![External Dashboard Link](img/external_dashboard_link.png)
diff --git a/doc/user/project/packages/maven_repository.md b/doc/user/project/packages/maven_repository.md
index 94785eb6aec..9b7af738696 100644
--- a/doc/user/project/packages/maven_repository.md
+++ b/doc/user/project/packages/maven_repository.md
@@ -12,7 +12,7 @@ project can have its own space to store its Maven artifacts.
NOTE: **Note:**
This option is available only if your GitLab administrator has
-[enabled support for the Maven repository](https://docs.gitlab.com/ee/administration/packages.html).**[PREMIUM ONLY]**
+[enabled support for the Maven repository](../../../administration/packages.md).**[PREMIUM ONLY]**
After the Packages feature is enabled, the Maven Repository will be available for
all new projects by default. To enable it for existing projects, or if you want
diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md
index 9f4c01c9a0a..2e274573434 100644
--- a/doc/user/project/packages/npm_registry.md
+++ b/doc/user/project/packages/npm_registry.md
@@ -20,7 +20,7 @@ within a subgroup is not supported yet.
NOTE: **Note:**
This option is available only if your GitLab administrator has
-[enabled support for the NPM registry](https://docs.gitlab.com/ee/administration/packages.html).**[PREMIUM ONLY]**
+[enabled support for the NPM registry](../../../administration/packages.md).**[PREMIUM ONLY]**
After the NPM registry is enabled, it will be available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md
index 87cd4941ae6..8baf41dba78 100644
--- a/doc/user/project/pages/getting_started_part_four.md
+++ b/doc/user/project/pages/getting_started_part_four.md
@@ -1,10 +1,6 @@
---
-last_updated: 2018-02-16
-author: Marcia Ramos
-author_gitlab: marcia
-level: intermediate
-article_type: user guide
-date: 2017-02-22
+last_updated: 2019-06-04
+type: reference, howto
---
# Creating and Tweaking GitLab CI/CD for GitLab Pages
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 7dbf58b5715..6d538ca2455 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -1,5 +1,6 @@
---
-last_updated: 2018-02-16
+last_updated: 2018-06-04
+type: concepts, reference
---
# Static sites and GitLab Pages domains
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index 9f2bc281f85..d585c19fc5c 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -1,10 +1,6 @@
---
-last_updated: 2018-11-19
-author: Marcia Ramos
-author_gitlab: marcia
-level: beginner
-article_type: user guide
-date: 2017-02-22
+last_updated: 2019-06-04
+type: concepts, reference, howto
---
# GitLab Pages custom domains and SSL/TLS Certificates
@@ -96,7 +92,7 @@ you need to log into your domain's admin control panel and add a DNS
`CNAME` record pointing your subdomain to your website URL
(`namespace.gitlab.io`) address.
-Notice that, despite it's a user or project website, the `CNAME`
+Note that, whether it's a user or a project website, the `CNAME`
should point to your Pages domain (`namespace.gitlab.io`),
without any `/project-name`.
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index faf51154e3b..3e50cd4887c 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -1,10 +1,6 @@
---
-last_updated: 2019-03-05
-author: Marcia Ramos
-author_gitlab: marcia
-level: beginner
-article_type: user guide
-date: 2017-02-22
+last_updated: 2019-06-04
+type: reference, howto
---
# Projects for GitLab Pages and URL structure
@@ -13,11 +9,11 @@ date: 2017-02-22
To get started with GitLab Pages, you need:
-1. A project
-1. A configuration file (`.gitlab-ci.yml`) to deploy your site
+1. A project, thus a repository to hold your website's codebase.
+1. A configuration file (`.gitlab-ci.yml`) to deploy your site.
1. A specific `job` called `pages` in the configuration file
- that will make GitLab aware that you are deploying a GitLab Pages website
-1. A `public` directory with the content of the website
+ that will make GitLab aware that you are deploying a GitLab Pages website.
+1. A `public` directory with the static content of the website.
Optional Features:
@@ -88,7 +84,7 @@ website from your project's **Settings > Pages**.
You can also take some **optional** further steps:
-- _Remove the fork relationship._ The fork relashionship is necessary to contribute back to the project you originally forked from. If you don't have any intentions to do so, you can remove it. To do so, navigate to your project's **Settings**, expand **Advanced settings**, and scroll down to **Remove fork relationship**:
+- _Remove the fork relationship._ The fork relationship is necessary to contribute back to the project you originally forked from. If you don't have any intentions to do so, you can remove it. To do so, navigate to your project's **Settings**, expand **Advanced settings**, and scroll down to **Remove fork relationship**:
![remove fork relationship](img/remove_fork_relationship.png)
@@ -140,7 +136,7 @@ where you'll find its default URL.
repository to you local computer and moving your site files into it,
you can run `git init` in your local website directory, add the
remote URL: `git remote add origin git@gitlab.com:namespace/project-name.git`,
- then add, commit, and push.
+ then add, commit, and push to GitLab.
## URLs and Baseurls
@@ -173,4 +169,4 @@ baseurl: ""
## Custom Domains
GitLab Pages supports custom domains and subdomains, served under HTTP or HTTPS.
-Please check the [next part](getting_started_part_three.md) of this series for an overview.
+See [GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md) for more information.
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 91098d51160..04bda212128 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -1,6 +1,7 @@
---
description: 'Learn how to use GitLab Pages to deploy a static website at no additional cost.'
-last_updated: 2019-03-05
+last_updated: 2019-06-04
+type: index, reference
---
# GitLab Pages
@@ -140,7 +141,7 @@ To learn more about configuration options for GitLab Pages, read the following:
| [Static websites and Pages domains](getting_started_part_one.md) | Understand what is a static website, and how GitLab Pages default domains work. |
| [Projects and URL structure](getting_started_part_two.md) | Forking projects and creating new ones from scratch, understanding URLs structure and baseurls. |
| [GitLab CI/CD for GitLab Pages](getting_started_part_four.md) | Understand how to create your own `.gitlab-ci.yml` for your site. |
-| [Exploring GitLab Pages](introduction.md) | Technical aspects, specific configuration options, custom 404 pages, limitations. |
+| [Exploring GitLab Pages](introduction.md) | Requirements, technical aspects, specific GitLab CI's configuration options, custom 404 pages, limitations, FAQ. |
|---+---|
| [Custom domains and SSL/TLS Certificates](getting_started_part_three.md) | How to add custom domains and subdomains to your website, configure DNS records and SSL/TLS certificates. |
| [CloudFlare certificates](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) | Secure your Pages site with CloudFlare certificates. |
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index a14a446aead..4fab7f79e0c 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -1,3 +1,8 @@
+---
+type: reference
+last_updated: 2018-06-04
+---
+
# Exploring GitLab Pages
This document is a user guide to explore the options and settings
@@ -10,7 +15,7 @@ To familiarize yourself with GitLab Pages first:
- Learn how to enable GitLab Pages
across your GitLab instance on the [administrator documentation](../../../administration/pages/index.md).
-## Pages requirements
+## GitLab Pages requirements
In brief, this is what you need to upload your website in GitLab Pages:
@@ -34,6 +39,99 @@ If you are using [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) to hos
Visit the [GitLab Pages group](https://gitlab.com/groups/pages) for a complete list of example projects. Contributions are very welcome.
+## Custom error codes Pages
+
+You can provide your own 403 and 404 error pages by creating the `403.html` and
+`404.html` files respectively in the root directory of the `public/` directory
+that will be included in the artifacts. Usually this is the root directory of
+your project, but that may differ depending on your static generator
+configuration.
+
+If the case of `404.html`, there are different scenarios. For example:
+
+- If you use project Pages (served under `/projectname/`) and try to access
+ `/projectname/non/existing_file`, GitLab Pages will try to serve first
+ `/projectname/404.html`, and then `/404.html`.
+- If you use user/group Pages (served under `/`) and try to access
+ `/non/existing_file` GitLab Pages will try to serve `/404.html`.
+- If you use a custom domain and try to access `/non/existing_file`, GitLab
+ Pages will try to serve only `/404.html`.
+
+## Redirects in GitLab Pages
+
+Since you cannot use any custom server configuration files, like `.htaccess` or
+any `.conf` file, if you want to redirect a page to another
+location, you can use the [HTTP meta refresh tag][metarefresh].
+
+Some static site generators provide plugins for that functionality so that you
+don't have to create and edit HTML files manually. For example, Jekyll has the
+[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from).
+
+## GitLab Pages Access Control **[CORE ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) in GitLab 11.5.
+
+NOTE: **Note:**
+GitLab Pages access control is not activated on GitLab.com. You can check its
+progress on the
+[infrastructure issue tracker](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5576).
+
+You can enable Pages access control on your project, so that only
+[members of your project](../../permissions.md#project-members-permissions)
+(at least Guest) can access your website:
+
+1. Navigate to your project's **Settings > General > Permissions**.
+1. Toggle the **Pages** button to enable the access control.
+
+ NOTE: **Note:**
+ If you don't see the toggle button, that means that it's not enabled.
+ Ask your administrator to [enable it](../../../administration/pages/index.md#access-control).
+
+1. The Pages access control dropdown allows you to set who can view pages hosted
+ with GitLab Pages, depending on your project's visibility:
+
+ - If your project is private:
+ - **Only project members**: Only project members will be able to browse the website.
+ - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
+ - If your project is internal:
+ - **Only project members**: Only project members will be able to browse the website.
+ - **Everyone with access**: Everyone logged into GitLab will be able to browse the website, no matter their project membership.
+ - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
+ - If your project is public:
+ - **Only project members**: Only project members will be able to browse the website.
+ - **Everyone with access**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
+
+1. Click **Save changes**.
+
+---
+
+The next time someone tries to access your website and the access control is
+enabled, they will be presented with a page to sign into GitLab and verify they
+can access the website.
+
+## Unpublishing your Pages
+
+If you ever feel the need to purge your Pages content, you can do so by going
+to your project's settings through the gear icon in the top right, and then
+navigating to **Pages**. Hit the **Remove pages** button and your Pages website
+will be deleted.
+
+![Remove pages](img/remove_pages.png)
+
+## Limitations
+
+When using Pages under the general domain of a GitLab instance (`*.example.io`),
+you _cannot_ use HTTPS with sub-subdomains. That means that if your
+username/groupname contains a dot, for example `foo.bar`, the domain
+`https://foo.bar.example.io` will _not_ work. This is a limitation of the
+[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you
+don't redirect HTTP to HTTPS.
+
+[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC"
+
+GitLab Pages [does **not** support group websites for subgroups](../../group/subgroups/index.md#limitations).
+You can only create the highest-level group website.
+
## Specific configuration options for Pages
Learn how to set up GitLab CI/CD for specific use cases.
@@ -208,99 +306,6 @@ NOTE: **Note:**
When `public/data/index.html` exists, it takes priority over the `public/data.html`
file for both the `/data` and `/data/` URL paths.
-### Custom error codes pages
-
-You can provide your own 403 and 404 error pages by creating the `403.html` and
-`404.html` files respectively in the root directory of the `public/` directory
-that will be included in the artifacts. Usually this is the root directory of
-your project, but that may differ depending on your static generator
-configuration.
-
-If the case of `404.html`, there are different scenarios. For example:
-
-- If you use project Pages (served under `/projectname/`) and try to access
- `/projectname/non/existing_file`, GitLab Pages will try to serve first
- `/projectname/404.html`, and then `/404.html`.
-- If you use user/group Pages (served under `/`) and try to access
- `/non/existing_file` GitLab Pages will try to serve `/404.html`.
-- If you use a custom domain and try to access `/non/existing_file`, GitLab
- Pages will try to serve only `/404.html`.
-
-### Redirects in GitLab Pages
-
-Since you cannot use any custom server configuration files, like `.htaccess` or
-any `.conf` file, if you want to redirect a page to another
-location, you can use the [HTTP meta refresh tag][metarefresh].
-
-Some static site generators provide plugins for that functionality so that you
-don't have to create and edit HTML files manually. For example, Jekyll has the
-[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from).
-
-### GitLab Pages Access Control **[CORE ONLY]**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) in GitLab 11.5.
-
-NOTE: **Note:**
-GitLab Pages access control is not activated on GitLab.com. You can check its
-progress on the
-[infrastructure issue tracker](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5576).
-
-You can enable Pages access control on your project, so that only
-[members of your project](../../permissions.md#project-members-permissions)
-(at least Guest) can access your website:
-
-1. Navigate to your project's **Settings > General > Permissions**.
-1. Toggle the **Pages** button to enable the access control.
-
- NOTE: **Note:**
- If you don't see the toggle button, that means that it's not enabled.
- Ask your administrator to [enable it](../../../administration/pages/index.md#access-control).
-
-1. The Pages access control dropdown allows you to set who can view pages hosted
- with GitLab Pages, depending on your project's visibility:
-
- - If your project is private:
- - **Only project members**: Only project members will be able to browse the website.
- - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
- - If your project is internal:
- - **Only project members**: Only project members will be able to browse the website.
- - **Everyone with access**: Everyone logged into GitLab will be able to browse the website, no matter their project membership.
- - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
- - If your project is public:
- - **Only project members**: Only project members will be able to browse the website.
- - **Everyone with access**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
-
-1. Click **Save changes**.
-
----
-
-The next time someone tries to access your website and the access control is
-enabled, they will be presented with a page to sign into GitLab and verify they
-can access the website.
-
-## Unpublishing your Pages
-
-If you ever feel the need to purge your Pages content, you can do so by going
-to your project's settings through the gear icon in the top right, and then
-navigating to **Pages**. Hit the **Remove pages** button and your Pages website
-will be deleted.
-
-![Remove pages](img/remove_pages.png)
-
-## Limitations
-
-When using Pages under the general domain of a GitLab instance (`*.example.io`),
-you _cannot_ use HTTPS with sub-subdomains. That means that if your
-username/groupname contains a dot, for example `foo.bar`, the domain
-`https://foo.bar.example.io` will _not_ work. This is a limitation of the
-[HTTP Over TLS protocol][rfc]. HTTP pages will continue to work provided you
-don't redirect HTTP to HTTPS.
-
-[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC"
-
-GitLab Pages [does **not** support group websites for subgroups](../../group/subgroups/index.md#limitations).
-You can only create the highest-level group website.
-
## Frequently Asked Questions
### Can I download my generated pages?
diff --git a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
index da1b7c59c8e..91a660c0f7a 100644
--- a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
+++ b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
@@ -1,5 +1,7 @@
---
description: "How to secure GitLab Pages websites with Let's Encrypt."
+type: howto
+last_updated: 2019-06-04
---
# Let's Encrypt for GitLab Pages
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index 629b5e1fde4..002addfc043 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -16,7 +16,7 @@
> [administration/job_artifacts](../../../administration/job_artifacts.md).
Artifacts is a list of files and directories which are attached to a job
-after it completes successfully. This feature is enabled by default in all
+after it finishes. This feature is enabled by default in all
GitLab installations.
## Defining artifacts in `.gitlab-ci.yml`
@@ -36,12 +36,14 @@ pdf:
A job named `pdf` calls the `xelatex` command in order to build a pdf file from
the latex source file `mycv.tex`. We then define the `artifacts` paths which in
turn are defined with the `paths` keyword. All paths to files and directories
-are relative to the repository that was cloned during the build. These uploaded
-artifacts will be kept in GitLab for 1 week as defined by the `expire_in`
-definition. You have the option to keep the artifacts from expiring via the
-[web interface](#browsing-artifacts). If the expiry time is not defined,
-it defaults to the [instance wide
-setting](../../admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only).
+are relative to the repository that was cloned during the build.
+
+The artifacts will be uploaded when the job succeeds by default, but can be set to upload
+when the job fails, or always, if the [`artifacts:when`](../../../ci/yaml/README.md#artifactswhen)
+parameter is used. These uploaded artifacts will be kept in GitLab for 1 week as defined
+by the `expire_in` definition. You have the option to keep the artifacts from expiring
+via the [web interface](#browsing-artifacts). If the expiry time is not defined, it defaults
+to the [instance wide setting](../../admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only).
For more examples on artifacts, follow the [artifacts reference in
`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifacts).
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 8b762307ac4..16f48c462eb 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -20,6 +20,22 @@ There are two options. Using:
The default Git strategy can be overridden by the [GIT_STRATEGY variable](../../../ci/yaml/README.md#git-strategy)
in `.gitlab-ci.yml`.
+## Git shallow clone
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28919) in GitLab 12.0.
+
+NOTE: **Note**: As of GitLab 12.0, newly created projects will automaticallyl have a default
+`git depth` value of `50`.
+
+It is possible to limit the number of changes that GitLab CI/CD will fetch when cloning
+a repository. Setting a limit to `git depth` can speed up Pipelines execution. Maximum
+allowed value is `1000`.
+
+To disable shallow clone and make GitLab CI/CD fetch all branches and tags each time,
+keep the value empty or set to `0`.
+
+This value can also be [overridden by `GIT_DEPTH`](../../../ci/large_repositories/index.md#shallow-cloning) variable in `.gitlab-ci.yml` file.
+
## Timeout
Timeout defines the maximum amount of time in minutes that a job is able run.
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 15eb862b431..1d640966013 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -35,7 +35,7 @@ discussions, and descriptions:
| `/label ~label1 ~label2` | Add label(s). Label names can also start without ~ but mixed syntax is not supported. | ✓ | ✓ |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s)| ✓ | ✓ |
| `/relabel ~label1 ~label2` | Replace label | ✓ | ✓ |
-| <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request | ✓ | ✓ |
+| <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request in the project | ✓ | ✓ |
| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate | ✓ | ✓ |
| `/remove_estimate` | Remove time estimate | ✓ | ✓ |
| <code>/spend &lt;time(1h 30m &#124; -1h 5m)&gt; &lt;date(YYYY-MM-DD)&gt;</code> | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index cb514b76a4e..6fccfd40987 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -123,7 +123,7 @@ You can live preview changes submitted to a new branch with
[Review Apps](../../../ci/review_apps/index.md).
With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request
-[approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers.
+[approval](../merge_requests/merge_request_approvals.md) from your managers.
To create, delete, and view [branches](branches/index.md) via GitLab's UI:
@@ -154,7 +154,7 @@ Via command line, you can commit multiple times before pushing.
you will trigger a pipeline per push, not per commit.
- **Skip pipelines:**
You can add to you commit message the keyword
- [`[ci skip]`](../../../ci/yaml/README.html#skipping-jobs)
+ [`[ci skip]`](../../../ci/yaml/README.md#skipping-jobs)
and GitLab CI will skip that pipeline.
- **Cross-link issues and merge requests:**
[Cross-linking](../issues/crosslinking_issues.md#from-commit-messages)
@@ -226,7 +226,7 @@ Find it under your project's **Repository > Compare**.
## Locked files **[PREMIUM]**
-Use [File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html) to
+Use [File Locking](../file_lock.md) to
lock your files to prevent any conflicting changes.
## Repository's API
diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md
index 2339759ecc8..e3d771524ce 100644
--- a/doc/user/project/repository/reducing_the_repo_size_using_git.md
+++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md
@@ -1,6 +1,6 @@
# Reducing the repository size using Git
-A GitLab Enterprise Edition administrator can set a [repository size limit][admin-repo-size]
+A GitLab Enterprise Edition administrator can set a [repository size limit](../../admin_area/settings/account_and_limit_settings.md)
which will prevent you from exceeding it.
When a project has reached its size limit, you will not be able to push to it,
@@ -14,7 +14,8 @@ move some blobs to LFS, or remove some old dependency updates from history.
Unfortunately, it's not so easy and that workflow won't work. Deleting files in
a commit doesn't actually reduce the size of the repo since the earlier commits
and blobs are still around. What you need to do is rewrite history with Git's
-[`filter-branch` option][gitscm], or a tool like the [BFG Repo-Cleaner][bfg].
+[`filter-branch` option](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#The-Nuclear-Option:-filter-branch),
+or a tool like the [BFG Repo-Cleaner](https://rtyley.github.io/bfg-repo-cleaner/).
Note that even with that method, until `git gc` runs on the GitLab side, the
"removed" commits and blobs will still be around. You also need to be able to
@@ -137,7 +138,3 @@ remove some of them, but it should not be depended on for security purposes!
```
Your repository should now be below the size limit.
-
-[admin-repo-size]: https://docs.gitlab.com/ee/user/admin_area/settings/account_and_limit_settings.html#repository-size-limit
-[bfg]: https://rtyley.github.io/bfg-repo-cleaner/
-[gitscm]: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#The-Nuclear-Option:-filter-branch
diff --git a/doc/user/project/security_dashboard.md b/doc/user/project/security_dashboard.md
index 43e910b29fe..a3da1ec97d3 100644
--- a/doc/user/project/security_dashboard.md
+++ b/doc/user/project/security_dashboard.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html'
+redirect_to: '../application_security/security_dashboard/index.md'
---
-This document was moved to [another location](https://docs.gitlab.com/ee/user/application_security/security_dashboard/index.html).
+This document was moved to [another location](../application_security/security_dashboard/index.md).
diff --git a/doc/user/project/settings/img/import_export_download_export.png b/doc/user/project/settings/img/import_export_download_export.png
index 668254073e8..ab81c87bf5f 100644
--- a/doc/user/project/settings/img/import_export_download_export.png
+++ b/doc/user/project/settings/img/import_export_download_export.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_export_button.png b/doc/user/project/settings/img/import_export_export_button.png
index 7f21bb2335b..9e368739695 100644
--- a/doc/user/project/settings/img/import_export_export_button.png
+++ b/doc/user/project/settings/img/import_export_export_button.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_mail_link.png b/doc/user/project/settings/img/import_export_mail_link.png
index 48ef42855bc..985c37650d3 100644
--- a/doc/user/project/settings/img/import_export_mail_link.png
+++ b/doc/user/project/settings/img/import_export_mail_link.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_new_project.png b/doc/user/project/settings/img/import_export_new_project.png
index b335700c5be..fc1f73c5d6e 100644
--- a/doc/user/project/settings/img/import_export_new_project.png
+++ b/doc/user/project/settings/img/import_export_new_project.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_select_file.png b/doc/user/project/settings/img/import_export_select_file.png
index e1e5e031d81..e3e1a5ef980 100644
--- a/doc/user/project/settings/img/import_export_select_file.png
+++ b/doc/user/project/settings/img/import_export_select_file.png
Binary files differ
diff --git a/doc/user/project/settings/img/settings_edit_button.png b/doc/user/project/settings/img/settings_edit_button.png
deleted file mode 100644
index 32bcda03c7e..00000000000
--- a/doc/user/project/settings/img/settings_edit_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 89008fd15b9..819515d7a4c 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -2,10 +2,11 @@
>**Notes:**
>
-> - [Introduced][ce-3050] in GitLab 8.9.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/3050) in GitLab 8.9.
> - Importing will not be possible if the import instance version differs from
> that of the exporter.
-> - For GitLab admins, please read through [Project import/export administration](../../../administration/raketasks/project_import_export.md).
+> - For GitLab admins, please read through
+> [Project import/export administration](../../../administration/raketasks/project_import_export.md).
> - For existing installations, the project import option has to be enabled in
> application settings (`/admin/application_settings`) under 'Import sources'.
> Ask your administrator if you don't see the **GitLab export** button when
@@ -14,15 +15,15 @@
> on the GitLab instance in application settings (`/admin/application_settings`)
> under 'Visibility and Access Controls'.
> - You can find some useful raketasks if you are an administrator in the
-> [import_export](../../../administration/raketasks/project_import_export.md)
-> raketask.
-> - The exports are stored in a temporary [shared directory][tmp] and are deleted
-> every 24 hours by a specific worker.
+> [import_export](../../../administration/raketasks/project_import_export.md) raketask.
+> - The exports are stored in a temporary [shared directory](../../../development/shared_files.md)
+> and are deleted every 24 hours by a specific worker.
> - Group members will get exported as project members, as long as the user has
> maintainer or admin access to the group where the exported project lives. An admin
> in the import side is required to map the users, based on email or username.
> Otherwise, a supplementary comment is left to mention the original author and
> the MRs, notes or issues will be owned by the importer.
+> - Project members with owner access will get imported as maintainers.
> - Control project Import/Export with the [API](../../../api/project_import_export.md).
> - If an imported project contains merge requests originated from forks,
> then new branches associated with such merge requests will be created
@@ -76,9 +77,9 @@ The following items will NOT be exported:
## Exporting a project and its data
-1. Go to the project settings page by clicking on **Edit Project**:
+1. Go to your project's homepage.
- ![Project settings button](img/settings_edit_button.png)
+1. Click **Settings** in the sidebar.
1. Scroll down to find the **Export project** button:
@@ -97,19 +98,14 @@ The following items will NOT be exported:
## Importing the project
-1. The new GitLab project import feature is at the far right of the import
- options when creating a New Project. Make sure you are in the right namespace
- and you have entered a project name. Click on **GitLab export**:
+1. The GitLab project import feature is the first import option when creating a
+ new project. Click on **GitLab export**:
![New project](img/import_export_new_project.png)
-1. You can see where the project will be imported to. You can now select file
- exported previously:
+1. Enter your project name and URL. Then select the file you exported previously:
![Select file](img/import_export_select_file.png)
1. Click on **Import project** to begin importing. Your newly imported project
page will appear soon.
-
-[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
-[tmp]: ../../../development/shared_files.md
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 99dd018a3ba..ba890c5ac01 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -26,6 +26,12 @@ Set up your project's access, [visibility](../../../public_access/public_access.
![projects sharing permissions](img/sharing_and_permissions_settings.png)
+If Issues are disabled, or you can't access Issues because you're not a project member, then Lables and Milestones
+links will be missing from the sidebar UI.
+
+You can still access them with direct links if you can access Merge Requests. This is deliberate, if you can see
+Issues or Merge Requests, both of which use Labels and Milestones, then you shouldn't be denied access to Labels and Milestones pages.
+
### Issue settings
Add an [issue description template](../description_templates.md#description-templates) to your project, so that every new issue will start with a custom template.
@@ -36,7 +42,7 @@ Set up your project's merge request settings:
- Set up the merge request method (merge commit, [fast-forward merge](../merge_requests/fast_forward_merge.html)).
- Merge request [description templates](../description_templates.md#description-templates).
-- Enable [merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#merge-request-approvals). **[STARTER]**
+- Enable [merge request approvals](../merge_requests/merge_request_approvals.md). **[STARTER]**
- Enable [merge only of pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md).
- Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved).
@@ -44,7 +50,7 @@ Set up your project's merge request settings:
### Service Desk **[PREMIUM]**
-Enable [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) for your project to offer customer support.
+Enable [Service Desk](../service_desk.md) for your project to offer customer support.
### Export project
@@ -100,9 +106,9 @@ Only project Owners and Admin users have the [permissions] to transfer a project
You can transfer an existing project into a [group](../../group/index.md) if:
-1. you have at least **Maintainer** [permissions] to that group
-1. you are an **Owner** of the project.
-
+1. You have at least **Maintainer** [permissions] to that group.
+1. The project is in a subgroup you own.
+1. You are at least a **Maintainer** of the project under your personal namespace.
Similarly, if you are an owner of a group, you can transfer any of its projects
under your own user.
diff --git a/doc/user/search/advanced_global_search.md b/doc/user/search/advanced_global_search.md
index 38b26f31417..f80f4183802 100644
--- a/doc/user/search/advanced_global_search.md
+++ b/doc/user/search/advanced_global_search.md
@@ -2,7 +2,7 @@
> - [Introduced][ee-109] in GitLab [Starter][ee] 8.4.
> - This is the user documentation. To install and configure Elasticsearch,
-> visit the [administrator documentation](https://docs.gitlab.com/ee/integration/elasticsearch.html).
+> visit the [administrator documentation](../../integration/elasticsearch.md).
NOTE: **Note**
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md
index 8d4aac5f502..d302cb7a809 100644
--- a/doc/user/search/advanced_search_syntax.md
+++ b/doc/user/search/advanced_search_syntax.md
@@ -3,7 +3,7 @@
> **Notes:**
> - Introduced in [GitLab Enterprise Starter][ee] 9.2
> - This is the user documentation. To install and configure Elasticsearch,
-> visit the [administrator documentation](https://docs.gitlab.com/ee/integration/elasticsearch.html).
+> visit the [administrator documentation](../../integration/elasticsearch.md).
NOTE: **Note**
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 84ab7840140..40e2486ace5 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -13,15 +13,15 @@ comments: false
- [Groups](../user/group/index.md)
- Issues - The GitLab Issue Tracker is an advanced and complete tool for
tracking the evolution of a new idea or the process of solving a problem.
- - [Exporting Issues](https://docs.gitlab.com/ee/user/project/issues/csv_export.html) **[STARTER]** Export issues as a CSV, emailed as an attachment.
+ - [Exporting Issues](../user/project/issues/csv_export.md) **[STARTER]** Export issues as a CSV, emailed as an attachment.
- [Confidential issues](../user/project/issues/confidential_issues.md)
- [Due date for issues](../user/project/issues/due_dates.md)
- [Issue Board](../user/project/issue_board.md)
- [Keyboard shortcuts](shortcuts.md)
- [File finder](file_finder.md)
-- [File lock](https://docs.gitlab.com/ee/user/project/file_lock.html) **[PREMIUM]**
+- [File lock](../user/project/file_lock.md) **[PREMIUM]**
- [Labels](../user/project/labels.md)
-- [Issue weight](https://docs.gitlab.com/ee/workflow/issue_weight.html) **[STARTER]**
+- [Issue weight](issue_weight.md) **[STARTER]**
- [Notification emails](notifications.md)
- [Projects](../user/project/index.md)
- [Project forking workflow](forking_workflow.md)
@@ -44,9 +44,9 @@ comments: false
- [Merge requests versions](../user/project/merge_requests/versions.md)
- ["Work In Progress" merge requests](../user/project/merge_requests/work_in_progress_merge_requests.md)
- [Fast-forward merge requests](../user/project/merge_requests/fast_forward_merge.md)
- - [Merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) **[STARTER]**
+ - [Merge request approvals](../user/project/merge_requests/merge_request_approvals.md) **[STARTER]**
- [Repository mirroring](repository_mirroring.md) **[STARTER]**
-- [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) **[PREMIUM]**
+- [Service Desk](../user/project/service_desk.md) **[PREMIUM]**
- [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
- [Importing from SVN, GitHub, Bitbucket, etc](importing/README.md)
- [Todos](todos.md)
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 1b9fb504b15..7d0abb93262 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -166,7 +166,7 @@ This branch is the place for any work related to this change.
NOTE: **Note:**
The name of a branch might be dictated by organizational standards.
-For example, in GitLab, any branches in GitLab EE that are equivalent to branches in GitLab CE [must end in `-ee`](https://docs.gitlab.com/ee/development/automatic_ce_ee_merge.html#cherry-picking-from-ce-to-ee).
+For example, in GitLab, any branches in GitLab EE that are equivalent to branches in GitLab CE [must end in `-ee`](../development/automatic_ce_ee_merge.md#cherry-picking-from-ce-to-ee).
When you are done or want to discuss the code, open a merge request.
A merge request is an online place to discuss the change and review the code.
diff --git a/doc/workflow/img/notification_global_settings.png b/doc/workflow/img/notification_global_settings.png
index 8a5494d16a8..72f7418f1f8 100644
--- a/doc/workflow/img/notification_global_settings.png
+++ b/doc/workflow/img/notification_global_settings.png
Binary files differ
diff --git a/doc/workflow/img/repository_mirroring_pull_settings_upper.png b/doc/workflow/img/repository_mirroring_pull_settings_upper.png
index c60354fdca7..8e15b5a0784 100644
--- a/doc/workflow/img/repository_mirroring_pull_settings_upper.png
+++ b/doc/workflow/img/repository_mirroring_pull_settings_upper.png
Binary files differ
diff --git a/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md b/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md
index 1aeab5980a3..71c73e3dffe 100644
--- a/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md
+++ b/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md
@@ -42,7 +42,7 @@ Annex to Git LFS.
### TL; DR
If you know what you are doing and want to skip the reading, this is what you
-need to do (we assume you have [git-annex enabled][annex-gitlab-use] in your
+need to do (we assume you have [git-annex enabled](../git_annex.md#using-gitlab-git-annex) in your
repository and that you have made backups in case something goes wrong).
Fire up a terminal, navigate to your Git repository and:
@@ -82,7 +82,7 @@ Make sure the you read about the [`direct` mode][annex-direct] as it contains
useful information that may fit in your use case. Note that `annex direct` is
deprecated in Git Annex version 6, so you may need to upgrade your repository
if the server also has Git Annex 6 installed. Read more in the
-[Git Annex troubleshooting tips][annex-tips] section.
+[Git Annex troubleshooting tips](../git_annex.md#troubleshooting-tips) section.
1. Backup your repository
@@ -166,44 +166,45 @@ GitLab.com), therefore, you don't need to do anything server-side.
1. First, make sure you have `git-lfs` installed locally:
- ```bash
- git lfs help
- ```
+ ```bash
+ git lfs help
+ ```
- If the terminal doesn't prompt you with a full response on `git-lfs` commands,
- [install the Git LFS client][install-lfs] first.
+ If the terminal doesn't prompt you with a full response on `git-lfs` commands,
+ [install the Git LFS client][install-lfs] first.
1. Inside the repo, run the following command to initiate LFS:
- ```bash
- git lfs install
- ```
+ ```bash
+ git lfs install
+ ```
1. Enable `git-lfs` for the group of files you want to track. You
can track specific files, all files containing the same extension, or an
entire directory:
- ```bash
- git lfs track images/01.png # per file
- git lfs track **/*.png # per extension
- git lfs track images/ # per directory
- ```
+ ```bash
+ git lfs track images/01.png # per file
+ git lfs track **/*.png # per extension
+ git lfs track images/ # per directory
+ ```
- Once you do that, run `git status` and you'll see `.gitattributes` added
- to your repo. It collects all file patterns that you chose to track via
- `git-lfs`.
+ Once you do that, run `git status` and you'll see `.gitattributes` added
+ to your repo. It collects all file patterns that you chose to track via
+ `git-lfs`.
1. Add the files, commit and push them to GitLab:
- ```bash
- git add .
- git commit -m "commit message"
- git push
- ```
+ ```bash
+ git add .
+ git commit -m "commit message"
+ git push
+ ```
- If your remote is set up with HTTP, you will be asked to enter your login
- credentials. If you have [2FA enabled][2fa], make sure to use a
- [personal access token][token] instead of your password.
+ If your remote is set up with HTTP, you will be asked to enter your login
+ credentials. If you have [2FA enabled](../../user/profile/account/two_factor_authentication.md), make sure to use a
+ [personal access token](../../user/profile/account/two_factor_authentication.md#personal-access-tokens)
+ instead of your password.
## Removing the Git Annex branches
@@ -238,18 +239,11 @@ git annex uninit
- (Blog Post) [Getting Started with Git FLS][post-1]
- (Blog Post) [Announcing LFS Support in GitLab][post-2]
- (Blog Post) [GitLab Annex Solves the Problem of Versioning Large Binaries with Git][post-3]
-- (GitLab Docs) [Git Annex][doc-1]
-- (GitLab Docs) [Git LFS][doc-2]
+- (GitLab Docs) [Git Annex](../git_annex.md)
+- (GitLab Docs) [Git LFS](manage_large_binaries_with_git_lfs.md)
-[2fa]: ../../user/profile/account/two_factor_authentication.md
-[token]: ../../user/profile/account/two_factor_authentication.html#personal-access-tokens
-[annex-tips]: ../git_annex.html#troubleshooting-tips
[annex-direct]: https://git-annex.branchable.com/direct_mode/
-[annex-gitlab-use]: ../git_annex.md#using-gitlab-git-annex
-[annex-ee]: https://docs.gitlab.com/ee/workflow/git_annex.html
[bkp-ext-drive]: https://www.thomas-krenn.com/en/wiki/Git-annex_Repository_on_an_External_Hard_Drive
-[doc-1]: https://docs.gitlab.com/ee/workflow/git_annex.html
-[doc-2]: https://docs.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html
[Git Annex]: http://git-annex.branchable.com/
[Git LFS]: https://git-lfs.github.com/
[install-lfs]: https://git-lfs.github.com/
diff --git a/doc/workflow/merge_request_approvals.md b/doc/workflow/merge_request_approvals.md
index f8a99ec3d57..bfcd8faf236 100644
--- a/doc/workflow/merge_request_approvals.md
+++ b/doc/workflow/merge_request_approvals.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html'
+redirect_to: '../user/project/merge_requests/merge_request_approvals.md'
---
-This document was moved to [user/project/merge_requests/merge_request_approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html).
+This document was moved to [another location](../user/project/merge_requests/merge_request_approvals.md).
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 0eb4f85e0ec..5d560f2e000 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -16,12 +16,16 @@ Notification settings are divided into three groups:
Each of these settings have levels of notification:
+- Global: For groups and projects, notifications as per global settings.
- Watch: Receive notifications for any activity.
-- On Mention: Receive notifications when `@mentioned` in comments.
- Participate: Receive notifications for threads you have participated in.
+- On Mention: Receive notifications when `@mentioned` in comments.
- Disabled: Turns off notifications.
- Custom: Receive notifications for custom selected events.
-- Global: For groups and projects, notifications as per global settings.
+
+> Introduced in GitLab 12.0
+
+You can also select an email address to receive notifications for each group you belong to.
### Global Settings
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index 2f8f1545b84..9772bd385ba 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -99,6 +99,7 @@ The repository will push soon. To force a push, click the appropriate button.
## Pulling from a remote repository **[STARTER]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2.
+> [Added Git LFS support](https://gitlab.com/gitlab-org/gitlab-ee/issues/10871) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.11.
You can set up a repository to automatically have its branches, tags, and commits updated from an
upstream repository.
@@ -282,10 +283,10 @@ project mirroring again by [Forcing an update](#forcing-an-update-core).
[GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
Pull mirroring uses polling to detect new branches and commits added upstream, often minutes
-afterwards. If you notify GitLab by [API](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project),
+afterwards. If you notify GitLab by [API](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter),
updates will be pulled immediately.
-For more information, see [Start the pull mirroring process for a Project](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project).
+For more information, see [Start the pull mirroring process for a Project](https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project-starter).
## Forcing an update **[CORE]**
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
index 6714d08f2f7..5068b5d4d20 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -1,6 +1,6 @@
# GitLab keyboard shortcuts
-You can see GitLab's keyboard shortcuts by using 'shift + ?'
+You can see GitLab's keyboard shortcuts by using <kbd>shift</kbd> + <kbd>?</kbd>
## Global Shortcuts
diff --git a/lib/api/api.rb b/lib/api/api.rb
index f4a96b9711b..20f8c637274 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -98,7 +98,6 @@ module API
mount ::API::Boards
mount ::API::Branches
mount ::API::BroadcastMessages
- mount ::API::CircuitBreakers
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::ContainerRegistry
diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb
deleted file mode 100644
index da756daadcc..00000000000
--- a/lib/api/circuit_breakers.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class CircuitBreakers < Grape::API
- before { authenticated_as_admin! }
-
- resource :circuit_breakers do
- params do
- requires :type,
- type: String,
- desc: "The type of circuitbreaker",
- values: ['repository_storage']
- end
- resource ':type' do
- namespace '', requirements: { type: 'repository_storage' } do
- desc 'Get all git storages' do
- detail 'This feature was introduced in GitLab 9.5'
- end
- get do
- present []
- end
-
- desc 'Get all failing git storages' do
- detail 'This feature was introduced in GitLab 9.5'
- end
- get 'failing' do
- present []
- end
-
- desc 'Reset all storage failures and open circuitbreaker' do
- detail 'This feature was introduced in GitLab 9.5'
- end
- delete do
- end
- end
- end
- end
- end
-end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 65eb9bfb87e..80913f4ca07 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -96,17 +96,27 @@ module API
end
end
optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
+ optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the commit from'
optional :author_email, type: String, desc: 'Author email for commit'
optional :author_name, type: String, desc: 'Author name for commit'
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch`'
end
post ':id/repository/commits' do
+ if params[:start_project]
+ start_project = find_project!(params[:start_project])
+
+ unless user_project.forked_from?(start_project)
+ forbidden!("Project is not included in the fork network for #{start_project.full_name}")
+ end
+ end
+
authorize_push_to_branch!(params[:branch])
attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch)
attrs[:start_branch] ||= attrs[:branch_name]
+ attrs[:start_project] = start_project if start_project
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 5928ee1657b..693172b7d08 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -206,7 +206,7 @@ module API
delete_note(noteable, params[:note_id])
end
- if Noteable::RESOLVABLE_TYPES.include?(noteable_type.to_s)
+ if Noteable.resolvable_types.include?(noteable_type.to_s)
desc "Resolve/unresolve an existing #{noteable_type.to_s.downcase} discussion" do
success Entities::Discussion
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 1a3318fe849..880b07d0a84 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -239,6 +239,7 @@ module API
end
end
+ expose :empty_repo?, as: :empty_repo
expose :archived?, as: :archived
expose :visibility
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
@@ -302,6 +303,7 @@ module API
expose :commit_count
expose :storage_size
expose :repository_size
+ expose :wiki_size
expose :lfs_objects_size
expose :build_artifacts_size, as: :job_artifacts_size
end
@@ -354,6 +356,7 @@ module API
with_options format_with: -> (value) { value.to_i } do
expose :storage_size
expose :repository_size
+ expose :wiki_size
expose :lfs_objects_size
expose :build_artifacts_size, as: :job_artifacts_size
end
@@ -573,6 +576,8 @@ module API
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue|
issue
end
+
+ expose :task_completion_status
end
class Issue < IssueBasic
@@ -721,6 +726,8 @@ module API
end
expose :squash
+
+ expose :task_completion_status
end
class MergeRequest < MergeRequestBasic
@@ -1303,6 +1310,7 @@ module API
class Variable < Grape::Entity
expose :variable_type, :key, :value
expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }
+ expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) }
end
class Pipeline < PipelineBasic
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 6893c8c40be..ec1020c7c78 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -7,34 +7,9 @@ module API
before { authenticate_non_get! }
- helpers do
- params :optional_params_ce do
- optional :description, type: String, desc: 'The description of the group'
- optional :visibility, type: String,
- values: Gitlab::VisibilityLevel.string_values,
- default: Gitlab::VisibilityLevel.string_level(
- Gitlab::CurrentSettings.current_application_settings.default_group_visibility),
- desc: 'The visibility of the group'
- optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
- optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
- optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
- end
-
- params :optional_params_ee do
- end
-
- params :optional_update_params_ee do
- end
- end
-
- include ::API::Helpers::GroupsHelpers
+ helpers Helpers::GroupsHelpers
helpers do
- params :optional_params do
- use :optional_params_ce
- use :optional_params_ee
- end
-
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 7e4539d0419..00bcf6b055b 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -445,7 +445,7 @@ module API
end
def present_carrierwave_file!(file, supports_direct_download: true)
- return not_found! unless file.exists?
+ return not_found! unless file&.exists?
if file.file_storage?
present_disk_file!(file.path, file.filename)
diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb
index ae677547760..2c33d79f6c8 100644
--- a/lib/api/helpers/groups_helpers.rb
+++ b/lib/api/helpers/groups_helpers.rb
@@ -4,6 +4,30 @@ module API
module Helpers
module GroupsHelpers
extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ce do
+ optional :description, type: String, desc: 'The description of the group'
+ optional :visibility, type: String,
+ values: Gitlab::VisibilityLevel.string_values,
+ default: Gitlab::VisibilityLevel.string_level(
+ Gitlab::CurrentSettings.current_application_settings.default_group_visibility),
+ desc: 'The visibility of the group'
+ optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
+ optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
+ optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
+ end
+
+ params :optional_params_ee do
+ end
+
+ params :optional_update_params_ee do
+ end
+
+ params :optional_params do
+ use :optional_params_ce
+ use :optional_params_ee
+ end
end
end
end
diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb
index fc66cec5341..5b7199fddb0 100644
--- a/lib/api/helpers/issues_helpers.rb
+++ b/lib/api/helpers/issues_helpers.rb
@@ -3,6 +3,14 @@
module API
module Helpers
module IssuesHelpers
+ extend Grape::API::Helpers
+
+ params :optional_issue_params_ee do
+ end
+
+ params :optional_issues_params_ee do
+ end
+
def self.update_params_at_least_one_of
[
:assignee_id,
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index 73d58ee7f37..1395ffadab9 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -19,28 +19,13 @@ module API
.non_request
end
- # rubocop: disable CodeReuse/ActiveRecord
def find_all_members_for_project(project)
- shared_group_ids = project.project_group_links.pluck(:group_id)
- project_group_ids = project.group&.self_and_ancestors&.pluck(:id)
- source_ids = [project.id, project_group_ids, shared_group_ids]
- .flatten
- .compact
- Member.includes(:user)
- .joins(user: :project_authorizations)
- .where(project_authorizations: { project_id: project.id })
- .where(source_id: source_ids)
+ MembersFinder.new(project, current_user).execute(include_invited_groups_members: true)
end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def find_all_members_for_group(group)
- source_ids = group.self_and_ancestors.pluck(:id)
- Member.includes(:user)
- .where(source_id: source_ids)
- .where(source_type: 'Namespace')
+ GroupMembersFinder.new(group).execute
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 94b58a64d26..2a9b17ad22a 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -23,7 +23,7 @@ module API
def base_request_uri
@base_request_uri ||= URI.parse(request.url).tap do |uri|
uri.host = Gitlab.config.gitlab.host
- uri.port = nil
+ uri.port = Gitlab.config.gitlab.port
end
end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index aaf32dafca4..813e46e9520 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -4,48 +4,45 @@ module API
module Helpers
module ProjectsHelpers
extend ActiveSupport::Concern
+ extend Grape::API::Helpers
- included do
- helpers do
- params :optional_project_params_ce do
- optional :description, type: String, desc: 'The description of the project'
- optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
- optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
- optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
- optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
- optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
- optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
- optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
- optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
- optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
- optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
- optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
- optional :public_builds, type: Boolean, desc: 'Perform public builds'
- optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
- optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
- optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
- optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
- optional :avatar, type: File, desc: 'Avatar image for project'
- optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
- optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
- optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
- optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
- end
+ params :optional_project_params_ce do
+ optional :description, type: String, desc: 'The description of the project'
+ optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
+ optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
+ optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
+ optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
+ optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
+ optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
+ optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
+ optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
+ optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
+ optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
+ optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
+ optional :public_builds, type: Boolean, desc: 'Perform public builds'
+ optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
+ optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
+ optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
+ optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
+ optional :avatar, type: File, desc: 'Avatar image for project'
+ optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
+ optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
+ optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
+ optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
+ end
+
+ params :optional_project_params_ee do
+ end
- if Gitlab.ee?
- params :optional_project_params_ee do
- optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins'
- optional :approvals_before_merge, type: Integer, desc: 'How many approvers should approve merge request by default'
- optional :mirror, type: Boolean, desc: 'Enables pull mirroring in a project'
- optional :mirror_trigger_builds, type: Boolean, desc: 'Pull mirroring triggers builds'
- end
- end
+ params :optional_project_params do
+ use :optional_project_params_ce
+ use :optional_project_params_ee
+ end
+
+ params :optional_filter_params_ee do
+ end
- params :optional_project_params do
- use :optional_project_params_ce
- use :optional_project_params_ee if Gitlab.ee?
- end
- end
+ params :optional_update_params_ee do
end
def self.update_params_at_least_one_of
diff --git a/lib/api/helpers/protected_branches_helpers.rb b/lib/api/helpers/protected_branches_helpers.rb
new file mode 100644
index 00000000000..0fc6841d79a
--- /dev/null
+++ b/lib/api/helpers/protected_branches_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module ProtectedBranchesHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ee do
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index 953be7f3798..44c577204b8 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -563,6 +563,12 @@ module API
name: :notify_only_broken_pipelines,
type: Boolean,
desc: 'Notify only broken pipelines'
+ },
+ {
+ required: false,
+ name: :notify_only_default_branch,
+ type: Boolean,
+ desc: 'Send notifications only for the default branch'
}
],
'pivotaltracker' => [
diff --git a/lib/api/helpers/settings_helpers.rb b/lib/api/helpers/settings_helpers.rb
new file mode 100644
index 00000000000..6441bb579ff
--- /dev/null
+++ b/lib/api/helpers/settings_helpers.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module SettingsHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ee do
+ end
+
+ def self.optional_attributes
+ [*::ApplicationSettingsHelper.visible_attributes,
+ *::ApplicationSettingsHelper.external_authorization_service_attributes,
+ :performance_bar_allowed_group_id].freeze
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/users_helpers.rb b/lib/api/helpers/users_helpers.rb
new file mode 100644
index 00000000000..56fd3c6602d
--- /dev/null
+++ b/lib/api/helpers/users_helpers.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module UsersHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ee do
+ end
+
+ params :optional_index_params_ee do
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/variables_helpers.rb b/lib/api/helpers/variables_helpers.rb
new file mode 100644
index 00000000000..78a92d0f5a6
--- /dev/null
+++ b/lib/api/helpers/variables_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module VariablesHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_params_ee do
+ end
+ end
+ end
+end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 0b4da01f3c8..039ebf92187 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -9,16 +9,6 @@ module API
before { authenticate_non_get! }
helpers do
- if Gitlab.ee?
- params :issues_params_ee do
- optional :weight, types: [Integer, String], integer_none_any: true, desc: 'The weight of the issue'
- end
-
- params :issue_params_ee do
- optional :weight, type: Integer, desc: 'The weight of the issue'
- end
- end
-
params :issues_stats_params do
optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
@@ -47,7 +37,7 @@ module API
optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
optional :confidential, type: Boolean, desc: 'Filter confidential or public issues'
- use :issues_params_ee if Gitlab.ee?
+ use :optional_issues_params_ee
end
params :issues_params do
@@ -73,7 +63,7 @@ module API
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked"
- use :issue_params_ee if Gitlab.ee?
+ use :optional_issue_params_ee
end
end
@@ -262,14 +252,9 @@ module API
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue
- # Setting updated_at only allowed for admins and owners as well
- if params[:updated_at].present?
- if current_user.admin? || user_project.owner == current_user || current_user.owned_groups.include?(user_project.owner)
- issue.system_note_timestamp = params[:updated_at]
- else
- params.delete(:updated_at)
- end
- end
+ # Setting updated_at is allowed only for admins and owners
+ params.delete(:updated_at) unless current_user.can?(:set_issue_updated_at, user_project)
+ issue.system_note_timestamp = params[:updated_at]
update_params = declared_params(include_missing: false).merge(request: request, api: true)
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index ce85772e4ed..955624404f1 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -386,9 +386,8 @@ module API
)
if merge_when_pipeline_succeeds && merge_request.head_pipeline && merge_request.head_pipeline.active?
- ::MergeRequests::MergeWhenPipelineSucceedsService
- .new(merge_request.target_project, current_user, merge_params)
- .execute(merge_request)
+ AutoMergeService.new(merge_request.target_project, current_user, merge_params)
+ .execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else
::MergeRequests::MergeService
.new(merge_request.target_project, current_user, merge_params)
@@ -429,11 +428,9 @@ module API
post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
merge_request = find_project_merge_request(params[:merge_request_iid])
- unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
+ unauthorized! unless merge_request.can_cancel_auto_merge?(current_user)
- ::MergeRequests::MergeWhenPipelineSucceedsService
- .new(merge_request.target_project, current_user)
- .cancel(merge_request)
+ AutoMergeService.new(merge_request.target_project, current_user).cancel(merge_request)
end
desc 'Rebase the merge request against its target branch' do
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index c64ec2fcc95..71891e43dcc 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -3,7 +3,8 @@
module API
class ProjectImport < Grape::API
include PaginationParams
- include Helpers::ProjectsHelpers
+
+ helpers Helpers::ProjectsHelpers
helpers do
def import_params
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index cb0106592f5..1e14c77b5be 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -6,27 +6,12 @@ module API
class Projects < Grape::API
include PaginationParams
include Helpers::CustomAttributes
- include Helpers::ProjectsHelpers
+
+ helpers Helpers::ProjectsHelpers
before { authenticate_non_get! }
helpers do
- if Gitlab.ee?
- params :optional_filter_params_ee do
- optional :wiki_checksum_failed, type: Grape::API::Boolean, default: false, desc: 'Limit by projects where wiki checksum is failed'
- optional :repository_checksum_failed, type: Grape::API::Boolean, default: false, desc: 'Limit by projects where repository checksum is failed'
- end
-
- params :optional_update_params_ee do
- optional :mirror_user_id, type: Integer, desc: 'User responsible for all the activity surrounding a pull mirror event'
- optional :only_mirror_protected_branches, type: Grape::API::Boolean, desc: 'Only mirror protected branches'
- optional :mirror_overwrites_diverged_branches, type: Grape::API::Boolean, desc: 'Pull mirror overwrites diverged branches'
- optional :import_url, type: String, desc: 'URL from which the project is imported'
- optional :packages_enabled, type: Grape::API::Boolean, desc: 'Enable project packages feature'
- optional :fallback_approvals_required, type: Integer, desc: 'Overall approvals required when no rule is present'
- end
- end
-
# EE::API::Projects would override this method
def apply_filters(projects)
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
@@ -77,7 +62,7 @@ module API
optional :with_programming_language, type: String, desc: 'Limit to repositories which use the given programming language'
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user'
- use :optional_filter_params_ee if Gitlab.ee?
+ use :optional_filter_params_ee
end
params :create_params do
@@ -296,7 +281,7 @@ module API
optional :path, type: String, desc: 'The path of the repository'
use :optional_project_params
- use :optional_update_params_ee if Gitlab.ee?
+ use :optional_update_params_ee
at_least_one_of(*Helpers::ProjectsHelpers.update_params_at_least_one_of)
end
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index f8cce1ed784..33dea25289a 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -8,6 +8,8 @@ module API
before { authorize_admin_project }
+ helpers Helpers::ProtectedBranchesHelpers
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -52,29 +54,7 @@ module API
values: ProtectedBranch::MergeAccessLevel.allowed_access_levels,
desc: 'Access levels allowed to merge (defaults: `40`, maintainer access level)'
- if Gitlab.ee?
- optional :unprotect_access_level, type: Integer,
- values: ProtectedBranch::UnprotectAccessLevel.allowed_access_levels,
- desc: 'Access levels allowed to unprotect (defaults: `40`, maintainer access level)'
-
- optional :allowed_to_push, type: Array, desc: 'An array of users/groups allowed to push' do
- optional :access_level, type: Integer, values: ProtectedBranch::PushAccessLevel.allowed_access_levels
- optional :user_id, type: Integer
- optional :group_id, type: Integer
- end
-
- optional :allowed_to_merge, type: Array, desc: 'An array of users/groups allowed to merge' do
- optional :access_level, type: Integer, values: ProtectedBranch::MergeAccessLevel.allowed_access_levels
- optional :user_id, type: Integer
- optional :group_id, type: Integer
- end
-
- optional :allowed_to_unprotect, type: Array, desc: 'An array of users/groups allowed to unprotect' do
- optional :access_level, type: Integer, values: ProtectedBranch::UnprotectAccessLevel.allowed_access_levels
- optional :user_id, type: Integer
- optional :group_id, type: Integer
- end
- end
+ use :optional_params_ee
end
# rubocop: disable CodeReuse/ActiveRecord
post ':id/protected_branches' do
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 8046acfa397..6767ef882cb 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -4,6 +4,8 @@ module API
class Settings < Grape::API
before { authenticated_as_admin! }
+ helpers Helpers::SettingsHelpers
+
helpers do
def current_settings
@current_setting ||=
@@ -136,54 +138,10 @@ module API
desc: "Restrictions on the complexity of uploaded #{type.upcase} keys. A value of #{ApplicationSetting::FORBIDDEN_KEY_VALUE} disables all #{type.upcase} keys."
end
- if Gitlab.ee?
- optional :elasticsearch_aws, type: Boolean, desc: 'Enable support for AWS hosted elasticsearch'
-
- given elasticsearch_aws: ->(val) { val } do
- optional :elasticsearch_aws_access_key, type: String, desc: 'AWS IAM access key'
- requires :elasticsearch_aws_region, type: String, desc: 'The AWS region the elasticsearch domain is configured'
- optional :elasticsearch_aws_secret_access_key, type: String, desc: 'AWS IAM secret access key'
- end
-
- optional :elasticsearch_indexing, type: Boolean, desc: 'Enable Elasticsearch indexing'
-
- given elasticsearch_indexing: ->(val) { val } do
- optional :elasticsearch_search, type: Boolean, desc: 'Enable Elasticsearch search'
- requires :elasticsearch_url, type: String, desc: 'The url to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., "http://localhost:9200, http://localhost:9201")'
- optional :elasticsearch_limit_indexing, type: Boolean, desc: 'Limit Elasticsearch to index certain namespaces and projects'
- end
-
- given elasticsearch_limit_indexing: ->(val) { val } do
- optional :elasticsearch_namespace_ids, type: Array[Integer], coerce_with: Validations::Types::LabelsList.coerce, desc: 'The namespace ids to index with Elasticsearch.'
- optional :elasticsearch_project_ids, type: Array[Integer], coerce_with: Validations::Types::LabelsList.coerce, desc: 'The project ids to index with Elasticsearch.'
- end
-
- optional :email_additional_text, type: String, desc: 'Additional text added to the bottom of every email for legal/auditing/compliance reasons'
- optional :help_text, type: String, desc: 'GitLab server administrator information'
- optional :repository_size_limit, type: Integer, desc: 'Size limit per repository (MB)'
- optional :file_template_project_id, type: Integer, desc: 'ID of project where instance-level file templates are stored.'
- optional :repository_storages, type: Array[String], desc: 'A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random.'
- optional :snowplow_enabled, type: Boolean, desc: 'Enable Snowplow'
-
- given snowplow_enabled: ->(val) { val } do
- requires :snowplow_collector_uri, type: String, desc: 'Snowplow Collector URI'
- optional :snowplow_cookie_domain, type: String, desc: 'Snowplow cookie domain'
- optional :snowplow_site_id, type: String, desc: 'Snowplow Site/Application ID'
- end
-
- optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
- end
-
- optional_attributes = [*::ApplicationSettingsHelper.visible_attributes,
- *::ApplicationSettingsHelper.external_authorization_service_attributes,
- :performance_bar_allowed_group_id]
-
- if Gitlab.ee?
- optional_attributes += EE::ApplicationSettingsHelper.possible_licensed_attributes
- end
+ use :optional_params_ee
- optional(*optional_attributes)
- at_least_one_of(*optional_attributes)
+ optional(*Helpers::SettingsHelpers.optional_attributes)
+ at_least_one_of(*Helpers::SettingsHelpers.optional_attributes)
end
put "application/settings" do
attrs = declared_params(include_missing: false)
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 2f23e33bd4a..6afeebb6890 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -15,6 +15,8 @@ module API
authenticate_non_get!
end
+ helpers Helpers::UsersHelpers
+
helpers do
# rubocop: disable CodeReuse/ActiveRecord
def find_user_by_id(params)
@@ -52,10 +54,7 @@ module API
optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
all_or_none_of :extern_uid, :provider
- if Gitlab.ee?
- optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user'
- optional :extra_shared_runners_minutes_limit, type: Integer, desc: '(admin-only) Extra pipeline minutes quota for this user'
- end
+ use :optional_params_ee
end
params :sort_params do
@@ -85,10 +84,7 @@ module API
use :sort_params
use :pagination
use :with_custom_attributes
-
- if Gitlab.ee?
- optional :skip_ldap, type: Boolean, default: false, desc: 'Skip LDAP users'
- end
+ use :optional_index_params_ee
end
# rubocop: disable CodeReuse/ActiveRecord
get do
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index a1bb21b3a06..af1d7936556 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -7,6 +7,8 @@ module API
before { authenticate! }
before { authorize! :admin_build, user_project }
+ helpers Helpers::VariablesHelpers
+
helpers do
def filter_variable_parameters(params)
# This method exists so that EE can more easily filter out certain
@@ -54,12 +56,11 @@ module API
params do
requires :key, type: String, desc: 'The key of the variable'
requires :value, type: String, desc: 'The value of the variable'
- optional :protected, type: String, desc: 'Whether the variable is protected'
+ optional :protected, type: Boolean, desc: 'Whether the variable is protected'
+ optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
- if Gitlab.ee?
- optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
- end
+ use :optional_params_ee
end
post ':id/variables' do
variable_params = declared_params(include_missing: false)
@@ -80,12 +81,11 @@ module API
params do
optional :key, type: String, desc: 'The key of the variable'
optional :value, type: String, desc: 'The value of the variable'
- optional :protected, type: String, desc: 'Whether the variable is protected'
+ optional :protected, type: Boolean, desc: 'Whether the variable is protected'
+ optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
- if Gitlab.ee?
- optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
- end
+ use :optional_params_ee
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
diff --git a/lib/banzai/commit_renderer.rb b/lib/banzai/commit_renderer.rb
index f346151a3c1..2acc9d13f07 100644
--- a/lib/banzai/commit_renderer.rb
+++ b/lib/banzai/commit_renderer.rb
@@ -2,7 +2,7 @@
module Banzai
module CommitRenderer
- ATTRIBUTES = [:description, :title].freeze
+ ATTRIBUTES = [:description, :title, :full_title].freeze
def self.render(commits, project, user = nil)
obj_renderer = ObjectRenderer.new(user: user, default_project: project)
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 44b151d01e7..0224dd8fcd1 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -363,6 +363,14 @@ module Banzai
group_ref
end
+
+ def unescape_html_entities(text)
+ CGI.unescapeHTML(text.to_s)
+ end
+
+ def escape_html_entities(text)
+ CGI.escapeHTML(text.to_s)
+ end
end
end
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 4d67140b0a1..4892668fc22 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -104,14 +104,6 @@ module Banzai
matches[:namespace] && matches[:project]
end
- def unescape_html_entities(text)
- CGI.unescapeHTML(text.to_s)
- end
-
- def escape_html_entities(text)
- CGI.escapeHTML(text.to_s)
- end
-
def object_link_title(object, matches)
# use title of wrapped element instead
nil
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index fce042e8946..08969753d75 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -51,13 +51,13 @@ module Banzai
# default implementation.
return super(text, pattern) if pattern != Milestone.reference_pattern
- text.gsub(pattern) do |match|
+ unescape_html_entities(text).gsub(pattern) do |match|
milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name])
if milestone
yield match, milestone.id, $~[:project], $~[:namespace], $~
else
- match
+ escape_html_entities(match)
end
end
end
diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb
index f4cc8beeb52..77b5053f38c 100644
--- a/lib/banzai/filter/wiki_link_filter/rewriter.rb
+++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb
@@ -4,6 +4,8 @@ module Banzai
module Filter
class WikiLinkFilter < HTML::Pipeline::Filter
class Rewriter
+ UNSAFE_SLUG_REGEXES = [/\Ajavascript:/i].freeze
+
def initialize(link_string, wiki:, slug:)
@uri = Addressable::URI.parse(link_string)
@wiki_base_path = wiki && wiki.wiki_base_path
@@ -35,6 +37,8 @@ module Banzai
# Of the form `./link`, `../link`, or similar
def apply_hierarchical_link_rules!
+ return if slug_considered_unsafe?
+
@uri = Addressable::URI.join(@slug, @uri) if @uri.to_s[0] == '.'
end
@@ -54,6 +58,10 @@ module Banzai
def repository_upload?
@uri.relative? && @uri.path.starts_with?(Wikis::CreateAttachmentService::ATTACHMENT_PATH)
end
+
+ def slug_considered_unsafe?
+ UNSAFE_SLUG_REGEXES.any? { |r| r.match?(@slug) }
+ end
end
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 30cafd11834..d67f461be57 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -25,7 +25,6 @@ module Banzai
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
- Filter::EmojiFilter,
Filter::TableOfContentsFilter,
Filter::AutolinkFilter,
Filter::ExternalLinkFilter,
@@ -34,6 +33,7 @@ module Banzai
*reference_filters,
+ Filter::EmojiFilter,
Filter::TaskListFilter,
Filter::InlineDiffFilter,
diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb
index 7db5f5e1f7d..c2da7fec7cc 100644
--- a/lib/banzai/redactor.rb
+++ b/lib/banzai/redactor.rb
@@ -70,8 +70,11 @@ module Banzai
# Build the raw <a> tag just with a link as href and content if
# it's originally a link pattern. We shouldn't return a plain text href.
original_link =
- if link_reference == 'true' && href = original_content
- %(<a href="#{href}">#{href}</a>)
+ if link_reference == 'true'
+ href = node.attr('href')
+ content = original_content
+
+ %(<a href="#{href}">#{content}</a>)
end
# The reference should be replaced by the original link's content,
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index 3f107fbbf3b..ccaf06c5d6a 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -40,6 +40,7 @@ module Gitlab
SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}.freeze
VERSION = File.read(root.join("VERSION")).strip.freeze
INSTALLATION_TYPE = File.read(root.join("INSTALLATION_TYPE")).strip.freeze
+ HTTP_PROXY_ENV_VARS = %w(http_proxy https_proxy HTTP_PROXY HTTPS_PROXY).freeze
def self.com?
# Check `gl_subdomain?` as well to keep parity with gitlab.com
@@ -66,6 +67,10 @@ module Gitlab
end
end
+ def self.http_proxy_env?
+ HTTP_PROXY_ENV_VARS.any? { |name| ENV[name] }
+ end
+
def self.process_name
return 'sidekiq' if Sidekiq.server?
return 'console' if defined?(Rails::Console)
diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb
index 36fc8061d92..72a187377d0 100644
--- a/lib/gitlab/auth/o_auth/auth_hash.rb
+++ b/lib/gitlab/auth/o_auth/auth_hash.rb
@@ -55,7 +55,7 @@ module Gitlab
private
def info
- auth_hash.info
+ auth_hash['info']
end
def get_info(key)
diff --git a/lib/gitlab/auth_logger.rb b/lib/gitlab/auth_logger.rb
new file mode 100644
index 00000000000..6d3edba02b0
--- /dev/null
+++ b/lib/gitlab/auth_logger.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class AuthLogger < Gitlab::JsonLogger
+ def self.file_name_noext
+ 'auth'
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/calculate_wiki_sizes.rb b/lib/gitlab/background_migration/calculate_wiki_sizes.rb
new file mode 100644
index 00000000000..886c41a2b9d
--- /dev/null
+++ b/lib/gitlab/background_migration/calculate_wiki_sizes.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class CalculateWikiSizes
+ def perform(start_id, stop_id)
+ ::ProjectStatistics.where(wiki_size: nil)
+ .where(id: start_id..stop_id)
+ .includes(project: [:route, :group, namespace: [:owner]]).find_each do |statistics|
+ statistics.refresh!(only: [:wiki_size])
+ rescue => e
+ Rails.logger.error "Failed to update wiki statistics. id: #{statistics.id} message: #{e.message}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
new file mode 100644
index 00000000000..0e93b2cb2fa
--- /dev/null
+++ b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # save validity time pages domain
+ class FillValidTimeForPagesDomainCertificate
+ # define PagesDomain with only needed code
+ class PagesDomain < ActiveRecord::Base
+ self.table_name = 'pages_domains'
+
+ def x509
+ return unless certificate.present?
+
+ @x509 ||= OpenSSL::X509::Certificate.new(certificate)
+ rescue OpenSSL::X509::CertificateError
+ nil
+ end
+ end
+
+ def perform(start_id, stop_id)
+ PagesDomain.where(id: start_id..stop_id).find_each do |domain|
+ if Gitlab::Database.mysql?
+ domain.update_columns(
+ certificate_valid_not_before: domain.x509&.not_before,
+ certificate_valid_not_after: domain.x509&.not_after
+ )
+ else
+ # for some reason activerecord doesn't append timezone, iso8601 forces this
+ domain.update_columns(
+ certificate_valid_not_before: domain.x509&.not_before&.iso8601,
+ certificate_valid_not_after: domain.x509&.not_after&.iso8601
+ )
+ end
+ rescue => e
+ Rails.logger.error "Failed to update pages domain certificate valid time. id: #{domain.id}, message: #{e.message}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/reset_merge_status.rb b/lib/gitlab/background_migration/reset_merge_status.rb
new file mode 100644
index 00000000000..447fec8903c
--- /dev/null
+++ b/lib/gitlab/background_migration/reset_merge_status.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Updates the range of given MRs to merge_status "unchecked", if they're opened
+ # and mergeable.
+ class ResetMergeStatus
+ def perform(from_id, to_id)
+ relation = MergeRequest.where(id: from_id..to_id,
+ state: 'opened',
+ merge_status: 'can_be_merged')
+
+ relation.update_all(merge_status: 'unchecked')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/image.rb b/lib/gitlab/ci/config/entry/image.rb
index 0beeb44c272..21c42857895 100644
--- a/lib/gitlab/ci/config/entry/image.rb
+++ b/lib/gitlab/ci/config/entry/image.rb
@@ -24,7 +24,7 @@ module Gitlab
end
entry :ports, Entry::Ports,
- description: 'Ports used expose the image'
+ description: 'Ports used to expose the image'
attributes :ports
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 290c9591b98..762532f7007 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -34,7 +34,7 @@ module Gitlab
message: 'should be on_success, on_failure, ' \
'always, manual or delayed' }
validates :dependencies, array_of_strings: true
- validates :extends, type: String
+ validates :extends, array_of_strings_or_string: true
end
validates :start_in, duration: { limit: '1 day' }, if: :delayed?
diff --git a/lib/gitlab/ci/config/entry/service.rb b/lib/gitlab/ci/config/entry/service.rb
index 084fa4047a4..8d16371e857 100644
--- a/lib/gitlab/ci/config/entry/service.rb
+++ b/lib/gitlab/ci/config/entry/service.rb
@@ -24,6 +24,9 @@ module Gitlab
validates :alias, type: String, presence: true, unless: ->(record) { record.ports.blank? }
end
+ entry :ports, Entry::Ports,
+ description: 'Ports used to expose the service'
+
def alias
value[:alias]
end
diff --git a/lib/gitlab/ci/config/extendable/entry.rb b/lib/gitlab/ci/config/extendable/entry.rb
index 7793db09d33..0001a259281 100644
--- a/lib/gitlab/ci/config/extendable/entry.rb
+++ b/lib/gitlab/ci/config/extendable/entry.rb
@@ -5,6 +5,8 @@ module Gitlab
class Config
class Extendable
class Entry
+ include Gitlab::Utils::StrongMemoize
+
InvalidExtensionError = Class.new(Extendable::ExtensionError)
CircularDependencyError = Class.new(Extendable::ExtensionError)
NestingTooDeepError = Class.new(Extendable::ExtensionError)
@@ -28,34 +30,46 @@ module Gitlab
end
def value
- @value ||= @context.fetch(@key)
+ strong_memoize(:value) do
+ @context.fetch(@key)
+ end
end
- def base_hash!
- @base ||= Extendable::Entry
- .new(extends_key, @context, self)
- .extend!
+ def base_hashes!
+ strong_memoize(:base_hashes) do
+ extends_keys.map do |key|
+ Extendable::Entry
+ .new(key, @context, self)
+ .extend!
+ end
+ end
end
- def extends_key
- value.fetch(:extends).to_s.to_sym if extensible?
+ def extends_keys
+ strong_memoize(:extends_keys) do
+ next unless extensible?
+
+ Array(value.fetch(:extends)).map(&:to_s).map(&:to_sym)
+ end
end
def ancestors
- @ancestors ||= Array(@parent&.ancestors) + Array(@parent&.key)
+ strong_memoize(:ancestors) do
+ Array(@parent&.ancestors) + Array(@parent&.key)
+ end
end
def extend!
return value unless extensible?
- if unknown_extension?
+ if unknown_extensions.any?
raise Entry::InvalidExtensionError,
- "#{key}: unknown key in `extends`"
+ "#{key}: unknown keys in `extends` (#{show_keys(unknown_extensions)})"
end
- if invalid_base?
+ if invalid_bases.any?
raise Entry::InvalidExtensionError,
- "#{key}: invalid base hash in `extends`"
+ "#{key}: invalid base hashes in `extends` (#{show_keys(invalid_bases)})"
end
if nesting_too_deep?
@@ -68,11 +82,18 @@ module Gitlab
"#{key}: circular dependency detected in `extends`"
end
- @context[key] = base_hash!.deep_merge(value)
+ merged = {}
+ base_hashes!.each { |h| merged.deep_merge!(h) }
+
+ @context[key] = merged.deep_merge!(value)
end
private
+ def show_keys(keys)
+ keys.join(', ')
+ end
+
def nesting_too_deep?
ancestors.count > MAX_NESTING_LEVELS
end
@@ -81,12 +102,16 @@ module Gitlab
ancestors.include?(key)
end
- def unknown_extension?
- !@context.key?(extends_key)
+ def unknown_extensions
+ strong_memoize(:unknown_extensions) do
+ extends_keys.reject { |key| @context.key?(key) }
+ end
end
- def invalid_base?
- !@context[extends_key].is_a?(Hash)
+ def invalid_bases
+ strong_memoize(:invalid_bases) do
+ extends_keys.reject { |key| @context[key].is_a?(Hash) }
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/and.rb b/lib/gitlab/ci/pipeline/expression/lexeme/and.rb
new file mode 100644
index 00000000000..54a0e2ad9dd
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/and.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Expression
+ module Lexeme
+ class And < Lexeme::Operator
+ PATTERN = /&&/.freeze
+
+ def evaluate(variables = {})
+ @left.evaluate(variables) && @right.evaluate(variables)
+ end
+
+ def self.build(_value, behind, ahead)
+ new(behind, ahead)
+ end
+
+ def self.precedence
+ 11 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/base.rb b/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
index 70c774416f6..7ebd2e25398 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/base.rb
@@ -15,10 +15,14 @@ module Gitlab
end
def self.scan(scanner)
- if scanner.scan(self::PATTERN)
+ if scanner.scan(pattern)
Expression::Token.new(scanner.matched, self)
end
end
+
+ def self.pattern
+ self::PATTERN
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb b/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb
index 668e85f5b9e..62f4c14f597 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/equals.rb
@@ -8,11 +8,6 @@ module Gitlab
class Equals < Lexeme::Operator
PATTERN = /==/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
@left.evaluate(variables) == @right.evaluate(variables)
end
@@ -20,6 +15,10 @@ module Gitlab
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
index cd17bc4d78b..ecfab627226 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
@@ -8,21 +8,36 @@ module Gitlab
class Matches < Lexeme::Operator
PATTERN = /=~/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
text = @left.evaluate(variables)
regexp = @right.evaluate(variables)
regexp.scan(text.to_s).any?
+
+ if ci_variables_complex_expressions?
+ # return offset of first match, or nil if no matches
+ if match = regexp.scan(text.to_s).first
+ text.to_s.index(match)
+ end
+ else
+ # return true or false
+ regexp.scan(text.to_s).any?
+ end
end
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+
+ private
+
+ def ci_variables_complex_expressions?
+ Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
index 5fcc9406cc8..8166bcd5730 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_equals.rb
@@ -8,11 +8,6 @@ module Gitlab
class NotEquals < Lexeme::Operator
PATTERN = /!=/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
@left.evaluate(variables) != @right.evaluate(variables)
end
@@ -20,6 +15,10 @@ module Gitlab
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
index 14544d33e25..831c27fa0ea 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
@@ -8,11 +8,6 @@ module Gitlab
class NotMatches < Lexeme::Operator
PATTERN = /\!~/.freeze
- def initialize(left, right)
- @left = left
- @right = right
- end
-
def evaluate(variables = {})
text = @left.evaluate(variables)
regexp = @right.evaluate(variables)
@@ -23,6 +18,10 @@ module Gitlab
def self.build(_value, behind, ahead)
new(behind, ahead)
end
+
+ def self.precedence
+ 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb b/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb
index 3ebceb92eb7..3ddab7800c8 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/operator.rb
@@ -6,9 +6,29 @@ module Gitlab
module Expression
module Lexeme
class Operator < Lexeme::Base
+ # This operator class is design to handle single operators that take two
+ # arguments. Expression::Parser was originally designed to read infix operators,
+ # and so the two operands are called "left" and "right" here. If we wish to
+ # implement an Operator that takes a greater or lesser number of arguments, a
+ # structural change or additional Operator superclass will likely be needed.
+
+ OperatorError = Class.new(Expression::ExpressionError)
+
+ def initialize(left, right)
+ raise OperatorError, 'Invalid left operand' unless left.respond_to? :evaluate
+ raise OperatorError, 'Invalid right operand' unless right.respond_to? :evaluate
+
+ @left = left
+ @right = right
+ end
+
def self.type
:operator
end
+
+ def self.precedence
+ raise NotImplementedError
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/or.rb b/lib/gitlab/ci/pipeline/expression/lexeme/or.rb
new file mode 100644
index 00000000000..807876f905a
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/or.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Expression
+ module Lexeme
+ class Or < Lexeme::Operator
+ PATTERN = /\|\|/.freeze
+
+ def evaluate(variables = {})
+ @left.evaluate(variables) || @right.evaluate(variables)
+ end
+
+ def self.build(_value, behind, ahead)
+ new(behind, ahead)
+ end
+
+ def self.precedence
+ 12 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
index 2b719c9c6fc..e4cf360a1c1 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
@@ -8,10 +8,11 @@ module Gitlab
require_dependency 're2'
class Pattern < Lexeme::Value
- PATTERN = %r{^/.+/[ismU]*$}.freeze
+ PATTERN = %r{^/.+/[ismU]*$}.freeze
+ NEW_PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze
def initialize(regexp)
- @value = regexp
+ @value = self.class.eager_matching_with_escape_characters? ? regexp.gsub(/\\\//, '/') : regexp
unless Gitlab::UntrustedRegexp::RubySyntax.valid?(@value)
raise Lexer::SyntaxError, 'Invalid regular expression!'
@@ -24,9 +25,17 @@ module Gitlab
raise Expression::RuntimeError, 'Invalid regular expression!'
end
+ def self.pattern
+ eager_matching_with_escape_characters? ? NEW_PATTERN : PATTERN
+ end
+
def self.build(string)
new(string)
end
+
+ def self.eager_matching_with_escape_characters?
+ Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb
index e14edfae51d..22c210ae26b 100644
--- a/lib/gitlab/ci/pipeline/expression/lexer.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexer.rb
@@ -20,6 +20,19 @@ module Gitlab
Expression::Lexeme::NotMatches
].freeze
+ NEW_LEXEMES = [
+ Expression::Lexeme::Variable,
+ Expression::Lexeme::String,
+ Expression::Lexeme::Pattern,
+ Expression::Lexeme::Null,
+ Expression::Lexeme::Equals,
+ Expression::Lexeme::Matches,
+ Expression::Lexeme::NotEquals,
+ Expression::Lexeme::NotMatches,
+ Expression::Lexeme::And,
+ Expression::Lexeme::Or
+ ].freeze
+
MAX_TOKENS = 100
def initialize(statement, max_tokens: MAX_TOKENS)
@@ -45,7 +58,7 @@ module Gitlab
return tokens if @scanner.eos?
- lexeme = LEXEMES.find do |type|
+ lexeme = available_lexemes.find do |type|
type.scan(@scanner).tap do |token|
tokens.push(token) if token.present?
end
@@ -58,6 +71,10 @@ module Gitlab
raise Lexer::SyntaxError, 'Too many tokens!'
end
+
+ def available_lexemes
+ Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true) ? NEW_LEXEMES : LEXEMES
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/parser.rb b/lib/gitlab/ci/pipeline/expression/parser.rb
index ed184309ab4..589bf32a4d7 100644
--- a/lib/gitlab/ci/pipeline/expression/parser.rb
+++ b/lib/gitlab/ci/pipeline/expression/parser.rb
@@ -5,17 +5,30 @@ module Gitlab
module Pipeline
module Expression
class Parser
+ ParseError = Class.new(Expression::ExpressionError)
+
def initialize(tokens)
@tokens = tokens.to_enum
@nodes = []
end
- ##
- # This produces a reverse descent parse tree.
- #
- # It currently does not support precedence of operators.
- #
def tree
+ if Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
+ rpn_parse_tree
+ else
+ reverse_descent_parse_tree
+ end
+ end
+
+ def self.seed(statement)
+ new(Expression::Lexer.new(statement).tokens)
+ end
+
+ private
+
+ # This produces a reverse descent parse tree.
+ # It does not support precedence of operators.
+ def reverse_descent_parse_tree
while token = @tokens.next
case token.type
when :operator
@@ -32,8 +45,51 @@ module Gitlab
@nodes.last || Lexeme::Null.new
end
- def self.seed(statement)
- new(Expression::Lexer.new(statement).tokens)
+ def rpn_parse_tree
+ results = []
+
+ tokens_rpn.each do |token|
+ case token.type
+ when :value
+ results.push(token.build)
+ when :operator
+ right_operand = results.pop
+ left_operand = results.pop
+
+ token.build(left_operand, right_operand).tap do |res|
+ results.push(res)
+ end
+ else
+ raise ParseError, 'Unprocessable token found in parse tree'
+ end
+ end
+
+ raise ParseError, 'Unreachable nodes in parse tree' if results.count > 1
+ raise ParseError, 'Empty parse tree' if results.count < 1
+
+ results.pop
+ end
+
+ # Parse the expression into Reverse Polish Notation
+ # (See: Shunting-yard algorithm)
+ def tokens_rpn
+ output = []
+ operators = []
+
+ @tokens.each do |token|
+ case token.type
+ when :value
+ output.push(token)
+ when :operator
+ if operators.any? && token.lexeme.precedence >= operators.last.lexeme.precedence
+ output.push(operators.pop)
+ end
+
+ operators.push(token)
+ end
+ end
+
+ output.concat(operators.reverse)
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb
index ab5ae9caeea..0e81e1bd34c 100644
--- a/lib/gitlab/ci/pipeline/expression/statement.rb
+++ b/lib/gitlab/ci/pipeline/expression/statement.rb
@@ -7,27 +7,6 @@ module Gitlab
class Statement
StatementError = Class.new(Expression::ExpressionError)
- GRAMMAR = [
- # presence matchers
- %w[variable],
-
- # positive matchers
- %w[variable equals string],
- %w[variable equals variable],
- %w[variable equals null],
- %w[string equals variable],
- %w[null equals variable],
- %w[variable matches pattern],
-
- # negative matchers
- %w[variable notequals string],
- %w[variable notequals variable],
- %w[variable notequals null],
- %w[string notequals variable],
- %w[null notequals variable],
- %w[variable notmatches pattern]
- ].freeze
-
def initialize(statement, variables = {})
@lexer = Expression::Lexer.new(statement)
@variables = variables.with_indifferent_access
@@ -36,10 +15,6 @@ module Gitlab
def parse_tree
raise StatementError if @lexer.lexemes.empty?
- unless GRAMMAR.find { |syntax| syntax == @lexer.lexemes }
- raise StatementError, 'Unknown pipeline expression!'
- end
-
Expression::Parser.new(@lexer.tokens).tree
end
@@ -54,6 +29,7 @@ module Gitlab
end
def valid?
+ evaluate
parse_tree.is_a?(Lexeme::Base)
rescue Expression::ExpressionError
false
diff --git a/lib/gitlab/ci/templates/.yamllint b/lib/gitlab/ci/templates/.yamllint
new file mode 100644
index 00000000000..669c8646ff2
--- /dev/null
+++ b/lib/gitlab/ci/templates/.yamllint
@@ -0,0 +1,5 @@
+extends: default
+
+rules:
+ line-length: disable
+ document-start: disable
diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
index 120272200c6..2ca6e73a803 100644
--- a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
@@ -54,7 +54,7 @@ ensureContainer:
- rm -f android-signing-keystore.jks || true
artifacts:
paths:
- - app/build/outputs
+ - app/build/outputs
buildDebug:
extends: .build_job
@@ -116,6 +116,6 @@ promoteProduction:
# We only allow production promotion on `master` because
# it has its own production scoped secret variables
only:
- - master
+ - master
script:
- bundle exec fastlane promote_beta_to_production
diff --git a/lib/gitlab/ci/templates/Android.gitlab-ci.yml b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
index 2be7f933462..b7194110002 100644
--- a/lib/gitlab/ci/templates/Android.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
@@ -6,7 +6,7 @@ image: openjdk:8-jdk
variables:
ANDROID_COMPILE_SDK: "28"
ANDROID_BUILD_TOOLS: "28.0.2"
- ANDROID_SDK_TOOLS: "4333796"
+ ANDROID_SDK_TOOLS: "4333796"
before_script:
- apt-get --quiet update --yes
@@ -35,7 +35,7 @@ assembleDebug:
- ./gradlew assembleDebug
artifacts:
paths:
- - app/build/outputs/
+ - app/build/outputs/
debugTests:
stage: test
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 939112e6e41..65a6630365d 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -50,8 +50,8 @@ variables:
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
POSTGRES_VERSION: 9.6.2
- KUBERNETES_VERSION: 1.11.9
- HELM_VERSION: 2.13.1
+ KUBERNETES_VERSION: 1.11.10
+ HELM_VERSION: 2.14.0
DOCKER_DRIVER: overlay2
@@ -60,7 +60,7 @@ variables:
stages:
- build
- test
- - deploy # dummy stage to follow the template guidelines
+ - deploy # dummy stage to follow the template guidelines
- review
- dast
- staging
@@ -89,4 +89,4 @@ include:
dast:
except:
refs:
- - master \ No newline at end of file
+ - master
diff --git a/lib/gitlab/ci/templates/C++.gitlab-ci.yml b/lib/gitlab/ci/templates/C++.gitlab-ci.yml
index 9a8fa9d7091..33a2a534508 100644
--- a/lib/gitlab/ci/templates/C++.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/C++.gitlab-ci.yml
@@ -14,10 +14,10 @@ build:
artifacts:
paths:
- mybinary
- # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time
- # cache:
- # paths:
- # - "*.o"
+ # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time
+ # cache:
+ # paths:
+ # - "*.o"
# run tests using the binary built before
test:
diff --git a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
index 1e14aa8bea9..5f17c93b853 100644
--- a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
@@ -23,31 +23,31 @@ stages:
foodcritic:
stage: lint
script:
- - chef exec foodcritic .
+ - chef exec foodcritic .
cookstyle:
stage: lint
script:
- - chef exec cookstyle .
+ - chef exec cookstyle .
chefspec:
stage: test
script:
- - chef exec rspec spec
+ - chef exec rspec spec
# Set up your test matrix here. Example:
-#verify-centos-6:
-# stage: functional
-# before_script:
-# - apt-get update
-# - apt-get -y install rsync
-# script:
-# - kitchen verify default-centos-6 --destroy=always
+# verify-centos-6:
+# stage: functional
+# before_script:
+# - apt-get update
+# - apt-get -y install rsync
+# script:
+# - kitchen verify default-centos-6 --destroy=always
#
-#verify-centos-7:
-# stage: functional
-# before_script:
-# - apt-get update
-# - apt-get -y install rsync
-# script:
-# - kitchen verify default-centos-7 --destroy=always
+# verify-centos-7:
+# stage: functional
+# before_script:
+# - apt-get update
+# - apt-get -y install rsync
+# script:
+# - kitchen verify default-centos-7 --destroy=always
diff --git a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
index 0610cb9ccc0..c3568c0d2c8 100644
--- a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
@@ -8,7 +8,7 @@ before_script:
# If you need to install any external applications, like a
# postgres client, you may want to uncomment the line below:
#
- #- apt-get update -y
+ # - apt-get update -y
#
# Retrieve project dependencies
# Do this on before_script since it'll be shared between both test and
@@ -17,6 +17,6 @@ before_script:
test:
script:
- # If you need to run any migrations or configure the database, this
- # would be the point to do it.
- - lein test
+ # If you need to run any migrations or configure the database, this
+ # would be the point to do it.
+ - lein test
diff --git a/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml b/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml
index 36386a19fdc..e9301a2638d 100644
--- a/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Crystal.gitlab-ci.yml
@@ -22,15 +22,15 @@ cache:
# services such as redis or postgres
before_script:
- apt-get update -qq && apt-get install -y -qq libxml2-dev
- - crystal -v # Print out Crystal version for debugging
+ - crystal -v # Print out Crystal version for debugging
- shards
# If you are using built-in Crystal Spec.
spec:
script:
- - crystal spec
+ - crystal spec
# If you are using minitest.cr
minitest:
script:
- - crystal test/spec_test.cr # change to the file(s) you execute for tests
+ - crystal test/spec_test.cr # change to the file(s) you execute for tests
diff --git a/lib/gitlab/ci/templates/Django.gitlab-ci.yml b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
index 1d8be6f017e..d35fcb0f807 100644
--- a/lib/gitlab/ci/templates/Django.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
@@ -16,19 +16,19 @@ variables:
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- - ~/.cache/pip/
+ - ~/.cache/pip/
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- - python -V # Print out python version for debugging
+ - python -V # Print out python version for debugging
# Uncomment next line if your Django app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- pip install -r requirements.txt
# To get Django tests to work you may need to create a settings file using
# the following DATABASES:
-#
+#
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql_psycopg2',
@@ -46,4 +46,4 @@ test:
variables:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
script:
- - python manage.py test
+ - python manage.py test
diff --git a/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml b/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml
index cf9c731637c..4d4c6a64cd5 100644
--- a/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml
@@ -15,4 +15,4 @@ before_script:
mix:
script:
- - mix test
+ - mix test
diff --git a/lib/gitlab/ci/templates/Go.gitlab-ci.yml b/lib/gitlab/ci/templates/Go.gitlab-ci.yml
index 55fda1a4799..1b686bc6cc0 100644
--- a/lib/gitlab/ci/templates/Go.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Go.gitlab-ci.yml
@@ -16,21 +16,21 @@ before_script:
- cd $GOPATH/src/$REPO_NAME
stages:
- - test
- - build
- - deploy
+ - test
+ - build
+ - deploy
format:
- stage: test
- script:
- - go fmt $(go list ./... | grep -v /vendor/)
- - go vet $(go list ./... | grep -v /vendor/)
- - go test -race $(go list ./... | grep -v /vendor/)
+ stage: test
+ script:
+ - go fmt $(go list ./... | grep -v /vendor/)
+ - go vet $(go list ./... | grep -v /vendor/)
+ - go test -race $(go list ./... | grep -v /vendor/)
compile:
- stage: build
- script:
- - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/mybinary
- artifacts:
- paths:
- - mybinary
+ stage: build
+ script:
+ - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/mybinary
+ artifacts:
+ paths:
+ - mybinary
diff --git a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
index dbc868238f8..efcd1d3ddc0 100644
--- a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
@@ -16,25 +16,25 @@ variables:
# We use SDKMan as tool for managing versions
before_script:
- - apt-get update -qq && apt-get install -y -qq unzip
- - curl -sSL https://get.sdkman.io | bash
- - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
- - source /root/.sdkman/bin/sdkman-init.sh
- - sdk install gradle $GRADLE_VERSION < /dev/null
- - sdk use gradle $GRADLE_VERSION
-# As it's not a good idea to version gradle.properties feel free to add your
-# environments variable here
- - echo grailsVersion=$GRAILS_VERSION > gradle.properties
- - echo gradleWrapperVersion=2.14 >> gradle.properties
-# refresh dependencies from your project
- - ./gradlew --refresh-dependencies
-# Be aware that if you are using Angular profile,
-# Bower cannot be run as root if you don't allow it before.
-# Feel free to remove next line if you are not using Bower
- - echo {\"allow_root\":true} > /root/.bowerrc
+ - apt-get update -qq && apt-get install -y -qq unzip
+ - curl -sSL https://get.sdkman.io | bash
+ - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
+ - source /root/.sdkman/bin/sdkman-init.sh
+ - sdk install gradle $GRADLE_VERSION < /dev/null
+ - sdk use gradle $GRADLE_VERSION
+ # As it's not a good idea to version gradle.properties feel free to add your
+ # environments variable here
+ - echo grailsVersion=$GRAILS_VERSION > gradle.properties
+ - echo gradleWrapperVersion=2.14 >> gradle.properties
+ # refresh dependencies from your project
+ - ./gradlew --refresh-dependencies
+ # Be aware that if you are using Angular profile,
+ # Bower cannot be run as root if you don't allow it before.
+ # Feel free to remove next line if you are not using Bower
+ - echo {\"allow_root\":true} > /root/.bowerrc
# This build job does the full grails pipeline
# (compile, test, integrationTest, war, assemble).
build:
- script:
- - ./gradlew build
+ script:
+ - ./gradlew build
diff --git a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
index 546c4affb4e..a09217e8cf0 100644
--- a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
@@ -26,8 +26,8 @@ performance:
- mv sitespeed-results/data/performance.json performance.json
artifacts:
paths:
- - performance.json
- - sitespeed-results/
+ - performance.json
+ - sitespeed-results/
only:
refs:
- branches
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index 779f4b5f006..1d55c64ec56 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -432,6 +432,7 @@ rollout 100%:
--set postgresql.postgresUser="$POSTGRES_USER" \
--set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
--set postgresql.postgresDatabase="$POSTGRES_DB" \
+ --set postgresql.imageTag="$POSTGRES_VERSION" \
--set application.migrateCommand="$DB_MIGRATE" \
$HELM_UPGRADE_EXTRA_ARGS \
--namespace="$KUBE_NAMESPACE" \
@@ -507,23 +508,13 @@ rollout 100%:
kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
}
- # Function to ensure backwards compatibility with AUTO_DEVOPS_DOMAIN
- function ensure_kube_ingress_base_domain() {
- if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ] && [ -n "$AUTO_DEVOPS_DOMAIN" ] ; then
- export KUBE_INGRESS_BASE_DOMAIN=$AUTO_DEVOPS_DOMAIN
- fi
- }
-
function check_kube_domain() {
- ensure_kube_ingress_base_domain
-
if [[ -z "$KUBE_INGRESS_BASE_DOMAIN" ]]; then
echo "In order to deploy or use Review Apps,"
- echo "AUTO_DEVOPS_DOMAIN or KUBE_INGRESS_BASE_DOMAIN variables must be set"
+ echo "KUBE_INGRESS_BASE_DOMAIN variables must be set"
echo "From 11.8, you can set KUBE_INGRESS_BASE_DOMAIN in cluster settings"
echo "or by defining a variable at group or project level."
echo "You can also manually add it in .gitlab-ci.yml"
- echo "AUTO_DEVOPS_DOMAIN support will be dropped on 12.0"
false
else
true
diff --git a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
index 2c4683fbfbb..32d4e07d398 100644
--- a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
@@ -20,7 +20,7 @@
# want coverage results.
- julia -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("MyPackage"); Pkg.test("MyPackage"; coverage = true)'
# Comment out below if you do not want coverage results.
- - julia -e 'using Pkg; Pkg.add("Coverage");
+ - julia -e 'using Pkg; Pkg.add("Coverage");
import MyPackage; cd(joinpath(dirname(pathof(MyPackage)), ".."));
using Coverage; cl, tl = get_summary(process_folder());
println("(", cl/tl*100, "%) covered")'
@@ -55,17 +55,16 @@ pages:
image: julia:0.7
stage: deploy
script:
- - apt-get update -qq && apt-get install -y git # needed by Documenter
- - julia -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("MyPackage");' # rebuild Julia (can be put somewhere else I'm sure
- - julia -e 'using Pkg; import MyPackage; Pkg.add("Documenter")' # install Documenter
- - julia --color=yes docs/make.jl # make documentation
- - mv docs/build public # move to the directory picked up by Gitlab pages
+ - apt-get update -qq && apt-get install -y git # needed by Documenter
+ - julia -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("MyPackage");' # rebuild Julia (can be put somewhere else I'm sure
+ - julia -e 'using Pkg; import MyPackage; Pkg.add("Documenter")' # install Documenter
+ - julia --color=yes docs/make.jl # make documentation
+ - mv docs/build public # move to the directory picked up by Gitlab pages
artifacts:
paths:
- public
only:
- - master
-
+ - master
# WARNING: This template is using the `julia` images from [Docker
# Hub][3]. One can use custom Julia images and/or the official ones found
diff --git a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
index e1cd29ecc94..9bde04dff19 100644
--- a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
@@ -16,8 +16,8 @@ variables:
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- - vendor/
- - node_modules/
+ - vendor/
+ - node_modules/
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
@@ -37,7 +37,7 @@ before_script:
- docker-php-ext-enable xdebug
# Install Composer and project dependencies.
- curl -sS https://getcomposer.org/installer | php
- - php composer.phar install
+ - php composer.phar install
# Install Node dependencies.
# comment this out if you don't have a node dependency
- npm install
@@ -63,10 +63,10 @@ before_script:
test:
script:
- # run laravel tests
- - php vendor/bin/phpunit --coverage-text --colors=never
- # run frontend tests
- # if you have any task for testing frontend
- # set it in your package.json script
- # comment this out if you don't have a frontend test
- - npm test
+ # run laravel tests
+ - php vendor/bin/phpunit --coverage-text --colors=never
+ # run frontend tests
+ # if you have any task for testing frontend
+ # set it in your package.json script
+ # comment this out if you don't have a frontend test
+ - npm test
diff --git a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
index c9838c7a7ff..13ab98d3a16 100644
--- a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
@@ -1,18 +1,14 @@
----
+# This file is a template, and might need editing before it works on your project.
+
# Build JAVA applications using Apache Maven (http://maven.apache.org)
# For docker image tags see https://hub.docker.com/_/maven/
#
# For general lifecycle information see https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
-#
-# This template will build and test your projects as well as create the documentation.
-#
+
+# This template will build and test your projects
# * Caches downloaded dependencies and plugins between invocation.
# * Verify but don't deploy merge requests.
# * Deploy built artifacts from master branch only.
-# * Shows how to use multiple jobs in test stage for verifying functionality
-# with multiple JDKs.
-# * Uses site:stage to collect the documentation for multi-module projects.
-# * Publishes the documentation for `master` branch.
variables:
# This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
@@ -23,78 +19,38 @@ variables:
# `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"
+# This template uses jdk8 for verifying and deploying images
+image: maven:3.3.9-jdk-8
+
# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
paths:
- .m2/repository
-# This will only validate and compile stuff and run e.g. maven-enforcer-plugin.
-# Because some enforcer rules might check dependency convergence and class duplications
-# we use `test-compile` here instead of `validate`, so the correct classpath is picked up.
-.validate: &validate
- stage: build
- script:
- - 'mvn $MAVEN_CLI_OPTS test-compile'
-
# For merge requests do not `deploy` but only run `verify`.
# See https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
.verify: &verify
stage: test
script:
- - 'mvn $MAVEN_CLI_OPTS verify site site:stage'
+ - 'mvn $MAVEN_CLI_OPTS verify'
except:
- master
-# Validate merge requests using JDK7
-validate:jdk7:
- <<: *validate
- image: maven:3.3.9-jdk-7
-
-# Validate merge requests using JDK8
-validate:jdk8:
- <<: *validate
- image: maven:3.3.9-jdk-8
-
-# Verify merge requests using JDK7
-verify:jdk7:
- <<: *verify
- image: maven:3.3.9-jdk-7
-
# Verify merge requests using JDK8
verify:jdk8:
<<: *verify
- image: maven:3.3.9-jdk-8
+# To deploy packages from CI, create a ci_settings.xml file
+# For deploying packages to GitLab's Maven Repository: See https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for more details.
+# Please note: The GitLab Maven Repository is currently only available in GitLab Premium / Ultimate.
# For `master` branch run `mvn deploy` automatically.
-# Here you need to decide whether you want to use JDK7 or 8.
-# To get this working you need to define a volume while configuring your gitlab-ci-multi-runner.
-# Mount your `settings.xml` as `/root/.m2/settings.xml` which holds your secrets.
-# See https://maven.apache.org/settings.html
deploy:jdk8:
- # Use stage test here, so the pages job may later pickup the created site.
- stage: test
- script:
- - 'mvn $MAVEN_CLI_OPTS deploy site site:stage'
- only:
- - master
- # Archive up the built documentation site.
- artifacts:
- paths:
- - target/staging
- image: maven:3.3.9-jdk-8
-
-pages:
- image: busybox:latest
stage: deploy
script:
- # Because Maven appends the artifactId automatically to the staging path if you did define a parent pom,
- # you might need to use `mv target/staging/YOUR_ARTIFACT_ID public` instead.
- - mv target/staging public
- dependencies:
- - deploy:jdk8
- artifacts:
- paths:
- - public
+ - if [ ! -f ci_settings.xml ];
+ then echo "CI settings missing\! If deploying to GitLab Maven Repository, please see https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for instructions.";
+ fi
+ - 'mvn $MAVEN_CLI_OPTS deploy -s ci_settings.xml'
only:
- master
diff --git a/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml b/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
index 41de1458582..b87178141a1 100644
--- a/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
@@ -14,14 +14,14 @@ services:
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- - node_modules/
+ - node_modules/
test_async:
script:
- - npm install
- - node ./specs/start.js ./specs/async.spec.js
+ - npm install
+ - node ./specs/start.js ./specs/async.spec.js
test_db:
script:
- - npm install
- - node ./specs/start.js ./specs/db-postgres.spec.js
+ - npm install
+ - node ./specs/start.js ./specs/db-postgres.spec.js
diff --git a/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml b/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml
index 61a925e0d2d..65abee1f5eb 100644
--- a/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/OpenShift.gitlab-ci.yml
@@ -1,9 +1,9 @@
image: ayufan/openshift-cli
stages:
- - build # dummy stage to follow the template guidelines
+ - build # dummy stage to follow the template guidelines
- test
- - deploy # dummy stage to follow the template guidelines
+ - deploy # dummy stage to follow the template guidelines
- review
- staging
- production
diff --git a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
index 33f44ee9222..b9fee2d5731 100644
--- a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
@@ -4,19 +4,19 @@ image: php:7.1.1
# Select what we should cache between builds
cache:
paths:
- - vendor/
+ - vendor/
before_script:
-- apt-get update -yqq
-- apt-get install -yqq git libmcrypt-dev libpq-dev libcurl4-gnutls-dev libicu-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev
-# Install PHP extensions
-- docker-php-ext-install mbstring mcrypt pdo_pgsql curl json intl gd xml zip bz2 opcache
-# Install & enable Xdebug for code coverage reports
-- pecl install xdebug
-- docker-php-ext-enable xdebug
-# Install and run Composer
-- curl -sS https://getcomposer.org/installer | php
-- php composer.phar install
+ - apt-get update -yqq
+ - apt-get install -yqq git libmcrypt-dev libpq-dev libcurl4-gnutls-dev libicu-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev
+ # Install PHP extensions
+ - docker-php-ext-install mbstring mcrypt pdo_pgsql curl json intl gd xml zip bz2 opcache
+ # Install & enable Xdebug for code coverage reports
+ - pecl install xdebug
+ - docker-php-ext-enable xdebug
+ # Install and run Composer
+ - curl -sS https://getcomposer.org/installer | php
+ - php composer.phar install
# Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
# See http://docs.gitlab.com/ce/ci/services/README.html for examples.
@@ -33,4 +33,4 @@ variables:
# If Xdebug was installed you can generate a coverage report and see code coverage metrics.
test:
script:
- - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never \ No newline at end of file
+ - vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
diff --git a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
index d6de8cab5d1..d2dd3fbfb75 100644
--- a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
@@ -4,12 +4,12 @@ image: node:4.2.2
pages:
cache:
paths:
- - node_modules/
+ - node_modules/
script:
- - npm install -g brunch
- - brunch build --production
+ - npm install -g brunch
+ - brunch build --production
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml
index 791afdd23f1..ba422c08614 100644
--- a/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Doxygen.gitlab-ci.yml
@@ -3,11 +3,11 @@ image: alpine
pages:
script:
- - apk update && apk add doxygen
- - doxygen doxygen/Doxyfile
- - mv doxygen/documentation/html/ public/
+ - apk update && apk add doxygen
+ - doxygen doxygen/Doxyfile
+ - mv doxygen/documentation/html/ public/
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml
index 9df2a4797b2..a683561a455 100644
--- a/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Gatsby.gitlab-ci.yml
@@ -4,14 +4,14 @@ image: node:latest
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- - node_modules/
+ - node_modules/
pages:
script:
- - yarn install
- - ./node_modules/.bin/gatsby build --prefix-paths
+ - yarn install
+ - ./node_modules/.bin/gatsby build --prefix-paths
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml
index 249a168aa33..92f25280c6e 100644
--- a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml
@@ -2,11 +2,11 @@
pages:
stage: deploy
script:
- - mkdir .public
- - cp -r * .public
- - mv .public public
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
index 4b58003ee10..0e206423fa5 100644
--- a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
@@ -4,12 +4,12 @@ image: node:4.2.2
pages:
cache:
paths:
- - node_modules
+ - node_modules
script:
- - npm install -g harp
- - harp compile ./ public
+ - npm install -g harp
+ - harp compile ./ public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml
index a9e195370f7..d91a8d7421f 100644
--- a/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Hexo.gitlab-ci.yml
@@ -3,15 +3,15 @@ image: node:10.15.3
pages:
script:
- - npm install hexo-cli -g
- - test -e package.json && npm install
- - hexo generate
+ - npm install hexo-cli -g
+ - test -e package.json && npm install
+ - hexo generate
artifacts:
paths:
- - public
+ - public
cache:
paths:
- node_modules
key: project
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
index f9ddcc6fb0a..9a3ecd1c34f 100644
--- a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
@@ -3,15 +3,15 @@ image: dettmering/hugo-build
pages:
script:
- - hugo
+ - hugo
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
test:
script:
- - hugo
+ - hugo
except:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml
index f5b40f2b9f1..7a441a2f70f 100644
--- a/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Hyde.gitlab-ci.yml
@@ -3,7 +3,7 @@ image: python:2.7
cache:
paths:
- - vendor/
+ - vendor/
test:
stage: test
@@ -20,6 +20,6 @@ pages:
- hyde gen -d public
artifacts:
paths:
- - public
+ - public
only:
- master
diff --git a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
index 7a485f8d135..886b6c36249 100644
--- a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
@@ -4,28 +4,28 @@
# JBake https://jbake.org/ is a Java based, open source, static site/blog generator for developers & designers
#
# This yml works with jBake 2.5.1
-# Feel free to change JBAKE_VERSION version
+# Feel free to change JBAKE_VERSION version
#
# HowTo at: https://jorge.aguilera.gitlab.io/howtojbake/
image: java:8
variables:
- JBAKE_VERSION: 2.5.1
+ JBAKE_VERSION: 2.5.1
# We use SDKMan as tool for managing versions
before_script:
- - apt-get update -qq && apt-get install -y -qq unzip zip
- - curl -sSL https://get.sdkman.io | bash
- - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
- - source /root/.sdkman/bin/sdkman-init.sh
- - sdk install jbake $JBAKE_VERSION < /dev/null
- - sdk use jbake $JBAKE_VERSION
+ - apt-get update -qq && apt-get install -y -qq unzip zip
+ - curl -sSL https://get.sdkman.io | bash
+ - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
+ - source /root/.sdkman/bin/sdkman-init.sh
+ - sdk install jbake $JBAKE_VERSION < /dev/null
+ - sdk use jbake $JBAKE_VERSION
# This build job produced the output directory of your site
pages:
- script:
- - jbake . public
- artifacts:
- paths:
- - public
+ script:
+ - jbake . public
+ artifacts:
+ paths:
+ - public
diff --git a/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml
index 37f50554036..0d742aa282d 100644
--- a/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Jekyll.gitlab-ci.yml
@@ -6,25 +6,24 @@ variables:
JEKYLL_ENV: production
before_script:
-- bundle install
+ - bundle install
test:
stage: test
script:
- - bundle exec jekyll build -d test
+ - bundle exec jekyll build -d test
artifacts:
paths:
- - test
+ - test
except:
- - master
+ - master
pages:
stage: deploy
script:
- - bundle exec jekyll build -d public
+ - bundle exec jekyll build -d public
artifacts:
paths:
- - public
+ - public
only:
- - master
-
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
index 5ca4619e200..2d26b86a328 100644
--- a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
@@ -7,8 +7,8 @@ image: php:7.2
# These folders are cached between builds
cache:
paths:
- - vendor/
- - node_modules/
+ - vendor/
+ - node_modules/
before_script:
# Update packages
@@ -28,10 +28,10 @@ before_script:
pages:
script:
- - npm run production
- - mv build_production public
+ - npm run production
+ - mv build_production public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml
index c5c44a5d86c..93ab8e0be0d 100644
--- a/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Lektor.gitlab-ci.yml
@@ -3,10 +3,10 @@ image: python:2.7
pages:
script:
- - pip install lektor
- - lektor build --output-path public
+ - pip install lektor
+ - lektor build --output-path public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
index c6ded272150..6524405133a 100644
--- a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
@@ -4,13 +4,13 @@ image: node:4.2.2
pages:
cache:
paths:
- - node_modules/
+ - node_modules/
script:
- - npm install -g metalsmith
- - npm install
- - make build
+ - npm install -g metalsmith
+ - npm install
+ - make build
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml
index 9f4cc0574d6..57ac323dfdf 100644
--- a/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Middleman.gitlab-ci.yml
@@ -3,25 +3,25 @@ image: ruby:2.3
cache:
paths:
- - vendor
+ - vendor
test:
script:
- - apt-get update -yqqq
- - apt-get install -y nodejs
- - bundle install --path vendor
- - bundle exec middleman build
+ - apt-get update -yqqq
+ - apt-get install -y nodejs
+ - bundle install --path vendor
+ - bundle exec middleman build
except:
- master
pages:
script:
- - apt-get update -yqqq
- - apt-get install -y nodejs
- - bundle install --path vendor
- - bundle exec middleman build
+ - apt-get update -yqqq
+ - apt-get install -y nodejs
+ - bundle install --path vendor
+ - bundle exec middleman build
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml
index b469b316ba5..7f037b5f5cf 100644
--- a/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Nanoc.gitlab-ci.yml
@@ -3,10 +3,10 @@ image: ruby:2.3
pages:
script:
- - bundle install -j4
- - nanoc
+ - bundle install -j4
+ - nanoc
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml
index 4762ec9acfd..6d912a89bc1 100644
--- a/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Octopress.gitlab-ci.yml
@@ -3,13 +3,13 @@ image: ruby:2.3
pages:
script:
- - apt-get update -qq && apt-get install -qq nodejs
- - bundle install -j4
- - bundle exec rake generate
- - mv public .public
- - mv .public/octopress public
+ - apt-get update -qq && apt-get install -qq nodejs
+ - bundle install -j4
+ - bundle exec rake generate
+ - mv public .public
+ - mv .public/octopress public
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml
index c5f3154f587..09c6649fc13 100644
--- a/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Pelican.gitlab-ci.yml
@@ -3,8 +3,8 @@ image: python:2.7-alpine
pages:
script:
- - pip install -r requirements.txt
- - pelican -s publishconf.py
+ - pip install -r requirements.txt
+ - pelican -s publishconf.py
artifacts:
paths:
- - public/
+ - public/
diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
index 3eaed4e91cd..00b8b94b574 100644
--- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
@@ -18,34 +18,34 @@ cache:
- venv/
before_script:
- - python -V # Print out python version for debugging
+ - python -V # Print out python version for debugging
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
test:
script:
- - python setup.py test
- - pip install tox flake8 # you can also use tox
- - tox -e py36,flake8
+ - python setup.py test
+ - pip install tox flake8 # you can also use tox
+ - tox -e py36,flake8
run:
script:
- - python setup.py bdist_wheel
- # an alternative approach is to install and run:
- - pip install dist/*
- # run the command here
+ - python setup.py bdist_wheel
+ # an alternative approach is to install and run:
+ - pip install dist/*
+ # run the command here
artifacts:
paths:
- - dist/*.whl
+ - dist/*.whl
pages:
script:
- - pip install sphinx sphinx-rtd-theme
- - cd doc ; make html
- - mv build/html/ ../public/
+ - pip install sphinx sphinx-rtd-theme
+ - cd doc ; make html
+ - mv build/html/ ../public/
artifacts:
paths:
- - public
+ - public
only:
- - master
+ - master
diff --git a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
index 93196dbd475..b3cad8b858a 100644
--- a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
@@ -21,7 +21,7 @@ cache:
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- - ruby -v # Print out ruby version for debugging
+ - ruby -v # Print out ruby version for debugging
# Uncomment next line if your rails app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby
@@ -29,19 +29,19 @@ before_script:
# Optional - Delete if not using `rubocop`
rubocop:
script:
- - rubocop
+ - rubocop
rspec:
script:
- - rspec spec
+ - rspec spec
rails:
variables:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
script:
- - rails db:migrate
- - rails db:seed
- - rails test
+ - rails db:migrate
+ - rails db:seed
+ - rails test
# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk
# are supported too: https://github.com/travis-ci/dpl
@@ -49,5 +49,5 @@ deploy:
type: deploy
environment: production
script:
- - gem install dpl
- - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY
+ - gem install dpl
+ - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY
diff --git a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml
index cab087c48c7..a25dc38e4e7 100644
--- a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml
@@ -5,19 +5,19 @@ image: "rust:latest"
# Optional: Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service
-#services:
-# - mysql:latest
-# - redis:latest
-# - postgres:latest
+# services:
+# - mysql:latest
+# - redis:latest
+# - postgres:latest
# Optional: Install a C compiler, cmake and git into the container.
# You will often need this when you (or any of your dependencies) depends on C code.
-#before_script:
-#- apt-get update -yqq
-#- apt-get install -yqq --no-install-recommends build-essential
+# before_script:
+# - apt-get update -yqq
+# - apt-get install -yqq --no-install-recommends build-essential
# Use cargo to test the project
test:cargo:
script:
- - rustc --version && cargo --version # Print version info for debugging
- - cargo test --all --verbose
+ - rustc --version && cargo --version # Print version info for debugging
+ - cargo test --all --verbose
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
index 324e39c7747..5372ec6cceb 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -23,6 +23,9 @@ container_scanning:
DOCKER_HOST: tcp://${DOCKER_SERVICE}:2375/
# https://hub.docker.com/r/arminc/clair-local-scan/tags
CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
+ ## Disable the proxy for clair-local-scan, otherwise Container Scanning will
+ ## fail when a proxy is used.
+ NO_PROXY: ${DOCKER_SERVICE},localhost
allow_failure: true
services:
- docker:stable-dind
diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
index 27a498b2daf..4b55ffd3771 100644
--- a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
@@ -15,22 +15,22 @@ dast:
image:
name: "registry.gitlab.com/gitlab-org/security-products/dast:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
variables:
- # URL to scan:
- # DAST_WEBSITE: https://example.com/
- #
- # Time limit for target availability (scan is attempted even when timeout):
- # DAST_TARGET_AVAILABILITY_TIMEOUT: 60
- #
- # Set these variables to scan with an authenticated user:
- # DAST_AUTH_URL: https://example.com/sign-in
- # DAST_USERNAME: john.doe@example.com
- # DAST_PASSWORD: john-doe-password
- # DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form
- # DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form
- # DAST_AUTH_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional: URLs to skip during the authenticated scan; comma-separated, no spaces in between
- #
- # Perform ZAP Full Scan, which includes both passive and active scanning:
- # DAST_FULL_SCAN_ENABLED: "true"
+ # URL to scan:
+ # DAST_WEBSITE: https://example.com/
+ #
+ # Time limit for target availability (scan is attempted even when timeout):
+ # DAST_TARGET_AVAILABILITY_TIMEOUT: 60
+ #
+ # Set these variables to scan with an authenticated user:
+ # DAST_AUTH_URL: https://example.com/sign-in
+ # DAST_USERNAME: john.doe@example.com
+ # DAST_PASSWORD: john-doe-password
+ # DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form
+ # DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form
+ # DAST_AUTH_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional: URLs to skip during the authenticated scan; comma-separated, no spaces in between
+ #
+ # Perform ZAP Full Scan, which includes both passive and active scanning:
+ # DAST_FULL_SCAN_ENABLED: "true"
allow_failure: true
script:
- export DAST_WEBSITE=${DAST_WEBSITE:-$(cat environment_url.txt)}
diff --git a/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml
index 0208beb35b8..b6555e627a1 100644
--- a/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml
@@ -1,7 +1,7 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/project/merge_requests/license_management.html
variables:
- LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager.
+ LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager.
license_management:
stage: test
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index 706692e063b..abf16e5b2e7 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -40,6 +40,7 @@ sast:
SAST_BRAKEMAN_LEVEL \
SAST_GOSEC_LEVEL \
SAST_FLAWFINDER_LEVEL \
+ SAST_GITLEAKS_ENTROPY_LEVEL \
SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
SAST_PULL_ANALYZER_IMAGE_TIMEOUT \
SAST_RUN_ANALYZER_TIMEOUT \
diff --git a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
index 0fb7c57ab72..a3db2705bf6 100644
--- a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
@@ -8,25 +8,13 @@ stages:
- deploy
.serverless:build:image:
- variables:
- DOCKERFILE: "Dockerfile"
stage: build
- image:
- name: gcr.io/kaniko-project/executor:debug
- entrypoint: [""]
- only:
- refs:
- - master
- script:
- - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/$DOCKERFILE --destination $CI_REGISTRY_IMAGE
+ image: registry.gitlab.com/gitlab-org/gitlabktl:latest
+ script: /usr/bin/gitlabktl app build
.serverless:deploy:image:
stage: deploy
- image: gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5
- only:
- refs:
- - master
+ image: gcr.io/triggermesh/tm@sha256:3cfdd470a66b741004fb02354319d79f1598c70117ce79978d2e07e192bfb336 # v0.0.11
environment: development
script:
- echo "$CI_REGISTRY_IMAGE"
diff --git a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml
index 9aa4abd4791..ffed7a0fec2 100644
--- a/lib/gitlab/ci/templates/Swift.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Swift.gitlab-ci.yml
@@ -25,7 +25,7 @@ archive_project:
- master
artifacts:
paths:
- - build/ProjectName.ipa
+ - build/ProjectName.ipa
tags:
- ios_11-3
- xcode_9-3
diff --git a/lib/gitlab/ci/templates/dotNET-Core.yml b/lib/gitlab/ci/templates/dotNET-Core.yml
index 708b75f83e8..40ca296d7bd 100644
--- a/lib/gitlab/ci/templates/dotNET-Core.yml
+++ b/lib/gitlab/ci/templates/dotNET-Core.yml
@@ -1,4 +1,5 @@
-# This is a simple example illustrating how to build and test .NET Core project
+---
+# This is a simple example illustrating how to build and test .NET Core project
# with GitLab Continuous Integration / Continuous Delivery.
# ### Specify the Docker image
@@ -17,21 +18,21 @@ image: microsoft/dotnet:latest
# ### Define variables
#
variables:
- # 1) Name of directory where restore and build objects are stored.
- OBJECTS_DIRECTORY: 'obj'
- # 2) Name of directory used for keeping restored dependencies.
- NUGET_PACKAGES_DIRECTORY: '.nuget'
- # 3) A relative path to the source code from project repository root.
- # NOTE: Please edit this path so it matches the structure of your project!
- SOURCE_CODE_PATH: '*/*/'
+ # 1) Name of directory where restore and build objects are stored.
+ OBJECTS_DIRECTORY: 'obj'
+ # 2) Name of directory used for keeping restored dependencies.
+ NUGET_PACKAGES_DIRECTORY: '.nuget'
+ # 3) A relative path to the source code from project repository root.
+ # NOTE: Please edit this path so it matches the structure of your project!
+ SOURCE_CODE_PATH: '*/*/'
# ### Define stage list
#
# In this example there are only two stages.
# Initially, the project will be built and then tested.
stages:
- - build
- - test
+ - build
+ - test
# ### Define global cache rule
#
@@ -50,31 +51,31 @@ stages:
# With global cache rule, cached dependencies will be downloaded before every job
# and then unpacked to the paths as specified below.
cache:
- # Per-stage and per-branch caching.
- key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
- paths:
- # Specify three paths that should be cached:
+ # Per-stage and per-branch caching.
+ key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
+ paths:
+ # Specify three paths that should be cached:
+ #
+ # 1) Main JSON file holding information about package dependency tree, packages versions,
+ # frameworks etc. It also holds information where to the dependencies were restored.
+ - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/project.assets.json'
+ # 2) Other NuGet and MSBuild related files. Also needed.
+ - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/*.csproj.nuget.*'
+ # 3) Path to the directory where restored dependencies are kept.
+ - '$NUGET_PACKAGES_DIRECTORY'
#
- # 1) Main JSON file holding information about package dependency tree, packages versions,
- # frameworks etc. It also holds information where to the dependencies were restored.
- - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/project.assets.json'
- # 2) Other NuGet and MSBuild related files. Also needed.
- - '$SOURCE_CODE_PATH$OBJECTS_DIRECTORY/*.csproj.nuget.*'
- # 3) Path to the directory where restored dependencies are kept.
- - '$NUGET_PACKAGES_DIRECTORY'
- #
- # 'pull-push' policy means that latest cache will be downloaded (if it exists)
- # before executing the job, and a newer version will be uploaded afterwards.
- # Such a setting saves time when there are no changes in referenced third-party
- # packages.
- #
- # For example, if you run a pipeline with changes in your code,
- # but with no changes within third-party packages which your project is using,
- # then project restore will happen quickly as all required dependencies
- # will already be there — unzipped from cache.
+ # 'pull-push' policy means that latest cache will be downloaded (if it exists)
+ # before executing the job, and a newer version will be uploaded afterwards.
+ # Such a setting saves time when there are no changes in referenced third-party
+ # packages.
+ #
+ # For example, if you run a pipeline with changes in your code,
+ # but with no changes within third-party packages which your project is using,
+ # then project restore will happen quickly as all required dependencies
+ # will already be there — unzipped from cache.
- # 'pull-push' policy is the default cache policy, you do not have to specify it explicitly.
- policy: pull-push
+ # 'pull-push' policy is the default cache policy, you do not have to specify it explicitly.
+ policy: pull-push
# ### Restore project dependencies
#
@@ -87,30 +88,30 @@ cache:
#
# Learn more about GitLab cache: https://docs.gitlab.com/ee/ci/caching/index.html
before_script:
- - 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY'
+ - 'dotnet restore --packages $NUGET_PACKAGES_DIRECTORY'
build:
- stage: build
- # ### Build all projects discovered from solution file.
- #
- # Note: this will fail if you have any projects in your solution that are not
- # .NET Core-based projects (e.g. WCF service), which is based on .NET Framework,
- # not .NET Core. In this scenario, you will need to build every .NET Core-based
- # project by explicitly specifying a relative path to the directory
- # where it is located (e.g. 'dotnet build ./src/ConsoleApp').
- # Only one project path can be passed as a parameter to 'dotnet build' command.
- script:
- - 'dotnet build --no-restore'
+ stage: build
+ # ### Build all projects discovered from solution file.
+ #
+ # Note: this will fail if you have any projects in your solution that are not
+ # .NET Core-based projects (e.g. WCF service), which is based on .NET Framework,
+ # not .NET Core. In this scenario, you will need to build every .NET Core-based
+ # project by explicitly specifying a relative path to the directory
+ # where it is located (e.g. 'dotnet build ./src/ConsoleApp').
+ # Only one project path can be passed as a parameter to 'dotnet build' command.
+ script:
+ - 'dotnet build --no-restore'
tests:
- stage: test
- # ### Run the tests
- #
- # You can either run tests for all test projects that are defined in your solution
- # with 'dotnet test' or run tests only for specific project by specifying
- # a relative path to the directory where it is located (e.g. 'dotnet test ./test/UnitTests').
- #
- # You may want to define separate testing jobs for different types of testing
- # (e.g. integration tests, unit tests etc).
- script:
- - 'dotnet test --no-restore'
+ stage: test
+ # ### Run the tests
+ #
+ # You can either run tests for all test projects that are defined in your solution
+ # with 'dotnet test' or run tests only for specific project by specifying
+ # a relative path to the directory where it is located (e.g. 'dotnet test ./test/UnitTests').
+ #
+ # You may want to define separate testing jobs for different types of testing
+ # (e.g. integration tests, unit tests etc).
+ script:
+ - 'dotnet test --no-restore'
diff --git a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
index 5e128b793d0..b29f45323f5 100644
--- a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
@@ -3,12 +3,12 @@
# (located in gitlab-runner.exe directory):
# shell = "powershell"
#
-# The script is composed of 3 stages: build, test and deploy.
+# The script is composed of 3 stages: build, test and deploy.
#
# The build stage restores NuGet packages and uses msbuild to build the exe and msi
# One major issue you'll find is that you can't build msi projects from command line
# if you use vdproj. There are workarounds building msi via devenv, but they rarely work
-# The best solution is migrating your vdproj projects to WiX, as it can be build directly
+# The best solution is migrating your vdproj projects to WiX, as it can be build directly
# by msbuild.
#
# The test stage runs nunit from command line against Test project inside your solution
@@ -38,28 +38,28 @@ stages:
build_job:
stage: build
only:
- - tags # the build process will only be started by git tag commits
+ - tags # the build process will only be started by git tag commits
script:
- - '& "$env:NUGET_PATH" restore' # restore Nuget dependencies
- - '& "$env:MSBUILD_PATH" /p:Configuration=Release' # build the project
+ - '& "$env:NUGET_PATH" restore' # restore Nuget dependencies
+ - '& "$env:MSBUILD_PATH" /p:Configuration=Release' # build the project
artifacts:
- expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on
+ expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on
paths:
- - '$env:EXE_RELEASE_FOLDER\YourApp.exe' # saving exe to copy to deploy folder
- - '$env:MSI_RELEASE_FOLDER\YourApp Setup.msi' # saving msi to copy to deploy folder
- - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests
+ - '$env:EXE_RELEASE_FOLDER\YourApp.exe' # saving exe to copy to deploy folder
+ - '$env:MSI_RELEASE_FOLDER\YourApp Setup.msi' # saving msi to copy to deploy folder
+ - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests
test_job:
stage: test
only:
- tags
script:
- - '& "$env:NUNIT_PATH" ".\$env:TEST_FOLDER\Tests.dll"' # running NUnit tests
+ - '& "$env:NUNIT_PATH" ".\$env:TEST_FOLDER\Tests.dll"' # running NUnit tests
artifacts:
- when: always # save test results even when the task fails
- expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on
+ when: always # save test results even when the task fails
+ expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on
paths:
- - '.\TestResult.xml' # saving NUnit results to copy to deploy folder
+ - '.\TestResult.xml' # saving NUnit results to copy to deploy folder
dependencies:
- build_job
@@ -79,7 +79,6 @@ deploy_job:
- 'xcopy /y ".\$env:EXE_RELEASE_FOLDER\YourApp.exe" "$deployFolder"'
- 'xcopy /y ".\$env:MSI_RELEASE_FOLDER\YourApp Setup.msi" "$deployFolder"'
- 'xcopy /y ".\TestResult.xml" "$deployFolder"'
-
dependencies:
- build_job
- test_job
diff --git a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
index a4a9e96c1d2..87aea8527d1 100644
--- a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
@@ -1,13 +1,13 @@
# This is a very simple template that mainly relies on FastLane to build and distribute your app.
# Read more about how to use this template on the blog post https://about.gitlab.com/2019/03/06/ios-publishing-with-gitlab-and-fastlane/
-# You will also need fastlane and signing configuration for this to work, along with a MacOS runner.
+# You will also need fastlane and signing configuration for this to work, along with a MacOS runner.
# These details are provided in the blog post.
# Note that when you're using the shell executor for MacOS builds, the
# build and tests run as the identity of the runner logged in user, directly on
# the build host. This is less secure than using container executors, so please
-# take a look at our security implications documentation at
-# https://docs.gitlab.com/runner/security/#usage-of-shell-executor for additional
+# take a look at our security implications documentation at
+# https://docs.gitlab.com/runner/security/#usage-of-shell-executor for additional
# detail on what to keep in mind in this scenario.
stages:
@@ -27,4 +27,4 @@ build:
- bundle exec fastlane build
artifacts:
paths:
- - ./*.ipa
+ - ./*.ipa
diff --git a/lib/gitlab/cluster/lifecycle_events.rb b/lib/gitlab/cluster/lifecycle_events.rb
index b05dca409d1..e0f9eb59924 100644
--- a/lib/gitlab/cluster/lifecycle_events.rb
+++ b/lib/gitlab/cluster/lifecycle_events.rb
@@ -44,6 +44,14 @@ module Gitlab
(@master_restart_hooks ||= []) << block
end
+ def on_master_start(&block)
+ if in_clustered_environment?
+ on_before_fork(&block)
+ else
+ on_worker_start(&block)
+ end
+ end
+
#
# Lifecycle integration methods (called from unicorn.rb, puma.rb, etc.)
#
diff --git a/lib/gitlab/cluster/puma_worker_killer_initializer.rb b/lib/gitlab/cluster/puma_worker_killer_initializer.rb
index 4ed9a9a02ab..4affc52b7b0 100644
--- a/lib/gitlab/cluster/puma_worker_killer_initializer.rb
+++ b/lib/gitlab/cluster/puma_worker_killer_initializer.rb
@@ -27,6 +27,9 @@ module Gitlab
# is restarted already, thus periodically restarting workers shouldn't be
# needed.
config.rolling_restart_frequency = false
+
+ observer = Gitlab::Cluster::PumaWorkerKillerObserver.new
+ config.pre_term = observer.callback
end
PumaWorkerKiller.start
diff --git a/lib/gitlab/cluster/puma_worker_killer_observer.rb b/lib/gitlab/cluster/puma_worker_killer_observer.rb
new file mode 100644
index 00000000000..3b4ebc3fbae
--- /dev/null
+++ b/lib/gitlab/cluster/puma_worker_killer_observer.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Cluster
+ class PumaWorkerKillerObserver
+ def initialize
+ @counter = Gitlab::Metrics.counter(:puma_killer_terminations_total, 'Number of workers terminated by PumaWorkerKiller')
+ end
+
+ # returns the Proc to be used as the observer callback block
+ def callback
+ method(:log_termination)
+ end
+
+ private
+
+ def log_termination(worker)
+ labels = { worker: "worker_#{worker.index}" }
+
+ @counter.increment(labels)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index f0ca397609d..7a0fb419f8e 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -1,6 +1,4 @@
# frozen_string_literal: true
-require 'net/http'
-require 'json'
require_relative 'teammate'
@@ -8,7 +6,6 @@ module Gitlab
module Danger
module Helper
RELEASE_TOOLS_BOT = 'gitlab-release-tools-bot'
- ROULETTE_DATA_URL = URI.parse('https://about.gitlab.com/roulette.json').freeze
# Returns a list of all files that have been added, modified or renamed.
# `git.modified_files` might contain paths that already have been renamed,
@@ -49,32 +46,6 @@ module Gitlab
ee? ? 'gitlab-ee' : 'gitlab-ce'
end
- # Looks up the current list of GitLab team members and parses it into a
- # useful form
- #
- # @return [Array<Teammate>]
- def team
- @team ||=
- begin
- rsp = Net::HTTP.get_response(ROULETTE_DATA_URL)
- raise "Failed to read #{ROULETTE_DATA_URL}: #{rsp.code} #{rsp.message}" unless
- rsp.is_a?(Net::HTTPSuccess)
-
- data = JSON.parse(rsp.body)
- data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
- rescue JSON::ParserError
- raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
- end
- end
-
- # Like +team+, but only returns teammates in the current project, based on
- # project_name.
- #
- # @return [Array<Teammate>]
- def project_team
- team.select { |member| member.in_project?(project_name) }
- end
-
# @return [Hash<String,Array<String>>]
def changes_by_category
all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
@@ -101,7 +72,8 @@ module Gitlab
CATEGORY_LABELS = {
docs: "~Documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
none: "",
- qa: "~QA"
+ qa: "~QA",
+ test: "~test for `spec/features/*`"
}.freeze
CATEGORIES = {
%r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`.
@@ -133,6 +105,7 @@ module Gitlab
%r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
%r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
+ %r{\A(ee/)?spec/features/} => :test,
%r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
%r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
%r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
diff --git a/lib/gitlab/danger/roulette.rb b/lib/gitlab/danger/roulette.rb
new file mode 100644
index 00000000000..062eda38ee4
--- /dev/null
+++ b/lib/gitlab/danger/roulette.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'net/http'
+require 'json'
+require 'cgi'
+
+require_relative 'teammate'
+
+module Gitlab
+ module Danger
+ module Roulette
+ ROULETTE_DATA_URL = 'https://about.gitlab.com/roulette.json'
+ HTTPError = Class.new(RuntimeError)
+
+ # Looks up the current list of GitLab team members and parses it into a
+ # useful form
+ #
+ # @return [Array<Teammate>]
+ def team
+ @team ||=
+ begin
+ data = http_get_json(ROULETTE_DATA_URL)
+ data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
+ rescue JSON::ParserError
+ raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
+ end
+ end
+
+ # Like +team+, but only returns teammates in the current project, based on
+ # project_name.
+ #
+ # @return [Array<Teammate>]
+ def project_team(project_name)
+ team.select { |member| member.in_project?(project_name) }
+ end
+
+ def canonical_branch_name(branch_name)
+ branch_name.gsub(/^[ce]e-|-[ce]e$/, '')
+ end
+
+ def new_random(seed)
+ Random.new(Digest::MD5.hexdigest(seed).to_i(16))
+ end
+
+ # Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
+ # selection will change on next spin
+ def spin_for_person(people, random:)
+ person = nil
+ people = people.dup
+
+ people.size.times do
+ person = people.sample(random: random)
+
+ break person unless out_of_office?(person)
+
+ people -= [person]
+ end
+
+ person
+ end
+
+ private
+
+ def out_of_office?(person)
+ username = CGI.escape(person.username)
+ api_endpoint = "https://gitlab.com/api/v4/users/#{username}/status"
+ response = http_get_json(api_endpoint)
+ response["message"]&.match?(/OOO/i)
+ rescue HTTPError, JSON::ParserError
+ false # this is no worse than not checking for OOO
+ end
+
+ def http_get_json(url)
+ rsp = Net::HTTP.get_response(URI.parse(url))
+
+ unless rsp.is_a?(Net::HTTPSuccess)
+ raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
+ end
+
+ JSON.parse(rsp.body)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
index bfada512727..b44f134f2c1 100644
--- a/lib/gitlab/danger/teammate.rb
+++ b/lib/gitlab/danger/teammate.rb
@@ -3,11 +3,12 @@
module Gitlab
module Danger
class Teammate
- attr_reader :name, :username, :projects
+ attr_reader :name, :username, :role, :projects
def initialize(options = {})
- @name = options['name']
@username = options['username']
+ @name = options['name'] || @username
+ @role = options['role']
@projects = options['projects']
end
@@ -20,20 +21,32 @@ module Gitlab
end
# Traintainers also count as reviewers
- def reviewer?(project, category)
- capabilities(project).include?("reviewer #{category}") || traintainer?(project, category)
+ def reviewer?(project, category, labels)
+ has_capability?(project, category, :reviewer, labels) ||
+ traintainer?(project, category, labels)
end
- def traintainer?(project, category)
- capabilities(project).include?("trainee_maintainer #{category}")
+ def traintainer?(project, category, labels)
+ has_capability?(project, category, :trainee_maintainer, labels)
end
- def maintainer?(project, category)
- capabilities(project).include?("maintainer #{category}")
+ def maintainer?(project, category, labels)
+ has_capability?(project, category, :maintainer, labels)
end
private
+ def has_capability?(project, category, kind, labels)
+ case category
+ when :test
+ area = role[/Test Automation Engineer, (\w+)/, 1]
+
+ area && labels.any?(area) if kind == :reviewer
+ else
+ capabilities(project).include?("#{kind} #{category}")
+ end
+ end
+
def capabilities(project)
Array(projects.fetch(project, []))
end
diff --git a/lib/gitlab/data_builder/note.rb b/lib/gitlab/data_builder/note.rb
index 65601dcdf31..16e62622ed4 100644
--- a/lib/gitlab/data_builder/note.rb
+++ b/lib/gitlab/data_builder/note.rb
@@ -44,6 +44,7 @@ module Gitlab
data[:commit] = build_data_for_commit(project, user, note)
elsif note.for_issue?
data[:issue] = note.noteable.hook_attrs
+ data[:issue][:labels] = note.noteable.labels(&:hook_attrs)
elsif note.for_merge_request?
data[:merge_request] = note.noteable.hook_attrs
elsif note.for_snippet?
diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb
index fa06fb935f7..e1e813849bf 100644
--- a/lib/gitlab/data_builder/pipeline.rb
+++ b/lib/gitlab/data_builder/pipeline.rb
@@ -19,7 +19,7 @@ module Gitlab
def hook_attrs(pipeline)
{
id: pipeline.id,
- ref: pipeline.ref,
+ ref: pipeline.source_ref,
tag: pipeline.tag,
sha: pipeline.sha,
before_sha: pipeline.before_sha,
diff --git a/lib/gitlab/diff/suggestion.rb b/lib/gitlab/diff/suggestion.rb
index 027c7a31bcf..4a3ac2106e2 100644
--- a/lib/gitlab/diff/suggestion.rb
+++ b/lib/gitlab/diff/suggestion.rb
@@ -33,6 +33,8 @@ module Gitlab
end
def to_content
+ return "" if @text.blank?
+
# The parsed suggestion doesn't have information about the correct
# ending characters (we may have a line break, or not), so we take
# this information from the last line being changed (last
diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb
index c8c03d5d001..6e17ffaf6ff 100644
--- a/lib/gitlab/diff/suggestions_parser.rb
+++ b/lib/gitlab/diff/suggestions_parser.rb
@@ -10,10 +10,12 @@ module Gitlab
# Returns an array of Gitlab::Diff::Suggestion which represents each
# suggestion in the given text.
#
- def parse(text, position:, project:)
+ def parse(text, position:, project:, supports_suggestion: true)
return [] unless position.complete?
- html = Banzai.render(text, project: nil, no_original_data: true)
+ html = Banzai.render(text, project: nil,
+ no_original_data: true,
+ suggestions_filter_enabled: supports_suggestion)
doc = Nokogiri::HTML(html)
suggestion_nodes = doc.search('pre.suggestion')
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 455588f3c66..8a2e711ec4e 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -34,34 +34,6 @@ module Gitlab
TagExistsError = Class.new(StandardError)
ChecksumError = Class.new(StandardError)
- class << self
- def create_hooks(repo_path, global_hooks_path)
- local_hooks_path = File.join(repo_path, 'hooks')
- real_local_hooks_path = :not_found
-
- begin
- real_local_hooks_path = File.realpath(local_hooks_path)
- rescue Errno::ENOENT
- # real_local_hooks_path == :not_found
- end
-
- # Do nothing if hooks already exist
- unless real_local_hooks_path == File.realpath(global_hooks_path)
- if File.exist?(local_hooks_path)
- # Move the existing hooks somewhere safe
- FileUtils.mv(
- local_hooks_path,
- "#{local_hooks_path}.old.#{Time.now.to_i}")
- end
-
- # Create the hooks symlink
- FileUtils.ln_sf(global_hooks_path, local_hooks_path)
- end
-
- true
- end
- end
-
# Directory name of repo
attr_reader :name
@@ -310,6 +282,11 @@ module Gitlab
(size.to_f / 1024).round(2)
end
+ # Return git object directory size in bytes
+ def object_directory_size
+ gitaly_repository_client.get_object_directory_size.to_f * 1024
+ end
+
# Build an array of commits.
#
# Usage.
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
index 3f13ebeb9d0..dfff6823689 100644
--- a/lib/gitlab/git_ref_validator.rb
+++ b/lib/gitlab/git_ref_validator.rb
@@ -5,12 +5,15 @@
module Gitlab
module GitRefValidator
extend self
+
+ EXPANDED_PREFIXES = %w[refs/heads/ refs/remotes/].freeze
+ DISALLOWED_PREFIXES = %w[-].freeze
+
# Validates a given name against the git reference specification
#
# Returns true for a valid reference name, false otherwise
def validate(ref_name)
- not_allowed_prefixes = %w(refs/heads/ refs/remotes/ -)
- return false if ref_name.start_with?(*not_allowed_prefixes)
+ return false if ref_name.start_with?(*(EXPANDED_PREFIXES + DISALLOWED_PREFIXES))
return false if ref_name == 'HEAD'
begin
@@ -19,5 +22,21 @@ module Gitlab
return false
end
end
+
+ def validate_merge_request_branch(ref_name)
+ return false if ref_name.start_with?(*DISALLOWED_PREFIXES)
+
+ expanded_name = if ref_name.start_with?(*EXPANDED_PREFIXES)
+ ref_name
+ else
+ "refs/heads/#{ref_name}"
+ end
+
+ begin
+ Rugged::Reference.valid_name?(expanded_name)
+ rescue ArgumentError
+ return false
+ end
+ end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 05e06eec012..e683d4e5bbe 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -33,7 +33,7 @@ module Gitlab
SERVER_FEATURE_CATFILE_CACHE = 'catfile-cache'.freeze
# Server feature flags should use '_' to separate words.
- SERVER_FEATURE_FLAGS = [SERVER_FEATURE_CATFILE_CACHE, 'delta_islands'].freeze
+ SERVER_FEATURE_FLAGS = [SERVER_FEATURE_CATFILE_CACHE].freeze
MUTEX = Mutex.new
diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb
index 077b63205a8..d16e45c964d 100644
--- a/lib/gitlab/gitaly_client/conflicts_service.rb
+++ b/lib/gitlab/gitaly_client/conflicts_service.rb
@@ -65,9 +65,9 @@ module Gitlab
our_commit_oid: @our_commit_oid,
target_repository: target_repository.gitaly_repository,
their_commit_oid: @their_commit_oid,
- source_branch: source_branch,
- target_branch: target_branch,
- commit_message: resolution.commit_message,
+ source_branch: encode_binary(source_branch),
+ target_branch: encode_binary(target_branch),
+ commit_message: encode_binary(resolution.commit_message),
user: Gitlab::Git::User.from_gitlab(resolution.user).to_gitaly
)
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index e4a59ee3f9b..b42e6cbad8d 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -107,7 +107,7 @@ module Gitlab
branch: encode_binary(branch),
target_ref: encode_binary(target_ref),
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
- message: message
+ message: encode_binary(message)
)
response = GitalyClient.call(@repository.storage, :operation_service, :user_merge_to_ref, request)
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index 68b17e86608..d8e9dccb644 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -47,6 +47,13 @@ module Gitlab
response.size
end
+ def get_object_directory_size
+ request = Gitaly::GetObjectDirectorySizeRequest.new(repository: @gitaly_repo)
+ response = GitalyClient.call(@storage, :repository_service, :get_object_directory_size, request, timeout: GitalyClient.medium_timeout)
+
+ response.size
+ end
+
def apply_gitattributes(revision)
request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: encode_binary(revision))
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request, timeout: GitalyClient.fast_timeout)
diff --git a/lib/gitlab/github_import/parallel_importer.rb b/lib/gitlab/github_import/parallel_importer.rb
index 9d81441d96e..1d3541b80c7 100644
--- a/lib/gitlab/github_import/parallel_importer.rb
+++ b/lib/gitlab/github_import/parallel_importer.rb
@@ -29,29 +29,13 @@ module Gitlab
end
def execute
- jid = generate_jid
-
- # The original import JID is the JID of the RepositoryImportWorker job,
- # which will be removed once that job completes. Reusing that JID could
- # result in StuckImportJobsWorker marking the job as stuck before we get
- # to running Stage::ImportRepositoryWorker.
- #
- # We work around this by setting the JID to a custom generated one, then
- # refreshing it in the various stages whenever necessary.
- Gitlab::SidekiqStatus
- .set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
-
- project.import_state.update_column(:jid, jid)
+ Gitlab::Import::SetAsyncJid.set_jid(project)
Stage::ImportRepositoryWorker
.perform_async(project.id)
true
end
-
- def generate_jid
- "github-importer/#{project.id}"
- end
end
end
end
diff --git a/lib/gitlab/graphql/loaders/batch_commit_loader.rb b/lib/gitlab/graphql/loaders/batch_commit_loader.rb
new file mode 100644
index 00000000000..f410c3195f8
--- /dev/null
+++ b/lib/gitlab/graphql/loaders/batch_commit_loader.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Loaders
+ class BatchCommitLoader
+ def initialize(repository, blob_id)
+ @repository, @blob_id = repository, blob_id
+ end
+
+ def find
+ BatchLoader.for(blob_id).batch(key: repository) do |blob_ids, loader, batch_args|
+ Gitlab::Git::Blob.batch_lfs_pointers(batch_args[:key], blob_ids).each do |loaded_blob|
+ loader.call(loaded_blob.id, loaded_blob.lfs_oid)
+ end
+ end
+ end
+
+ private
+
+ attr_reader :repository, :blob_id
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/loaders/batch_model_loader.rb b/lib/gitlab/graphql/loaders/batch_model_loader.rb
index 5a0099dc6b1..50d3293fcbb 100644
--- a/lib/gitlab/graphql/loaders/batch_model_loader.rb
+++ b/lib/gitlab/graphql/loaders/batch_model_loader.rb
@@ -12,7 +12,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def find
- BatchLoader.for({ model: model_class, id: model_id }).batch do |loader_info, loader|
+ BatchLoader.for({ model: model_class, id: model_id.to_i }).batch do |loader_info, loader|
per_model = loader_info.group_by { |info| info[:model] }
per_model.each do |model, info|
ids = info.map { |i| i[:id] }
diff --git a/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb b/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb
new file mode 100644
index 00000000000..5e151f4dbd7
--- /dev/null
+++ b/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Loaders
+ class BatchProjectStatisticsLoader
+ attr_reader :project_id
+
+ def initialize(project_id)
+ @project_id = project_id
+ end
+
+ def find
+ BatchLoader.for(project_id).batch do |project_ids, loader|
+ ProjectStatistics.for_project_ids(project_ids).each do |statistics|
+ loader.call(statistics.project_id, statistics)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/query_analyzers/log_query_complexity.rb b/lib/gitlab/graphql/query_analyzers/log_query_complexity.rb
deleted file mode 100644
index 95db130dbfc..00000000000
--- a/lib/gitlab/graphql/query_analyzers/log_query_complexity.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Graphql
- module QueryAnalyzers
- class LogQueryComplexity
- class << self
- def analyzer
- GraphQL::Analysis::QueryComplexity.new do |query, complexity|
- # temporary until https://gitlab.com/gitlab-org/gitlab-ce/issues/59587
- Rails.logger.info("[GraphQL Query Complexity] #{complexity} | admin? #{query.context[:current_user]&.admin?}")
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb
new file mode 100644
index 00000000000..01b55a1667f
--- /dev/null
+++ b/lib/gitlab/graphql/query_analyzers/logger_analyzer.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module QueryAnalyzers
+ class LoggerAnalyzer
+ COMPLEXITY_ANALYZER = GraphQL::Analysis::QueryComplexity.new { |query, complexity_value| complexity_value }
+ DEPTH_ANALYZER = GraphQL::Analysis::QueryDepth.new { |query, depth_value| depth_value }
+
+ def analyze?(query)
+ Feature.enabled?(:graphql_logging, default_enabled: true)
+ end
+
+ def initial_value(query)
+ variables = process_variables(query.provided_variables)
+ default_initial_values(query).merge({
+ query_string: query.query_string,
+ variables: variables
+ })
+ rescue => e
+ Gitlab::Sentry.track_exception(e)
+ default_initial_values(query)
+ end
+
+ def call(memo, visit_type, irep_node)
+ memo
+ end
+
+ def final_value(memo)
+ return if memo.nil?
+
+ analyzers = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER]
+ complexity, depth = GraphQL::Analysis.analyze_query(memo[:query], analyzers)
+
+ memo[:depth] = depth
+ memo[:complexity] = complexity
+ memo[:duration] = duration(memo[:time_started]).round(1)
+
+ GraphqlLogger.info(memo.except!(:time_started, :query))
+ rescue => e
+ Gitlab::Sentry.track_exception(e)
+ end
+
+ private
+
+ def process_variables(variables)
+ if variables.respond_to?(:to_s)
+ variables.to_s
+ else
+ variables
+ end
+ end
+
+ def duration(time_started)
+ nanoseconds = Gitlab::Metrics::System.monotonic_time - time_started
+ nanoseconds * 1000000
+ end
+
+ def default_initial_values(query)
+ {
+ time_started: Gitlab::Metrics::System.monotonic_time,
+ query_string: nil,
+ query: query,
+ variables: nil,
+ duration: nil
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/representation/tree_entry.rb b/lib/gitlab/graphql/representation/tree_entry.rb
new file mode 100644
index 00000000000..7ea83db5876
--- /dev/null
+++ b/lib/gitlab/graphql/representation/tree_entry.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Representation
+ class TreeEntry < SimpleDelegator
+ class << self
+ def decorate(entries, repository)
+ return if entries.nil?
+
+ entries.map do |entry|
+ if entry.is_a?(TreeEntry)
+ entry
+ else
+ self.new(entry, repository)
+ end
+ end
+ end
+ end
+
+ attr_accessor :repository
+
+ def initialize(raw_entry, repository)
+ @repository = repository
+
+ super(raw_entry)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql_logger.rb b/lib/gitlab/graphql_logger.rb
new file mode 100644
index 00000000000..43d917908b6
--- /dev/null
+++ b/lib/gitlab/graphql_logger.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class GraphqlLogger < Gitlab::JsonLogger
+ def self.file_name_noext
+ 'graphql_json'
+ end
+ end
+end
diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb
index d39ff8c21cc..cfc9ebe4f92 100644
--- a/lib/gitlab/hook_data/issue_builder.rb
+++ b/lib/gitlab/hook_data/issue_builder.rb
@@ -44,7 +44,8 @@ module Gitlab
human_total_time_spent: issue.human_total_time_spent,
human_time_estimate: issue.human_time_estimate,
assignee_ids: issue.assignee_ids,
- assignee_id: issue.assignee_ids.first # This key is deprecated
+ assignee_id: issue.assignee_ids.first, # This key is deprecated
+ labels: issue.labels
}
issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
diff --git a/lib/gitlab/http.rb b/lib/gitlab/http.rb
index bcd9e2be35f..db2b4dde244 100644
--- a/lib/gitlab/http.rb
+++ b/lib/gitlab/http.rb
@@ -9,9 +9,16 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
RedirectionTooDeep = Class.new(StandardError)
+ HTTP_ERRORS = [
+ SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET,
+ Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout,
+ Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError,
+ Gitlab::HTTP::RedirectionTooDeep
+ ].freeze
+
include HTTParty # rubocop:disable Gitlab/HTTParty
- connection_adapter ProxyHTTPConnectionAdapter
+ connection_adapter HTTPConnectionAdapter
def self.perform_request(http_method, path, options, &block)
super
diff --git a/lib/gitlab/proxy_http_connection_adapter.rb b/lib/gitlab/http_connection_adapter.rb
index a64cb47e77e..41eab3658bc 100644
--- a/lib/gitlab/proxy_http_connection_adapter.rb
+++ b/lib/gitlab/http_connection_adapter.rb
@@ -10,17 +10,19 @@
#
# This option will take precedence over the global setting.
module Gitlab
- class ProxyHTTPConnectionAdapter < HTTParty::ConnectionAdapter
+ class HTTPConnectionAdapter < HTTParty::ConnectionAdapter
def connection
- unless allow_local_requests?
- begin
- Gitlab::UrlBlocker.validate!(uri, allow_local_network: false)
- rescue Gitlab::UrlBlocker::BlockedUrlError => e
- raise Gitlab::HTTP::BlockedUrlError, "URL '#{uri}' is blocked: #{e.message}"
- end
+ begin
+ @uri, hostname = Gitlab::UrlBlocker.validate!(uri, allow_local_network: allow_local_requests?,
+ allow_localhost: allow_local_requests?,
+ dns_rebind_protection: dns_rebind_protection?)
+ rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ raise Gitlab::HTTP::BlockedUrlError, "URL '#{uri}' is blocked: #{e.message}"
end
- super
+ super.tap do |http|
+ http.hostname_override = hostname if hostname
+ end
end
private
@@ -29,6 +31,12 @@ module Gitlab
options.fetch(:allow_local_requests, allow_settings_local_requests?)
end
+ def dns_rebind_protection?
+ return false if Gitlab.http_proxy_env?
+
+ Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
+ end
+
def allow_settings_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
end
diff --git a/lib/gitlab/import/set_async_jid.rb b/lib/gitlab/import/set_async_jid.rb
new file mode 100644
index 00000000000..584d24045ee
--- /dev/null
+++ b/lib/gitlab/import/set_async_jid.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# The original import JID is the JID of the RepositoryImportWorker job,
+# which will be removed once that job completes. Reusing that JID could
+# result in StuckImportJobsWorker marking the job as stuck before we get
+# to running Stage::ImportRepositoryWorker.
+#
+# We work around this by setting the JID to a custom generated one, then
+# refreshing it in the various stages whenever necessary.
+module Gitlab
+ module Import
+ module SetAsyncJid
+ def self.set_jid(project)
+ jid = generate_jid(project)
+
+ Gitlab::SidekiqStatus
+ .set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+
+ project.import_state.update_column(:jid, jid)
+ end
+
+ def self.generate_jid(project)
+ "async-import/#{project.id}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb
index 93b37b7bc5f..c28a1674018 100644
--- a/lib/gitlab/import_export/attribute_cleaner.rb
+++ b/lib/gitlab/import_export/attribute_cleaner.rb
@@ -4,6 +4,7 @@ module Gitlab
module ImportExport
class AttributeCleaner
ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
+ PROHIBITED_REFERENCES = Regexp.union(/\Acached_markdown_version\Z/, /_id\Z/, /_html\Z/).freeze
def self.clean(*args)
new(*args).clean
@@ -24,7 +25,11 @@ module Gitlab
private
def prohibited_key?(key)
- key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key)
+ key =~ PROHIBITED_REFERENCES && !permitted_key?(key)
+ end
+
+ def permitted_key?(key)
+ ALLOWED_REFERENCES.include?(key)
end
def excluded_key?(key)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 7bbcb53f016..71c44af9254 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -156,6 +156,9 @@ excluded_attributes:
- :when
- :artifacts_file
- :artifacts_metadata
+ - :artifacts_file_store
+ - :artifacts_metadata_store
+ - :artifacts_size
- :commands
push_event_payload:
- :event_id
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index 6be95a16513..a154de5419e 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -59,7 +59,11 @@ module Gitlab
end
def member_hash(member)
- parsed_hash(member).merge('source_id' => @project.id, 'importing' => true)
+ parsed_hash(member).merge(
+ 'source_id' => @project.id,
+ 'importing' => true,
+ 'access_level' => [member['access_level'], ProjectMember::MAINTAINER].min
+ ).except('user_id')
end
def parsed_hash(member)
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index e1e70a008d9..efd3f550a22 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -153,6 +153,9 @@ module Gitlab
@relation_hash.delete('trace') # old export files have trace
@relation_hash.delete('token')
@relation_hash.delete('commands')
+ @relation_hash.delete('artifacts_file_store')
+ @relation_hash.delete('artifacts_metadata_store')
+ @relation_hash.delete('artifacts_size')
imported_object
elsif @relation_name == :merge_requests
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 67952ca0f2d..e4d625b5738 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -20,7 +20,8 @@ module Gitlab
ImportSource.new('git', 'Repo by URL', nil),
ImportSource.new('gitlab_project', 'GitLab export', Gitlab::ImportExport::Importer),
ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer),
- ImportSource.new('manifest', 'Manifest file', nil)
+ ImportSource.new('manifest', 'Manifest file', nil),
+ ImportSource.new('phabricator', 'Phabricator', Gitlab::PhabricatorImport::Importer)
].freeze
class << self
diff --git a/lib/gitlab/kubernetes/errors.rb b/lib/gitlab/kubernetes/errors.rb
new file mode 100644
index 00000000000..81bf636eef7
--- /dev/null
+++ b/lib/gitlab/kubernetes/errors.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ module Errors
+ CONNECTION = [
+ SocketError,
+ OpenSSL::SSL::SSLError,
+ Errno::ECONNRESET,
+ Errno::ENETUNREACH,
+ Errno::ECONNREFUSED,
+ Errno::EHOSTUNREACH,
+ Net::OpenTimeout,
+ Net::ReadTimeout,
+ IPAddr::InvalidAddressError
+ ].freeze
+
+ AUTHENTICATION = [
+ OpenSSL::X509::CertificateError
+ ].freeze
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/helm/api.rb b/lib/gitlab/kubernetes/helm/api.rb
index ff1dadf9247..978cafae9ac 100644
--- a/lib/gitlab/kubernetes/helm/api.rb
+++ b/lib/gitlab/kubernetes/helm/api.rb
@@ -24,6 +24,7 @@ module Gitlab
def uninstall(command)
namespace.ensure_exists!
+ create_config_map(command)
delete_pod!(command.pod_name)
kubeclient.create_pod(command.pod_resource)
diff --git a/lib/gitlab/lets_encrypt/challenge.rb b/lib/gitlab/lets_encrypt/challenge.rb
index 6a7f5e965c5..136268c974b 100644
--- a/lib/gitlab/lets_encrypt/challenge.rb
+++ b/lib/gitlab/lets_encrypt/challenge.rb
@@ -7,7 +7,7 @@ module Gitlab
@acme_challenge = acme_challenge
end
- delegate :url, :token, :file_content, :status, :request_validation, to: :acme_challenge
+ delegate :token, :file_content, :status, :request_validation, to: :acme_challenge
private
diff --git a/lib/gitlab/lets_encrypt/client.rb b/lib/gitlab/lets_encrypt/client.rb
index d7468b06767..66aea137012 100644
--- a/lib/gitlab/lets_encrypt/client.rb
+++ b/lib/gitlab/lets_encrypt/client.rb
@@ -3,6 +3,8 @@
module Gitlab
module LetsEncrypt
class Client
+ include Gitlab::Utils::StrongMemoize
+
PRODUCTION_DIRECTORY_URL = 'https://acme-v02.api.letsencrypt.org/directory'
STAGING_DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory'
@@ -35,6 +37,8 @@ module Gitlab
def enabled?
return false unless Feature.enabled?(:pages_auto_ssl)
+ return false unless private_key
+
Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted
end
@@ -45,7 +49,11 @@ module Gitlab
end
def private_key
- @private_key ||= OpenSSL::PKey.read(Gitlab::Application.secrets.lets_encrypt_private_key)
+ strong_memoize(:private_key) do
+ private_key_string = Gitlab::CurrentSettings.lets_encrypt_private_key
+ private_key_string ||= generate_private_key
+ OpenSSL::PKey.read(private_key_string) if private_key_string
+ end
end
def admin_email
@@ -69,6 +77,19 @@ module Gitlab
STAGING_DIRECTORY_URL
end
end
+
+ def generate_private_key
+ return if Gitlab::Database.read_only?
+
+ application_settings = Gitlab::CurrentSettings.current_application_settings
+ application_settings.with_lock do
+ unless application_settings.lets_encrypt_private_key
+ application_settings.update(lets_encrypt_private_key: OpenSSL::PKey::RSA.new(4096).to_pem)
+ end
+
+ application_settings.lets_encrypt_private_key
+ end
+ end
end
end
end
diff --git a/lib/gitlab/lets_encrypt/order.rb b/lib/gitlab/lets_encrypt/order.rb
index 5109b5e9843..9c2365f98a8 100644
--- a/lib/gitlab/lets_encrypt/order.rb
+++ b/lib/gitlab/lets_encrypt/order.rb
@@ -13,7 +13,16 @@ module Gitlab
::Gitlab::LetsEncrypt::Challenge.new(challenge)
end
- delegate :url, :status, to: :acme_order
+ def request_certificate(domain:, private_key:)
+ csr = ::Acme::Client::CertificateRequest.new(
+ private_key: OpenSSL::PKey.read(private_key),
+ subject: { common_name: domain }
+ )
+
+ acme_order.finalize(csr: csr)
+ end
+
+ delegate :url, :status, :expires, :certificate, to: :acme_order
private
diff --git a/lib/gitlab/markdown_cache.rb b/lib/gitlab/markdown_cache.rb
new file mode 100644
index 00000000000..0354c710a3f
--- /dev/null
+++ b/lib/gitlab/markdown_cache.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ # Increment this number every time the renderer changes its output
+ CACHE_COMMONMARK_VERSION_START = 10
+ CACHE_COMMONMARK_VERSION = 16
+
+ BaseError = Class.new(StandardError)
+ UnsupportedClassError = Class.new(BaseError)
+ end
+end
diff --git a/lib/gitlab/markdown_cache/active_record/extension.rb b/lib/gitlab/markdown_cache/active_record/extension.rb
new file mode 100644
index 00000000000..f3abe631779
--- /dev/null
+++ b/lib/gitlab/markdown_cache/active_record/extension.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ module ActiveRecord
+ module Extension
+ extend ActiveSupport::Concern
+
+ included do
+ # Using before_update here conflicts with elasticsearch-model somehow
+ before_create :refresh_markdown_cache, if: :invalidated_markdown_cache?
+ before_update :refresh_markdown_cache, if: :invalidated_markdown_cache?
+ end
+
+ # Always exclude _html fields from attributes (including serialization).
+ # They contain unredacted HTML, which would be a security issue
+ def attributes
+ attrs = super
+ html_fields = cached_markdown_fields.html_fields
+ whitelisted = cached_markdown_fields.html_fields_whitelisted
+ exclude_fields = html_fields - whitelisted
+
+ attrs.except!(*exclude_fields)
+ attrs.delete('cached_markdown_version') if whitelisted.empty?
+
+ attrs
+ end
+
+ def changed_markdown_fields
+ changed_attributes.keys.map(&:to_s) & cached_markdown_fields.markdown_fields.map(&:to_s)
+ end
+
+ def write_markdown_field(field_name, value)
+ write_attribute(field_name, value)
+ end
+
+ def markdown_field_changed?(field_name)
+ attribute_changed?(field_name)
+ end
+
+ def save_markdown(updates)
+ return unless persisted? && Gitlab::Database.read_write?
+
+ update_columns(updates)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown_cache/field_data.rb b/lib/gitlab/markdown_cache/field_data.rb
new file mode 100644
index 00000000000..14622c0f186
--- /dev/null
+++ b/lib/gitlab/markdown_cache/field_data.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ # Knows about the relationship between markdown and html field names, and
+ # stores the rendering contexts for the latter
+ class FieldData
+ def initialize
+ @data = {}
+ end
+
+ delegate :[], :[]=, to: :@data
+
+ def markdown_fields
+ @data.keys
+ end
+
+ def html_field(markdown_field)
+ "#{markdown_field}_html"
+ end
+
+ def html_fields
+ @html_fields ||= markdown_fields.map { |field| html_field(field) }
+ end
+
+ def html_fields_whitelisted
+ markdown_fields.each_with_object([]) do |field, fields|
+ if @data[field].fetch(:whitelisted, false)
+ fields << html_field(field)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown_cache/redis/extension.rb b/lib/gitlab/markdown_cache/redis/extension.rb
new file mode 100644
index 00000000000..97fc23343b4
--- /dev/null
+++ b/lib/gitlab/markdown_cache/redis/extension.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ module Redis
+ module Extension
+ extend ActiveSupport::Concern
+
+ attr_reader :cached_markdown_version
+
+ class_methods do
+ def cache_markdown_field(markdown_field, context = {})
+ super
+
+ # define the `[field]_html` accessor
+ html_field = cached_markdown_fields.html_field(markdown_field)
+ define_method(html_field) do
+ load_cached_markdown unless markdown_data_loaded?
+
+ instance_variable_get("@#{html_field}")
+ end
+ end
+ end
+
+ private
+
+ def save_markdown(updates)
+ markdown_store.save(updates)
+ end
+
+ def write_markdown_field(field_name, value)
+ instance_variable_set("@#{field_name}", value)
+ end
+
+ def markdown_field_changed?(field_name)
+ false
+ end
+
+ def changed_markdown_fields
+ []
+ end
+
+ def cached_markdown
+ @cached_data ||= markdown_store.read
+ end
+
+ def load_cached_markdown
+ cached_markdown.each do |field_name, value|
+ write_markdown_field(field_name, value)
+ end
+ end
+
+ def markdown_data_loaded?
+ cached_markdown_version.present? || markdown_store.loaded?
+ end
+
+ def markdown_store
+ @store ||= Gitlab::MarkdownCache::Redis::Store.new(self)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown_cache/redis/store.rb b/lib/gitlab/markdown_cache/redis/store.rb
new file mode 100644
index 00000000000..0f954404808
--- /dev/null
+++ b/lib/gitlab/markdown_cache/redis/store.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module MarkdownCache
+ module Redis
+ class Store
+ EXPIRES_IN = 1.day
+
+ def initialize(subject)
+ @subject = subject
+ @loaded = false
+ end
+
+ def save(updates)
+ @loaded = false
+
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmset(markdown_cache_key, updates)
+ r.expire(markdown_cache_key, EXPIRES_IN)
+ end
+ end
+
+ def read
+ @loaded = true
+
+ results = Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmget(markdown_cache_key, *fields)
+ end
+ # The value read from redis is a string, so we're converting it back
+ # to an int.
+ results[:cached_markdown_version] = results[:cached_markdown_version].to_i
+ results
+ end
+
+ def loaded?
+ @loaded
+ end
+
+ private
+
+ def fields
+ @fields ||= @subject.cached_markdown_fields.html_fields + [:cached_markdown_version]
+ end
+
+ def markdown_cache_key
+ unless @subject.respond_to?(:cache_key)
+ raise Gitlab::MarkdownCache::UnsupportedClassError,
+ "This class has no cache_key to use for caching"
+ end
+
+ "markdown_cache:#{@subject.cache_key}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/base_service.rb b/lib/gitlab/metrics/dashboard/base_service.rb
index 94aabd0466c..4664aee71f6 100644
--- a/lib/gitlab/metrics/dashboard/base_service.rb
+++ b/lib/gitlab/metrics/dashboard/base_service.rb
@@ -6,13 +6,13 @@ module Gitlab
module Metrics
module Dashboard
class BaseService < ::BaseService
- DASHBOARD_LAYOUT_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardLayoutError
+ PROCESSING_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError
def get_dashboard
return error("#{dashboard_path} could not be found.", :not_found) unless path_available?
success(dashboard: process_dashboard)
- rescue DASHBOARD_LAYOUT_ERROR => e
+ rescue PROCESSING_ERROR => e
error(e.message, :unprocessable_entity)
end
diff --git a/lib/gitlab/metrics/dashboard/processor.rb b/lib/gitlab/metrics/dashboard/processor.rb
index dd986020693..a33a010ad97 100644
--- a/lib/gitlab/metrics/dashboard/processor.rb
+++ b/lib/gitlab/metrics/dashboard/processor.rb
@@ -11,11 +11,13 @@ module Gitlab
SYSTEM_SEQUENCE = [
Stages::CommonMetricsInserter,
Stages::ProjectMetricsInserter,
+ Stages::EndpointInserter,
Stages::Sorter
].freeze
PROJECT_SEQUENCE = [
Stages::CommonMetricsInserter,
+ Stages::EndpointInserter,
Stages::Sorter
].freeze
diff --git a/lib/gitlab/metrics/dashboard/stages/base_stage.rb b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
index a6d1f974556..0db7b176e8d 100644
--- a/lib/gitlab/metrics/dashboard/stages/base_stage.rb
+++ b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
@@ -5,7 +5,8 @@ module Gitlab
module Dashboard
module Stages
class BaseStage
- DashboardLayoutError = Class.new(StandardError)
+ DashboardProcessingError = Class.new(StandardError)
+ LayoutError = Class.new(DashboardProcessingError)
DEFAULT_PANEL_TYPE = 'area-chart'
@@ -25,15 +26,15 @@ module Gitlab
protected
def missing_panel_groups!
- raise DashboardLayoutError.new('Top-level key :panel_groups must be an array')
+ raise LayoutError.new('Top-level key :panel_groups must be an array')
end
def missing_panels!
- raise DashboardLayoutError.new('Each "panel_group" must define an array :panels')
+ raise LayoutError.new('Each "panel_group" must define an array :panels')
end
def missing_metrics!
- raise DashboardLayoutError.new('Each "panel" must define an array :metrics')
+ raise LayoutError.new('Each "panel" must define an array :metrics')
end
def for_metrics
diff --git a/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb
new file mode 100644
index 00000000000..2a959854be0
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Stages
+ class EndpointInserter < BaseStage
+ MissingQueryError = Class.new(DashboardProcessingError)
+
+ def transform!
+ for_metrics do |metric|
+ metric[:prometheus_endpoint_path] = endpoint_for_metric(metric)
+ end
+ end
+
+ private
+
+ def endpoint_for_metric(metric)
+ Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
+ project,
+ environment,
+ proxy_path: query_type(metric),
+ query: query_for_metric(metric)
+ )
+ end
+
+ def query_type(metric)
+ metric[:query] ? :query : :query_range
+ end
+
+ def query_for_metric(metric)
+ query = metric[query_type(metric)]
+
+ raise MissingQueryError.new('Each "metric" must define one of :query or :query_range') unless query
+
+ query
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/samplers/puma_sampler.rb b/lib/gitlab/metrics/samplers/puma_sampler.rb
new file mode 100644
index 00000000000..25e40c70230
--- /dev/null
+++ b/lib/gitlab/metrics/samplers/puma_sampler.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'puma/state_file'
+
+module Gitlab
+ module Metrics
+ module Samplers
+ class PumaSampler < BaseSampler
+ def metrics
+ @metrics ||= init_metrics
+ end
+
+ def init_metrics
+ {
+ puma_workers: ::Gitlab::Metrics.gauge(:puma_workers, 'Total number of workers'),
+ puma_running_workers: ::Gitlab::Metrics.gauge(:puma_running_workers, 'Number of active workers'),
+ puma_stale_workers: ::Gitlab::Metrics.gauge(:puma_stale_workers, 'Number of stale workers'),
+ puma_phase: ::Gitlab::Metrics.gauge(:puma_phase, 'Phase number (increased during phased restarts)'),
+ puma_running: ::Gitlab::Metrics.gauge(:puma_running, 'Number of running threads'),
+ puma_queued_connections: ::Gitlab::Metrics.gauge(:puma_queued_connections, 'Number of connections in that worker\'s "todo" set waiting for a worker thread'),
+ puma_active_connections: ::Gitlab::Metrics.gauge(:puma_active_connections, 'Number of threads processing a request'),
+ puma_pool_capacity: ::Gitlab::Metrics.gauge(:puma_pool_capacity, 'Number of requests the worker is capable of taking right now'),
+ puma_max_threads: ::Gitlab::Metrics.gauge(:puma_max_threads, 'Maximum number of worker threads'),
+ puma_idle_threads: ::Gitlab::Metrics.gauge(:puma_idle_threads, 'Number of spawned threads which are not processing a request')
+ }
+ end
+
+ def sample
+ json_stats = puma_stats
+ return unless json_stats
+
+ stats = JSON.parse(json_stats)
+
+ if cluster?(stats)
+ sample_cluster(stats)
+ else
+ sample_single_worker(stats)
+ end
+ end
+
+ private
+
+ def puma_stats
+ Puma.stats
+ rescue NoMethodError
+ Rails.logger.info "PumaSampler: stats are not available yet, waiting for Puma to boot"
+ nil
+ end
+
+ def sample_cluster(stats)
+ set_master_metrics(stats)
+
+ stats['worker_status'].each do |worker|
+ last_status = worker['last_status']
+ labels = { worker: "worker_#{worker['index']}" }
+
+ metrics[:puma_phase].set(labels, worker['phase'])
+ set_worker_metrics(last_status, labels) if last_status.present?
+ end
+ end
+
+ def sample_single_worker(stats)
+ metrics[:puma_workers].set({}, 1)
+ metrics[:puma_running_workers].set({}, 1)
+
+ set_worker_metrics(stats)
+ end
+
+ def cluster?(stats)
+ stats['worker_status'].present?
+ end
+
+ def set_master_metrics(stats)
+ labels = { worker: "master" }
+
+ metrics[:puma_workers].set(labels, stats['workers'])
+ metrics[:puma_running_workers].set(labels, stats['booted_workers'])
+ metrics[:puma_stale_workers].set(labels, stats['old_workers'])
+ metrics[:puma_phase].set(labels, stats['phase'])
+ end
+
+ def set_worker_metrics(stats, labels = {})
+ metrics[:puma_running].set(labels, stats['running'])
+ metrics[:puma_queued_connections].set(labels, stats['backlog'])
+ metrics[:puma_active_connections].set(labels, stats['max_threads'] - stats['pool_capacity'])
+ metrics[:puma_pool_capacity].set(labels, stats['pool_capacity'])
+ metrics[:puma_max_threads].set(labels, stats['max_threads'])
+ metrics[:puma_idle_threads].set(labels, stats['running'] + stats['pool_capacity'] - stats['max_threads'])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 18a69321905..17eacbd21d8 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -23,25 +23,32 @@ module Gitlab
end
def init_metrics
- metrics = {}
- metrics[:sampler_duration] = ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels)
- metrics[:total_time] = ::Gitlab::Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels)
+ metrics = {
+ file_descriptors: ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels, :livesum),
+ memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels, :livesum),
+ process_cpu_seconds_total: ::Gitlab::Metrics.gauge(with_prefix(:process, :cpu_seconds_total), 'Process CPU seconds total'),
+ process_max_fds: ::Gitlab::Metrics.gauge(with_prefix(:process, :max_fds), 'Process max fds'),
+ process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used', labels, :livesum),
+ process_start_time_seconds: ::Gitlab::Metrics.gauge(with_prefix(:process, :start_time_seconds), 'Process start time seconds'),
+ sampler_duration: ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels),
+ total_time: ::Gitlab::Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels)
+ }
+
GC.stat.keys.each do |key|
metrics[key] = ::Gitlab::Metrics.gauge(with_prefix(:gc_stat, key), to_doc_string(key), labels, :livesum)
end
- metrics[:memory_usage] = ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels, :livesum)
- metrics[:file_descriptors] = ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels, :livesum)
-
metrics
end
def sample
start_time = System.monotonic_time
- metrics[:memory_usage].set(labels.merge(worker_label), System.memory_usage)
metrics[:file_descriptors].set(labels.merge(worker_label), System.file_descriptor_count)
-
+ metrics[:process_cpu_seconds_total].set(labels.merge(worker_label), ::Gitlab::Metrics::System.cpu_time)
+ metrics[:process_max_fds].set(labels.merge(worker_label), ::Gitlab::Metrics::System.max_open_file_descriptors)
+ metrics[:process_start_time_seconds].set(labels.merge(worker_label), ::Gitlab::Metrics::System.process_start_time)
+ set_memory_usage_metrics
sample_gc
metrics[:sampler_duration].increment(labels, System.monotonic_time - start_time)
@@ -61,11 +68,19 @@ module Gitlab
metrics[:total_time].increment(labels, GC::Profiler.total_time)
end
+ def set_memory_usage_metrics
+ memory_usage = System.memory_usage
+ memory_labels = labels.merge(worker_label)
+
+ metrics[:memory_bytes].set(memory_labels, memory_usage)
+ metrics[:process_resident_memory_bytes].set(memory_labels, memory_usage)
+ end
+
def worker_label
+ return { worker: 'sidekiq' } if Sidekiq.server?
return {} unless defined?(Unicorn::Worker)
worker_no = ::Prometheus::Client::Support::Unicorn.worker_id
-
if worker_no
{ worker: worker_no }
else
diff --git a/lib/gitlab/metrics/samplers/unicorn_sampler.rb b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
index bec64e864b3..9af7e0afed4 100644
--- a/lib/gitlab/metrics/samplers/unicorn_sampler.rb
+++ b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
@@ -4,16 +4,16 @@ module Gitlab
module Metrics
module Samplers
class UnicornSampler < BaseSampler
- def initialize(interval)
- super(interval)
+ def metrics
+ @metrics ||= init_metrics
end
- def unicorn_active_connections
- @unicorn_active_connections ||= ::Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
- end
-
- def unicorn_queued_connections
- @unicorn_queued_connections ||= ::Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
+ def init_metrics
+ {
+ unicorn_active_connections: ::Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max),
+ unicorn_queued_connections: ::Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max),
+ unicorn_workers: ::Gitlab::Metrics.gauge(:unicorn_workers, 'Unicorn workers')
+ }
end
def enabled?
@@ -23,14 +23,13 @@ module Gitlab
def sample
Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
- unicorn_active_connections.set({ socket_type: 'tcp', socket_address: addr }, stats.active)
- unicorn_queued_connections.set({ socket_type: 'tcp', socket_address: addr }, stats.queued)
+ set_unicorn_connection_metrics('tcp', addr, stats)
end
-
Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
- unicorn_active_connections.set({ socket_type: 'unix', socket_address: addr }, stats.active)
- unicorn_queued_connections.set({ socket_type: 'unix', socket_address: addr }, stats.queued)
+ set_unicorn_connection_metrics('unix', addr, stats)
end
+
+ metrics[:unicorn_workers].set({}, unicorn_workers_count)
end
private
@@ -39,6 +38,13 @@ module Gitlab
@tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
end
+ def set_unicorn_connection_metrics(type, addr, stats)
+ labels = { socket_type: type, socket_address: addr }
+
+ metrics[:unicorn_active_connections].set(labels, stats.active)
+ metrics[:unicorn_queued_connections].set(labels, stats.queued)
+ end
+
def unix_listeners
@unix_listeners ||= Unicorn.listener_names - tcp_listeners
end
@@ -46,6 +52,10 @@ module Gitlab
def unicorn_with_listeners?
defined?(Unicorn) && Unicorn.listener_names.any?
end
+
+ def unicorn_workers_count
+ `pgrep -f '[u]nicorn_rails worker.+ #{Rails.root.to_s}'`.split.count
+ end
end
end
end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 426496855e3..33c0de91c11 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -23,6 +23,22 @@ module Gitlab
def self.file_descriptor_count
Dir.glob('/proc/self/fd/*').length
end
+
+ def self.max_open_file_descriptors
+ match = File.read('/proc/self/limits').match(/Max open files\s*(\d+)/)
+
+ return unless match && match[1]
+
+ match[1].to_i
+ end
+
+ def self.process_start_time
+ fields = File.read('/proc/self/stat').split
+
+ # fields[21] is linux proc stat field "(22) starttime".
+ # The value is expressed in clock ticks, divide by clock ticks for seconds.
+ ( fields[21].to_i || 0 ) / clk_tck
+ end
else
def self.memory_usage
0.0
@@ -31,6 +47,14 @@ module Gitlab
def self.file_descriptor_count
0
end
+
+ def self.max_open_file_descriptors
+ 0
+ end
+
+ def self.process_start_time
+ 0
+ end
end
# THREAD_CPUTIME is not supported on OS X
@@ -59,6 +83,10 @@ module Gitlab
def self.monotonic_time
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
end
+
+ def self.clk_tck
+ @clk_tck ||= `getconf CLK_TCK`.to_i
+ end
end
end
end
diff --git a/lib/gitlab/omniauth_initializer.rb b/lib/gitlab/omniauth_initializer.rb
index e0ac9eec1f2..83204fa5d18 100644
--- a/lib/gitlab/omniauth_initializer.rb
+++ b/lib/gitlab/omniauth_initializer.rb
@@ -36,12 +36,25 @@ module Gitlab
hash_arguments = provider['args'].merge(provider_defaults(provider))
# A Hash from the configuration will be passed as is.
- provider_arguments << hash_arguments.symbolize_keys
+ provider_arguments << normalize_hash_arguments(hash_arguments)
end
provider_arguments
end
+ def normalize_hash_arguments(args)
+ args.symbolize_keys!
+
+ # Rails 5.1 deprecated the use of string names in the middleware
+ # (https://github.com/rails/rails/commit/83b767ce), so we need to
+ # pass in the actual class to Devise.
+ if args[:strategy_class].is_a?(String)
+ args[:strategy_class] = args[:strategy_class].constantize
+ end
+
+ args
+ end
+
def provider_defaults(provider)
case provider['name']
when 'cas3'
@@ -50,6 +63,12 @@ module Gitlab
{ remote_sign_out_handler: authentiq_signout_handler }
when 'shibboleth'
{ fail_with_empty_uid: true }
+ when 'openid_connect'
+ # If a name argument is omitted, OmniAuth will expect that the
+ # matching route is /auth/users/openidconnect instead of
+ # /auth/users/openid_connect because of
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/62208#note_178780341.
+ { name: 'openid_connect' }
else
{}
end
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index aa2c1ac9cef..a07b1246bee 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -58,7 +58,6 @@ module Gitlab
uploads
users
v2
- visual-review-toolbar.js
].freeze
# This list should contain all words following `/*namespace_id/:project_id` in
diff --git a/lib/gitlab/performance_bar/peek_query_tracker.rb b/lib/gitlab/performance_bar/peek_query_tracker.rb
index 16c16aa0265..3a27e26eaba 100644
--- a/lib/gitlab/performance_bar/peek_query_tracker.rb
+++ b/lib/gitlab/performance_bar/peek_query_tracker.rb
@@ -27,15 +27,16 @@ module Gitlab
subscribe('sql.active_record') do |_, start, finish, _, data|
if Gitlab::SafeRequestStore.store[:peek_enabled]
unless data[:cached]
- track_query(data[:sql].strip, data[:binds], start, finish)
+ backtrace = Gitlab::Profiler.clean_backtrace(caller)
+ track_query(data[:sql].strip, data[:binds], backtrace, start, finish)
end
end
end
end
- def track_query(raw_query, bindings, start, finish)
+ def track_query(raw_query, bindings, backtrace, start, finish)
duration = (finish - start) * 1000.0
- query_info = { duration: duration.round(3), sql: raw_query }
+ query_info = { duration: duration.round(3), sql: raw_query, backtrace: backtrace }
PEEK_DB_CLIENT.query_details << query_info
end
diff --git a/lib/gitlab/phabricator_import.rb b/lib/gitlab/phabricator_import.rb
new file mode 100644
index 00000000000..3885a9934d5
--- /dev/null
+++ b/lib/gitlab/phabricator_import.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ BaseError = Class.new(StandardError)
+
+ def self.available?
+ Feature.enabled?(:phabricator_import) &&
+ Gitlab::CurrentSettings.import_sources.include?('phabricator')
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/base_worker.rb b/lib/gitlab/phabricator_import/base_worker.rb
new file mode 100644
index 00000000000..b69c65e78f8
--- /dev/null
+++ b/lib/gitlab/phabricator_import/base_worker.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+# All workers within a Phabricator import should inherit from this worker and
+# implement the `#import` method. The jobs should then be scheduled using the
+# `.schedule` class method instead of `.perform_async`
+#
+# Doing this makes sure that only one job of that type is running at the same time
+# for a certain project. This will avoid deadlocks. When a job is already running
+# we'll wait for it for 10 times 5 seconds to restart. If the running job hasn't
+# finished, by then, we'll retry in 30 seconds.
+#
+# It also makes sure that we keep the import state of the project up to date:
+# - It keeps track of the jobs so we know how many jobs are running for the
+# project
+# - It refreshes the import jid, so it doesn't get cleaned up by the
+# `StuckImportJobsWorker`
+# - It marks the import as failed if a job failed to many times
+# - It marks the import as finished when all remaining jobs are done
+module Gitlab
+ module PhabricatorImport
+ class BaseWorker
+ include ApplicationWorker
+ include ProjectImportOptions # This marks the project as failed after too many tries
+ include Gitlab::ExclusiveLeaseHelpers
+
+ class << self
+ def schedule(project_id, *args)
+ perform_async(project_id, *args)
+ add_job(project_id)
+ end
+
+ def add_job(project_id)
+ worker_state(project_id).add_job
+ end
+
+ def remove_job(project_id)
+ worker_state(project_id).remove_job
+ end
+
+ def worker_state(project_id)
+ Gitlab::PhabricatorImport::WorkerState.new(project_id)
+ end
+ end
+
+ def perform(project_id, *args)
+ in_lock("#{self.class.name.underscore}/#{project_id}/#{args}", ttl: 2.hours, sleep_sec: 5.seconds) do
+ project = Project.find_by_id(project_id)
+ next unless project
+
+ # Bail if the import job already failed
+ next unless project.import_state&.in_progress?
+
+ project.import_state.refresh_jid_expiration
+
+ import(project, *args)
+
+ # If this is the last running job, finish the import
+ project.after_import if self.class.worker_state(project_id).running_count < 2
+
+ self.class.remove_job(project_id)
+ end
+ rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ # Reschedule a job if there was already a running one
+ # Running them at the same time could cause a deadlock updating the same
+ # resource
+ self.class.perform_in(30.seconds, project_id, *args)
+ end
+
+ private
+
+ def import(project, *args)
+ importer_class.new(project, *args).execute
+ end
+
+ def importer_class
+ raise NotImplementedError, "Implement `#{__method__}` on #{self.class}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/cache/map.rb b/lib/gitlab/phabricator_import/cache/map.rb
new file mode 100644
index 00000000000..fa8b37b20ca
--- /dev/null
+++ b/lib/gitlab/phabricator_import/cache/map.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Cache
+ class Map
+ def initialize(project)
+ @project = project
+ end
+
+ def get_gitlab_model(phabricator_id)
+ cached_info = get(phabricator_id)
+ return unless cached_info[:classname] && cached_info[:database_id]
+
+ cached_info[:classname].constantize.find_by_id(cached_info[:database_id])
+ end
+
+ def set_gitlab_model(object, phabricator_id)
+ set(object.class, object.id, phabricator_id)
+ end
+
+ private
+
+ attr_reader :project
+
+ def set(klass_name, object_id, phabricator_id)
+ key = cache_key_for_phabricator_id(phabricator_id)
+
+ redis.with do |r|
+ r.multi do |multi|
+ multi.mapped_hmset(key,
+ { classname: klass_name, database_id: object_id })
+ multi.expire(key, timeout)
+ end
+ end
+ end
+
+ def get(phabricator_id)
+ key = cache_key_for_phabricator_id(phabricator_id)
+
+ redis.with do |r|
+ r.pipelined do |pipe|
+ # Extend the TTL when a key was
+ pipe.expire(key, timeout)
+ pipe.mapped_hmget(key, :classname, :database_id)
+ end.last
+ end
+ end
+
+ def cache_key_for_phabricator_id(phabricator_id)
+ "#{Redis::Cache::CACHE_NAMESPACE}/phabricator-import/#{project.id}/#{phabricator_id}"
+ end
+
+ def redis
+ Gitlab::Redis::Cache
+ end
+
+ def timeout
+ # Setting the timeout to the same one as we do for clearing stuck jobs
+ # this makes sure all cache is available while the import is running.
+ StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit.rb b/lib/gitlab/phabricator_import/conduit.rb
new file mode 100644
index 00000000000..4c64d737389
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ ApiError = Class.new(Gitlab::PhabricatorImport::BaseError)
+ ResponseError = Class.new(ApiError)
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/client.rb b/lib/gitlab/phabricator_import/conduit/client.rb
new file mode 100644
index 00000000000..4469a3f5849
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/client.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Client
+ def initialize(phabricator_url, api_token)
+ @phabricator_url = phabricator_url
+ @api_token = api_token
+ end
+
+ def get(path, params: {})
+ response = Gitlab::HTTP.get(build_url(path), body: build_params(params), headers: headers)
+ Response.parse!(response)
+ rescue *Gitlab::HTTP::HTTP_ERRORS => e
+ # Wrap all errors from the API into an API-error.
+ raise ApiError.new(e)
+ end
+
+ private
+
+ attr_reader :phabricator_url, :api_token
+
+ def headers
+ { "Accept" => 'application/json' }
+ end
+
+ def build_url(path)
+ URI.join(phabricator_url, '/api/', path).to_s
+ end
+
+ def build_params(params)
+ params = params.dup
+ params.compact!
+ params.reverse_merge!("api.token" => api_token)
+
+ CGI.unescape(params.to_query)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/maniphest.rb b/lib/gitlab/phabricator_import/conduit/maniphest.rb
new file mode 100644
index 00000000000..848b71e49e7
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/maniphest.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Maniphest
+ def initialize(phabricator_url:, api_token:)
+ @client = Client.new(phabricator_url, api_token)
+ end
+
+ def tasks(after: nil)
+ TasksResponse.new(get_tasks(after))
+ end
+
+ private
+
+ def get_tasks(after)
+ client.get('maniphest.search',
+ params: {
+ after: after,
+ attachments: { projects: 1, subscribers: 1, columns: 1 }
+ })
+ end
+
+ attr_reader :client
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/pagination.rb b/lib/gitlab/phabricator_import/conduit/pagination.rb
new file mode 100644
index 00000000000..5f54cccdbc8
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/pagination.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Pagination
+ def initialize(cursor_json)
+ @cursor_json = cursor_json
+ end
+
+ def has_next_page?
+ next_page.present?
+ end
+
+ def next_page
+ cursor_json["after"]
+ end
+
+ private
+
+ attr_reader :cursor_json
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/response.rb b/lib/gitlab/phabricator_import/conduit/response.rb
new file mode 100644
index 00000000000..6053ecfbd5e
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/response.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class Response
+ def self.parse!(http_response)
+ unless http_response.success?
+ raise Gitlab::PhabricatorImport::Conduit::ResponseError,
+ "Phabricator responded with #{http_response.status}"
+ end
+
+ response = new(JSON.parse(http_response.body))
+
+ unless response.success?
+ raise ResponseError,
+ "Phabricator Error: #{response.error_code}: #{response.error_info}"
+ end
+
+ response
+ rescue JSON::JSONError => e
+ raise ResponseError.new(e)
+ end
+
+ def initialize(json)
+ @json = json
+ end
+
+ def success?
+ error_code.nil?
+ end
+
+ def error_code
+ json['error_code']
+ end
+
+ def error_info
+ json['error_info']
+ end
+
+ def data
+ json_result&.fetch('data')
+ end
+
+ def pagination
+ return unless cursor_info = json_result&.fetch('cursor')
+
+ @pagination ||= Pagination.new(cursor_info)
+ end
+
+ private
+
+ attr_reader :json
+
+ def json_result
+ json['result']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/tasks_response.rb b/lib/gitlab/phabricator_import/conduit/tasks_response.rb
new file mode 100644
index 00000000000..cbcf7259fb2
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/tasks_response.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class TasksResponse
+ def initialize(conduit_response)
+ @conduit_response = conduit_response
+ end
+
+ delegate :pagination, to: :conduit_response
+
+ def tasks
+ @tasks ||= conduit_response.data.map do |task_json|
+ Gitlab::PhabricatorImport::Representation::Task.new(task_json)
+ end
+ end
+
+ private
+
+ attr_reader :conduit_response
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/import_tasks_worker.rb b/lib/gitlab/phabricator_import/import_tasks_worker.rb
new file mode 100644
index 00000000000..c36954a8d41
--- /dev/null
+++ b/lib/gitlab/phabricator_import/import_tasks_worker.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ class ImportTasksWorker < BaseWorker
+ def importer_class
+ Gitlab::PhabricatorImport::Issues::Importer
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/importer.rb b/lib/gitlab/phabricator_import/importer.rb
new file mode 100644
index 00000000000..c1797f4027e
--- /dev/null
+++ b/lib/gitlab/phabricator_import/importer.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ class Importer
+ def self.async?
+ true
+ end
+
+ def self.imports_repository?
+ # This does not really import a repository, but we want to skip all
+ # repository related tasks in the `Projects::ImportService`
+ true
+ end
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ Gitlab::Import::SetAsyncJid.set_jid(project)
+ schedule_first_tasks_page
+
+ true
+ rescue => e
+ fail_import(e.message)
+
+ false
+ end
+
+ private
+
+ attr_reader :project
+
+ def schedule_first_tasks_page
+ ImportTasksWorker.schedule(project.id)
+ end
+
+ def fail_import(message)
+ project.import_state.mark_as_failed(message)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/issues/importer.rb b/lib/gitlab/phabricator_import/issues/importer.rb
new file mode 100644
index 00000000000..a58438452ff
--- /dev/null
+++ b/lib/gitlab/phabricator_import/issues/importer.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Issues
+ class Importer
+ def initialize(project, after = nil)
+ @project, @after = project, after
+ end
+
+ def execute
+ schedule_next_batch
+
+ tasks_response.tasks.each do |task|
+ TaskImporter.new(project, task).execute
+ end
+ end
+
+ private
+
+ attr_reader :project, :after
+
+ def schedule_next_batch
+ return unless tasks_response.pagination.has_next_page?
+
+ Gitlab::PhabricatorImport::ImportTasksWorker
+ .schedule(project.id, tasks_response.pagination.next_page)
+ end
+
+ def tasks_response
+ @tasks_response ||= client.tasks(after: after)
+ end
+
+ def client
+ @client ||=
+ Gitlab::PhabricatorImport::Conduit::Maniphest
+ .new(phabricator_url: project.import_data.data['phabricator_url'],
+ api_token: project.import_data.credentials[:api_token])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/issues/task_importer.rb b/lib/gitlab/phabricator_import/issues/task_importer.rb
new file mode 100644
index 00000000000..40d4392cbc1
--- /dev/null
+++ b/lib/gitlab/phabricator_import/issues/task_importer.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Issues
+ class TaskImporter
+ def initialize(project, task)
+ @project, @task = project, task
+ end
+
+ def execute
+ # TODO: get the user from the project namespace from the username loaded by Phab-id
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/60565
+ issue.author = User.ghost
+
+ # TODO: Reformat the description with attachments, escaping accidental
+ # links and add attachments
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/60603
+ issue.assign_attributes(task.issue_attributes)
+
+ save!
+
+ issue
+ end
+
+ private
+
+ attr_reader :project, :task
+
+ def save!
+ # Just avoiding an extra redis call, we've already updated the expiry
+ # when reading the id from the map
+ was_persisted = issue.persisted?
+
+ issue.save! if issue.changed?
+
+ object_map.set_gitlab_model(issue, task.phabricator_id) unless was_persisted
+ end
+
+ def issue
+ @issue ||= find_issue_by_phabricator_id(task.phabricator_id) ||
+ project.issues.new
+ end
+
+ def find_issue_by_phabricator_id(phabricator_id)
+ object_map.get_gitlab_model(phabricator_id)
+ end
+
+ def object_map
+ Gitlab::PhabricatorImport::Cache::Map.new(project)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/project_creator.rb b/lib/gitlab/phabricator_import/project_creator.rb
new file mode 100644
index 00000000000..b37a5b44980
--- /dev/null
+++ b/lib/gitlab/phabricator_import/project_creator.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ class ProjectCreator
+ def initialize(current_user, params)
+ @current_user = current_user
+ @params = params.dup
+ end
+
+ def execute
+ return unless import_url.present? && api_token.present?
+
+ project = Projects::CreateService.new(current_user, create_params).execute
+ return project unless project.persisted?
+
+ project.project_feature.update!(project_feature_attributes)
+
+ project
+ end
+
+ private
+
+ attr_reader :current_user, :params
+
+ def create_params
+ {
+ name: project_name,
+ path: project_path,
+ namespace_id: namespace_id,
+ import_type: 'phabricator',
+ import_url: Project::UNKNOWN_IMPORT_URL,
+ import_data: import_data
+ }
+ end
+
+ def project_name
+ params[:name]
+ end
+
+ def project_path
+ params[:path]
+ end
+
+ def namespace_id
+ params[:namespace_id] || current_user.namespace_id
+ end
+
+ def import_url
+ params[:phabricator_server_url]
+ end
+
+ def api_token
+ params[:api_token]
+ end
+
+ def project_feature_attributes
+ @project_features_attributes ||= begin
+ # everything disabled except for issues
+ ProjectFeature::FEATURES.map do |feature|
+ [ProjectFeature.access_level_attribute(feature), ProjectFeature::DISABLED]
+ end.to_h.merge(ProjectFeature.access_level_attribute(:issues) => ProjectFeature::ENABLED)
+ end
+ end
+
+ def import_data
+ {
+ data: {
+ phabricator_url: import_url
+ },
+ credentials: {
+ api_token: params.fetch(:api_token)
+ }
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/representation/task.rb b/lib/gitlab/phabricator_import/representation/task.rb
new file mode 100644
index 00000000000..6aedc71b626
--- /dev/null
+++ b/lib/gitlab/phabricator_import/representation/task.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Representation
+ class Task
+ def initialize(json)
+ @json = json
+ end
+
+ def phabricator_id
+ json['phid']
+ end
+
+ def issue_attributes
+ @issue_attributes ||= {
+ title: issue_title,
+ description: issue_description,
+ state: issue_state,
+ created_at: issue_created_at,
+ closed_at: issue_closed_at
+ }
+ end
+
+ private
+
+ attr_reader :json
+
+ def issue_title
+ # The 255 limit is the validation we impose on the Issue title in
+ # Issuable
+ @issue_title ||= json['fields']['name'].truncate(255)
+ end
+
+ def issue_description
+ json['fields']['description']['raw']
+ end
+
+ def issue_state
+ issue_closed_at.present? ? :closed : :opened
+ end
+
+ def issue_created_at
+ return unless json['fields']['dateCreated']
+
+ @issue_created_at ||= cast_datetime(json['fields']['dateCreated'])
+ end
+
+ def issue_closed_at
+ return unless json['fields']['dateClosed']
+
+ @issue_closed_at ||= cast_datetime(json['fields']['dateClosed'])
+ end
+
+ def cast_datetime(value)
+ Time.at(value.to_i)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/worker_state.rb b/lib/gitlab/phabricator_import/worker_state.rb
new file mode 100644
index 00000000000..38829e34509
--- /dev/null
+++ b/lib/gitlab/phabricator_import/worker_state.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ class WorkerState
+ def initialize(project_id)
+ @project_id = project_id
+ end
+
+ def add_job
+ redis.with do |r|
+ r.pipelined do |pipe|
+ pipe.incr(all_jobs_key)
+ pipe.expire(all_jobs_key, timeout)
+ end
+ end
+ end
+
+ def remove_job
+ redis.with do |r|
+ r.decr(all_jobs_key)
+ end
+ end
+
+ def running_count
+ redis.with { |r| r.get(all_jobs_key) }.to_i
+ end
+
+ private
+
+ attr_reader :project_id
+
+ def redis
+ Gitlab::Redis::SharedState
+ end
+
+ def all_jobs_key
+ @all_jobs_key ||= "phabricator-import/jobs/project-#{project_id}/job-count"
+ end
+
+ def timeout
+ # Make sure we get rid of all the information after a job is marked
+ # as failed/succeeded
+ StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 78337518988..0f3b97e2317 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -138,6 +138,12 @@ module Gitlab
project
end
+ def filter_milestones_by_project(milestones)
+ return Milestone.none unless Ability.allowed?(@current_user, :read_milestone, @project)
+
+ milestones.where(project_id: project.id) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
def repository_project_ref
@repository_project_ref ||= repository_ref || project.default_branch
end
diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
index 1cd158db2b0..e1579cfddc0 100644
--- a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
@@ -102,7 +102,7 @@ module Gitlab
@updates[:milestone_id] = nil
end
- desc _('Copy labels and milestone from other issue or merge request')
+ desc _('Copy labels and milestone from other issue or merge request in this project')
explanation do |source_issuable|
_("Copy labels and milestone from %{source_issuable_reference}.") % { source_issuable_reference: source_issuable.to_reference }
end
diff --git a/lib/gitlab/rack_timeout_observer.rb b/lib/gitlab/rack_timeout_observer.rb
new file mode 100644
index 00000000000..80d3f7dea60
--- /dev/null
+++ b/lib/gitlab/rack_timeout_observer.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class RackTimeoutObserver
+ def initialize
+ @counter = Gitlab::Metrics.counter(:rack_state_total, 'Number of requests in a given rack state')
+ end
+
+ # returns the Proc to be used as the observer callback block
+ def callback
+ method(:log_timeout_exception)
+ end
+
+ private
+
+ def log_timeout_exception(env)
+ info = env[::Rack::Timeout::ENV_INFO_KEY]
+ return unless info
+
+ @counter.increment(labels(info, env))
+ end
+
+ def labels(info, env)
+ params = controller_params(env) || grape_params(env) || {}
+
+ {
+ controller: params['controller'],
+ action: params['action'],
+ route: params['route'],
+ state: info.state
+ }
+ end
+
+ def controller_params(env)
+ env['action_dispatch.request.parameters']
+ end
+
+ def grape_params(env)
+ endpoint = env[Grape::Env::API_ENDPOINT]
+ route = endpoint&.route&.pattern&.origin
+ return unless route
+
+ { 'route' => route }
+ end
+ end
+end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 4a097a00101..3daa03d01d6 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -85,6 +85,10 @@ module Gitlab
UsersFinder.new(current_user, search: query).execute
end
+ def display_options(_scope)
+ {}
+ end
+
private
def projects
@@ -103,9 +107,11 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def milestones
- milestones = Milestone.where(project_id: project_ids_relation)
- milestones = milestones.search(query)
- milestones.reorder('milestones.updated_at DESC')
+ milestones = Milestone.search(query)
+
+ milestones = filter_milestones_by_project(milestones)
+
+ milestones.reorder('updated_at DESC')
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -123,6 +129,26 @@ module Gitlab
'projects'
end
+ # Filter milestones by authorized projects.
+ # For performance reasons project_id is being plucked
+ # to be used on a smaller query.
+ #
+ # rubocop: disable CodeReuse/ActiveRecord
+ def filter_milestones_by_project(milestones)
+ project_ids =
+ milestones.where(project_id: project_ids_relation)
+ .select(:project_id).distinct
+ .pluck(:project_id)
+
+ return Milestone.none if project_ids.nil?
+
+ authorized_project_ids_relation =
+ Project.where(id: project_ids).ids_with_milestone_available_for(current_user)
+
+ milestones.where(project_id: authorized_project_ids_relation)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
# rubocop: disable CodeReuse/ActiveRecord
def project_ids_relation
limit_projects.select(:id).reorder(nil)
diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb
index 2b7e12639be..0d3e78c0a66 100644
--- a/lib/gitlab/setup_helper.rb
+++ b/lib/gitlab/setup_helper.rb
@@ -44,6 +44,13 @@ module Gitlab
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
config[:bin_dir] = Gitlab.config.gitaly.client_path
+ if Rails.env.test?
+ # Compared to production, tests run in constrained environments. This
+ # number is meant to grow with the number of concurrent rails requests /
+ # sidekiq jobs, and concurrency will be low anyway in test.
+ config[:git] = { catfile_cache_size: 5 }
+ end
+
TomlRB.dump(config)
end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 641ba70ef83..9a8df719827 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -8,38 +8,68 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
class << self
- def validate!(url, ports: [], schemes: [], allow_localhost: false, allow_local_network: true, ascii_only: false, enforce_user: false, enforce_sanitization: false)
- return true if url.nil?
+ # Validates the given url according to the constraints specified by arguments.
+ #
+ # ports - Raises error if the given URL port does is not between given ports.
+ # allow_localhost - Raises error if URL resolves to a localhost IP address and argument is true.
+ # allow_local_network - Raises error if URL resolves to a link-local address and argument is true.
+ # ascii_only - Raises error if URL has unicode characters and argument is true.
+ # enforce_user - Raises error if URL user doesn't start with alphanumeric characters and argument is true.
+ # enforce_sanitization - Raises error if URL includes any HTML/CSS/JS tags and argument is true.
+ #
+ # Returns an array with [<uri>, <original-hostname>].
+ # rubocop:disable Metrics/CyclomaticComplexity
+ # rubocop:disable Metrics/ParameterLists
+ def validate!(
+ url,
+ ports: [],
+ schemes: [],
+ allow_localhost: false,
+ allow_local_network: true,
+ ascii_only: false,
+ enforce_user: false,
+ enforce_sanitization: false,
+ dns_rebind_protection: true)
+ # rubocop:enable Metrics/CyclomaticComplexity
+ # rubocop:enable Metrics/ParameterLists
+
+ return [nil, nil] if url.nil?
# Param url can be a string, URI or Addressable::URI
uri = parse_url(url)
validate_html_tags!(uri) if enforce_sanitization
- # Allow imports from the GitLab instance itself but only from the configured ports
- return true if internal?(uri)
-
+ hostname = uri.hostname
port = get_port(uri)
- validate_scheme!(uri.scheme, schemes)
- validate_port!(port, ports) if ports.any?
- validate_user!(uri.user) if enforce_user
- validate_hostname!(uri.hostname)
- validate_unicode_restriction!(uri) if ascii_only
+
+ unless internal?(uri)
+ validate_scheme!(uri.scheme, schemes)
+ validate_port!(port, ports) if ports.any?
+ validate_user!(uri.user) if enforce_user
+ validate_hostname!(hostname)
+ validate_unicode_restriction!(uri) if ascii_only
+ end
begin
- addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM).map do |addr|
+ addrs_info = Addrinfo.getaddrinfo(hostname, port, nil, :STREAM).map do |addr|
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
end
rescue SocketError
- return true
+ return [uri, nil]
end
+ protected_uri_with_hostname = enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection)
+
+ # Allow url from the GitLab instance itself but only for the configured hostname and ports
+ return protected_uri_with_hostname if internal?(uri)
+
validate_localhost!(addrs_info) unless allow_localhost
validate_loopback!(addrs_info) unless allow_localhost
validate_local_network!(addrs_info) unless allow_local_network
validate_link_local!(addrs_info) unless allow_local_network
- true
+ protected_uri_with_hostname
end
def blocked_url?(*args)
@@ -52,6 +82,25 @@ module Gitlab
private
+ # Returns the given URI with IP address as hostname and the original hostname respectively
+ # in an Array.
+ #
+ # It checks whether the resolved IP address matches with the hostname. If not, it changes
+ # the hostname to the resolved IP address.
+ #
+ # The original hostname is used to validate the SSL, given in that scenario
+ # we'll be making the request to the IP address, instead of using the hostname.
+ def enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection)
+ address = addrs_info.first
+ ip_address = address&.ip_address
+
+ return [uri, nil] unless dns_rebind_protection && ip_address && ip_address != hostname
+
+ uri = uri.dup
+ uri.hostname = ip_address
+ [uri, hostname]
+ end
+
def get_port(uri)
uri.port || uri.default_port
end
diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb
index 880712de5fe..215454fe63c 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -47,6 +47,10 @@ module Gitlab
@credentials ||= { user: @url.user.presence, password: @url.password.presence }
end
+ def user
+ credentials[:user]
+ end
+
def full_url
@full_url ||= generate_full_url.to_s
end
diff --git a/lib/quality/test_level.rb b/lib/quality/test_level.rb
new file mode 100644
index 00000000000..24d8eac200c
--- /dev/null
+++ b/lib/quality/test_level.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Quality
+ class TestLevel
+ UnknownTestLevelError = Class.new(StandardError)
+
+ TEST_LEVEL_FOLDERS = {
+ unit: %w[
+ bin
+ config
+ db
+ dependencies
+ factories
+ finders
+ frontend
+ graphql
+ helpers
+ initializers
+ javascripts
+ lib
+ migrations
+ models
+ policies
+ presenters
+ rack_servers
+ routing
+ rubocop
+ serializers
+ services
+ sidekiq
+ tasks
+ uploaders
+ validators
+ views
+ workers
+ elastic_integration
+ ],
+ integration: %w[
+ controllers
+ mailers
+ requests
+ ],
+ system: ['features']
+ }.freeze
+
+ attr_reader :prefix
+
+ def initialize(prefix = nil)
+ @prefix = prefix
+ @patterns = {}
+ @regexps = {}
+ end
+
+ def pattern(level)
+ @patterns[level] ||= "#{prefix}spec/{#{TEST_LEVEL_FOLDERS.fetch(level).join(',')}}{,/**/}*_spec.rb".freeze
+ end
+
+ def regexp(level)
+ @regexps[level] ||= Regexp.new("#{prefix}spec/(#{TEST_LEVEL_FOLDERS.fetch(level).join('|')})").freeze
+ end
+
+ def level_for(file_path)
+ case file_path
+ when regexp(:unit)
+ :unit
+ when regexp(:integration)
+ :integration
+ when regexp(:system)
+ :system
+ else
+ raise UnknownTestLevelError, "Test level for #{file_path} couldn't be set. Please rename the file properly or change the test level detection regexes in #{__FILE__}."
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake
index 2235a6ba194..91a52144dd2 100644
--- a/lib/tasks/gettext.rake
+++ b/lib/tasks/gettext.rake
@@ -19,6 +19,7 @@ namespace :gettext do
Rake::Task['gettext:po_to_json'].invoke
end
+ desc 'Regenerate gitlab.pot file'
task :regenerate do
pot_file = 'locale/gitlab.pot'
# Remove all translated files, this speeds up finding
diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake
index 7a42e4e92a0..a07ae3a418a 100644
--- a/lib/tasks/gitlab/assets.rake
+++ b/lib/tasks/gitlab/assets.rake
@@ -10,9 +10,15 @@ namespace :gitlab do
rake:assets:precompile
webpack:compile
gitlab:assets:fix_urls
+ gitlab:assets:compile_vrt
].each(&Gitlab::TaskHelpers.method(:invoke_and_time_task))
end
+ desc 'GitLab | Assets | Compile visual review toolbar'
+ task :compile_vrt do
+ system 'yarn', 'webpack-vrt'
+ end
+
desc 'GitLab | Assets | Clean up old compiled frontend assets'
task clean: ['rake:assets:clean']
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index ee3ef9dad6e..487808a7baa 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -51,9 +51,6 @@ namespace :gitlab do
end
end
- # (Re)create hooks
- Rake::Task['gitlab:shell:create_hooks'].invoke
-
Gitlab::Shell.ensure_secret_token!
end
@@ -78,15 +75,6 @@ namespace :gitlab do
end
end
end
-
- desc 'Create or repair repository hooks symlink'
- task create_hooks: :gitlab_environment do
- warn_user_is_not_gitlab
-
- puts 'Creating/Repairing hooks symlinks for all repositories'
- system(*%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args)
- puts 'done'.color(:green)
- end
end
def setup
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
index 2eddcb3c777..c881ad4cf12 100644
--- a/lib/tasks/spec.rake
+++ b/lib/tasks/spec.rake
@@ -1,7 +1,32 @@
+# frozen_string_literal: true
+
+return if Rails.env.production?
+
Rake::Task["spec"].clear if Rake::Task.task_defined?('spec')
namespace :spec do
- desc 'GitLab | Rspec | Run request specs'
+ desc 'GitLab | RSpec | Run unit tests'
+ RSpec::Core::RakeTask.new(:unit, :rspec_opts) do |t, args|
+ require_dependency 'quality/test_level'
+ t.pattern = Quality::TestLevel.new.pattern(:unit)
+ t.rspec_opts = args[:rspec_opts]
+ end
+
+ desc 'GitLab | RSpec | Run integration tests'
+ RSpec::Core::RakeTask.new(:integration, :rspec_opts) do |t, args|
+ require_dependency 'quality/test_level'
+ t.pattern = Quality::TestLevel.new.pattern(:integration)
+ t.rspec_opts = args[:rspec_opts]
+ end
+
+ desc 'GitLab | RSpec | Run system tests'
+ RSpec::Core::RakeTask.new(:system, :rspec_opts) do |t, args|
+ require_dependency 'quality/test_level'
+ t.pattern = Quality::TestLevel.new.pattern(:system)
+ t.rspec_opts = args[:rspec_opts]
+ end
+
+ desc '[Deprecated] Use the "bin/rspec --tag api" instead'
task :api do
cmds = [
%w(rake gitlab:setup),
@@ -10,7 +35,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GitLab | Rspec | Run feature specs'
+ desc '[Deprecated] Use the "spec:system" task instead'
task :feature do
cmds = [
%w(rake gitlab:setup),
@@ -19,7 +44,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GitLab | Rspec | Run model specs'
+ desc '[Deprecated] Use "bin/rspec spec/models" instead'
task :models do
cmds = [
%w(rake gitlab:setup),
@@ -28,7 +53,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GitLab | Rspec | Run service specs'
+ desc '[Deprecated] Use "bin/rspec spec/services" instead'
task :services do
cmds = [
%w(rake gitlab:setup),
@@ -37,7 +62,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GitLab | Rspec | Run lib specs'
+ desc '[Deprecated] Use "bin/rspec spec/lib" instead'
task :lib do
cmds = [
%w(rake gitlab:setup),
@@ -45,15 +70,6 @@ namespace :spec do
]
run_commands(cmds)
end
-
- desc 'GitLab | Rspec | Run other specs'
- task :other do
- cmds = [
- %w(rake gitlab:setup),
- %w(rspec spec --tag ~@api --tag ~@feature --tag ~@models --tag ~@lib --tag ~@services)
- ]
- run_commands(cmds)
- end
end
desc "GitLab | Run specs"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6209530deab..20c8a834d04 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19,9 +19,6 @@ msgstr ""
msgid " Please sign in."
msgstr ""
-msgid " Status"
-msgstr ""
-
msgid " Try to %{action} this file again."
msgstr ""
@@ -110,7 +107,7 @@ msgstr ""
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
-msgid "%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS"
+msgid "%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS"
msgstr ""
msgid "%{count} more"
@@ -193,6 +190,18 @@ msgstr ""
msgid "%{service_title} settings saved, but not activated."
msgstr ""
+msgid "%{size} GiB"
+msgstr ""
+
+msgid "%{size} KiB"
+msgstr ""
+
+msgid "%{size} MiB"
+msgstr ""
+
+msgid "%{size} bytes"
+msgstr ""
+
msgid "%{spammable_titlecase} was submitted to Akismet successfully."
msgstr ""
@@ -331,6 +340,9 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
+msgid "2FADevice|Registered On"
+msgstr ""
+
msgid "3 days"
msgstr ""
@@ -373,6 +385,9 @@ msgstr ""
msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
msgstr ""
+msgid "<no name set>"
+msgstr ""
+
msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes"
msgstr ""
@@ -412,7 +427,7 @@ msgstr ""
msgid "A fork is a copy of a project.<br />Forking a repository allows you to make changes without affecting the original project."
msgstr ""
-msgid "A member of GitLab's abuse team will review your report as soon as possible."
+msgid "A member of the abuse team will review your report as soon as possible."
msgstr ""
msgid "A new branch will be created in your fork and a new merge request will be started."
@@ -442,6 +457,9 @@ msgstr ""
msgid "API Help"
msgstr ""
+msgid "API Token"
+msgstr ""
+
msgid "About GitLab"
msgstr ""
@@ -487,6 +505,9 @@ msgstr ""
msgid "Account and limit"
msgstr ""
+msgid "Account: %{account}"
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -508,6 +529,9 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a GPG key"
+msgstr ""
+
msgid "Add a bullet list"
msgstr ""
@@ -532,12 +556,18 @@ msgstr ""
msgid "Add a todo"
msgstr ""
+msgid "Add an SSH key"
+msgstr ""
+
msgid "Add bold text"
msgstr ""
msgid "Add comment now"
msgstr ""
+msgid "Add email address"
+msgstr ""
+
msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
msgstr ""
@@ -739,12 +769,18 @@ msgstr ""
msgid "After a successful password update you will be redirected to login screen."
msgstr ""
+msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
+msgstr ""
+
msgid "All"
msgstr ""
msgid "All changes are committed"
msgstr ""
+msgid "All email addresses will be used to identify your commits."
+msgstr ""
+
msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
msgstr ""
@@ -850,6 +886,9 @@ msgstr ""
msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again."
msgstr ""
+msgid "An error occurred while fetching folder content."
+msgstr ""
+
msgid "An error occurred while fetching label colors."
msgstr ""
@@ -862,9 +901,6 @@ msgstr ""
msgid "An error occurred while fetching sidebar data"
msgstr ""
-msgid "An error occurred while fetching stages."
-msgstr ""
-
msgid "An error occurred while fetching the board lists. Please try again."
msgstr ""
@@ -943,6 +979,9 @@ msgstr ""
msgid "An error occurred whilst fetching the latest pipeline."
msgstr ""
+msgid "An error occurred whilst getting files for - %{branchId}"
+msgstr ""
+
msgid "An error occurred whilst loading all the files."
msgstr ""
@@ -1081,6 +1120,9 @@ msgstr ""
msgid "Are you sure you want to cancel editing this comment?"
msgstr ""
+msgid "Are you sure you want to delete this device? This action cannot be undone."
+msgstr ""
+
msgid "Are you sure you want to delete this list?"
msgstr ""
@@ -1114,6 +1156,9 @@ msgstr ""
msgid "Are you sure you want to reset the health check token?"
msgstr ""
+msgid "Are you sure you want to revoke this nickname?"
+msgstr ""
+
msgid "Are you sure you want to stop this environment?"
msgstr ""
@@ -1123,9 +1168,21 @@ msgstr ""
msgid "Are you sure?"
msgstr ""
+msgid "Are you sure? All commits that were signed with this GPG key will be unverified."
+msgstr ""
+
+msgid "Are you sure? Removing this GPG key does not affect already signed commits."
+msgstr ""
+
+msgid "Are you sure? This will invalidate your registered applications and U2F devices."
+msgstr ""
+
msgid "Artifacts"
msgstr ""
+msgid "As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser."
+msgstr ""
+
msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):"
msgstr ""
@@ -1278,6 +1335,12 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
+msgid "Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
+msgstr ""
+
+msgid "Automatic certificate management using Let's Encrypt"
+msgstr ""
+
msgid "Automatically marked as default internal user"
msgstr ""
@@ -1467,6 +1530,9 @@ msgstr ""
msgid "Branch name"
msgstr ""
+msgid "Branch not loaded - %{branchId}"
+msgstr ""
+
msgid "BranchSwitcherPlaceholder|Search branches"
msgstr ""
@@ -1614,6 +1680,9 @@ msgstr ""
msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
msgstr ""
+msgid "By default, all projects and groups will use the global notifications setting."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1698,6 +1767,9 @@ msgstr ""
msgid "Can't find variable: ZiteReader"
msgstr ""
+msgid "Can't scan the code?"
+msgstr ""
+
msgid "Cancel"
msgstr ""
@@ -1749,6 +1821,12 @@ msgstr ""
msgid "Change title"
msgstr ""
+msgid "Change your password"
+msgstr ""
+
+msgid "Change your password or recover your current one"
+msgstr ""
+
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr ""
@@ -1797,6 +1875,9 @@ msgstr ""
msgid "Checking branch availability..."
msgstr ""
+msgid "Checking username availability..."
+msgstr ""
+
msgid "Cherry-pick this commit"
msgstr ""
@@ -2052,7 +2133,7 @@ msgstr ""
msgid "ClusterIntegration|%{title} uninstalled successfully."
msgstr ""
-msgid "ClusterIntegration|%{title} upgraded successfully."
+msgid "ClusterIntegration|%{title} updated successfully."
msgstr ""
msgid "ClusterIntegration|A service token scoped to %{code}kube-system%{end_code} with %{code}cluster-admin%{end_code} privileges."
@@ -2247,21 +2328,9 @@ msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr ""
-msgid "ClusterIntegration|Install"
-msgstr ""
-
-msgid "ClusterIntegration|Installed"
-msgstr ""
-
-msgid "ClusterIntegration|Installing"
-msgstr ""
-
msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
msgstr ""
-msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
-msgstr ""
-
msgid "ClusterIntegration|Instance cluster"
msgstr ""
@@ -2295,6 +2364,9 @@ msgstr ""
msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
+msgid "ClusterIntegration|Knative domain name was updated successfully."
+msgstr ""
+
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
msgstr ""
@@ -2343,9 +2415,6 @@ msgstr ""
msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
msgstr ""
-msgid "ClusterIntegration|Manage"
-msgstr ""
-
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr ""
@@ -2412,9 +2481,6 @@ msgstr ""
msgid "ClusterIntegration|Request to begin uninstalling failed"
msgstr ""
-msgid "ClusterIntegration|Retry update"
-msgstr ""
-
msgid "ClusterIntegration|Save changes"
msgstr ""
@@ -2469,6 +2535,9 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while uninstalling %{title}"
msgstr ""
+msgid "ClusterIntegration|Something went wrong while updating Knative domain name."
+msgstr ""
+
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr ""
@@ -2490,6 +2559,9 @@ msgstr ""
msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
+msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
+msgstr ""
+
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr ""
@@ -2508,21 +2580,6 @@ msgstr ""
msgid "ClusterIntegration|Update failed. Please check the logs and try again."
msgstr ""
-msgid "ClusterIntegration|Updating"
-msgstr ""
-
-msgid "ClusterIntegration|Upgrade"
-msgstr ""
-
-msgid "ClusterIntegration|Upgrade failed"
-msgstr ""
-
-msgid "ClusterIntegration|Upgraded"
-msgstr ""
-
-msgid "ClusterIntegration|Upgrading"
-msgstr ""
-
msgid "ClusterIntegration|Validating project billing status"
msgstr ""
@@ -2544,6 +2601,9 @@ msgstr ""
msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
+msgid "ClusterIntegration|Your cluster API is unreachable. Please ensure your API URL is correct."
+msgstr ""
+
msgid "ClusterIntegration|Zone"
msgstr ""
@@ -2744,9 +2804,15 @@ msgstr ""
msgid "Confirm"
msgstr ""
+msgid "Confirmation email sent to %{email}"
+msgstr ""
+
msgid "Confirmation required"
msgstr ""
+msgid "Congratulations! You have enabled Two-factor Authentication!"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2849,6 +2915,9 @@ msgstr ""
msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
msgstr ""
+msgid "Control emails linked to your account"
+msgstr ""
+
msgid "Control the display of third party offers."
msgstr ""
@@ -2891,7 +2960,7 @@ msgstr ""
msgid "Copy labels and milestone from %{source_issuable_reference}."
msgstr ""
-msgid "Copy labels and milestone from other issue or merge request"
+msgid "Copy labels and milestone from other issue or merge request in this project"
msgstr ""
msgid "Copy link"
@@ -2960,6 +3029,9 @@ msgstr ""
msgid "Create a new branch"
msgstr ""
+msgid "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
msgid "Create a new issue"
msgstr ""
@@ -3065,6 +3137,9 @@ msgstr ""
msgid "Current Branch"
msgstr ""
+msgid "Current password"
+msgstr ""
+
msgid "CurrentUser|Profile"
msgstr ""
@@ -3448,6 +3523,9 @@ msgstr ""
msgid "Disable shared Runners"
msgstr ""
+msgid "Disable two-factor authentication"
+msgstr ""
+
msgid "Disabled"
msgstr ""
@@ -3511,6 +3589,9 @@ msgstr ""
msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled"
msgstr ""
+msgid "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
+msgstr ""
+
msgid "Don't show again"
msgstr ""
@@ -3526,6 +3607,9 @@ msgstr ""
msgid "Download asset"
msgstr ""
+msgid "Download codes"
+msgstr ""
+
msgid "Download export"
msgstr ""
@@ -3565,6 +3649,9 @@ msgstr ""
msgid "Edit Milestone"
msgstr ""
+msgid "Edit Password"
+msgstr ""
+
msgid "Edit Pipeline Schedule %{id}"
msgstr ""
@@ -3682,6 +3769,9 @@ msgstr ""
msgid "Enable Sentry for error reporting and logging."
msgstr ""
+msgid "Enable access to the Performance Bar for a given group."
+msgstr ""
+
msgid "Enable and configure InfluxDB metrics."
msgstr ""
@@ -3715,9 +3805,6 @@ msgstr ""
msgid "Enable shared Runners"
msgstr ""
-msgid "Enable the Performance Bar for a given group."
-msgstr ""
-
msgid "Enable two-factor authentication"
msgstr ""
@@ -3739,12 +3826,18 @@ msgstr ""
msgid "Ends at (UTC)"
msgstr ""
+msgid "Enforce DNS rebinding attack protection"
+msgstr ""
+
msgid "Enter at least three characters to search"
msgstr ""
msgid "Enter in your Bitbucket Server URL and personal access token below"
msgstr ""
+msgid "Enter in your Phabricator Server URL and personal access token below"
+msgstr ""
+
msgid "Enter the issue description"
msgstr ""
@@ -3757,7 +3850,7 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
-msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they will be masked by default so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
@@ -4347,6 +4440,9 @@ msgstr ""
msgid "Files"
msgstr ""
+msgid "Files breadcrumb"
+msgstr ""
+
msgid "Files, directories, and submodules in the path %{path} for commit reference %{ref}"
msgstr ""
@@ -4389,6 +4485,9 @@ msgstr ""
msgid "Fingerprint"
msgstr ""
+msgid "Fingerprint:"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -4482,6 +4581,9 @@ msgstr ""
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
+msgid "Friday"
+msgstr ""
+
msgid "From %{providerTitle}"
msgstr ""
@@ -4509,12 +4611,18 @@ msgstr ""
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr ""
+msgid "Full name"
+msgstr ""
+
msgid "GPG Key ID:"
msgstr ""
msgid "GPG Keys"
msgstr ""
+msgid "GPG keys allow you to verify signed commits."
+msgstr ""
+
msgid "GPG signature (loading...)"
msgstr ""
@@ -4557,6 +4665,9 @@ msgstr ""
msgid "Git revision"
msgstr ""
+msgid "Git shallow clone"
+msgstr ""
+
msgid "Git strategy for pipelines"
msgstr ""
@@ -4611,6 +4722,9 @@ msgstr ""
msgid "Given access %{time_ago}"
msgstr ""
+msgid "Global notification settings"
+msgstr ""
+
msgid "Go Back"
msgstr ""
@@ -4629,6 +4743,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to parent"
+msgstr ""
+
msgid "Go to project"
msgstr ""
@@ -4764,6 +4881,9 @@ msgstr ""
msgid "Groups"
msgstr ""
+msgid "Groups (%{count})"
+msgstr ""
+
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
@@ -4904,6 +5024,9 @@ msgstr ""
msgid "History"
msgstr ""
+msgid "History of authentications"
+msgstr ""
+
msgid "Hook was successfully created."
msgstr ""
@@ -4928,6 +5051,9 @@ msgstr ""
msgid "I accept the|Terms of Service and Privacy Policy"
msgstr ""
+msgid "I forgot my password"
+msgstr ""
+
msgid "I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}"
msgstr ""
@@ -5000,7 +5126,10 @@ msgstr ""
msgid "If this was a mistake you can leave the %{source_type}."
msgstr ""
-msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes."
+msgstr ""
+
+msgid "If your HTTP repository is not publicly accessible, add your credentials."
msgstr ""
msgid "ImageDiffViewer|2-up"
@@ -5081,6 +5210,12 @@ msgstr ""
msgid "Import repository"
msgstr ""
+msgid "Import tasks"
+msgstr ""
+
+msgid "Import tasks from Phabricator into issues"
+msgstr ""
+
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
@@ -5174,12 +5309,24 @@ msgstr ""
msgid "Insert suggestion"
msgstr ""
+msgid "Install"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
msgid "Install Runner on Kubernetes"
msgstr ""
+msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}."
+msgstr ""
+
+msgid "Installed"
+msgstr ""
+
+msgid "Installing"
+msgstr ""
+
msgid "Instance Statistics"
msgstr ""
@@ -5222,6 +5369,9 @@ msgstr ""
msgid "Invalid Login or password"
msgstr ""
+msgid "Invalid date"
+msgstr ""
+
msgid "Invalid feature"
msgstr ""
@@ -5231,6 +5381,9 @@ msgstr ""
msgid "Invalid file."
msgstr ""
+msgid "Invalid import params"
+msgstr ""
+
msgid "Invalid input, please avoid emojis"
msgstr ""
@@ -5249,6 +5402,9 @@ msgstr ""
msgid "Invite"
msgstr ""
+msgid "Invite \"%{trimmed}\" by email"
+msgstr ""
+
msgid "Invite group"
msgstr ""
@@ -5276,6 +5432,9 @@ msgstr ""
msgid "Issue update failed"
msgstr ""
+msgid "Issue was closed by %{name} %{reason}"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr ""
@@ -5456,6 +5615,9 @@ msgstr ""
msgid "Key (PEM)"
msgstr ""
+msgid "Key: %{key}"
+msgstr ""
+
msgid "Kubernetes"
msgstr ""
@@ -5563,6 +5725,9 @@ msgstr[1] ""
msgid "Last Pipeline"
msgstr ""
+msgid "Last accessed on"
+msgstr ""
+
msgid "Last activity"
msgstr ""
@@ -5590,6 +5755,12 @@ msgstr ""
msgid "Last updated"
msgstr ""
+msgid "Last used"
+msgstr ""
+
+msgid "Last used on:"
+msgstr ""
+
msgid "LastPushEvent|You pushed to"
msgstr ""
@@ -5620,6 +5791,9 @@ msgstr ""
msgid "Learn more about Kubernetes"
msgstr ""
+msgid "Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
msgid "Learn more about signing commits"
msgstr ""
@@ -5647,11 +5821,17 @@ msgstr ""
msgid "Let's Encrypt does not accept emails on example.com"
msgstr ""
+msgid "Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}."
+msgstr ""
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
msgstr[1] ""
+msgid "Linked emails (%{email_count})"
+msgstr ""
+
msgid "LinkedIn"
msgstr ""
@@ -5676,6 +5856,9 @@ msgstr ""
msgid "Live preview"
msgstr ""
+msgid "Loading functions timed out. Please reload the page to try again."
+msgstr ""
+
msgid "Loading the GitLab IDE..."
msgstr ""
@@ -5727,6 +5910,9 @@ msgstr ""
msgid "MRDiff|Show full file"
msgstr ""
+msgid "Make and review changes in the browser with the Web IDE"
+msgstr ""
+
msgid "Make issue confidential."
msgstr ""
@@ -5736,6 +5922,9 @@ msgstr ""
msgid "Makes this issue confidential"
msgstr ""
+msgid "Manage"
+msgstr ""
+
msgid "Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -5796,6 +5985,9 @@ msgstr ""
msgid "Mark as resolved"
msgstr ""
+msgid "Mark comment as resolved"
+msgstr ""
+
msgid "Mark this issue as a duplicate of another issue"
msgstr ""
@@ -6069,6 +6261,12 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
+msgid "Minimum length is %{minimum_password_length} characters"
+msgstr ""
+
+msgid "Minimum length is %{minimum_password_length} characters."
+msgstr ""
+
msgid "Mirror a repository"
msgstr ""
@@ -6281,6 +6479,9 @@ msgstr ""
msgid "New milestone"
msgstr ""
+msgid "New password"
+msgstr ""
+
msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr ""
@@ -6314,6 +6515,9 @@ msgstr ""
msgid "Next"
msgstr ""
+msgid "Nickname"
+msgstr ""
+
msgid "No"
msgstr ""
@@ -6371,6 +6575,9 @@ msgstr ""
msgid "No file selected"
msgstr ""
+msgid "No files"
+msgstr ""
+
msgid "No files found."
msgstr ""
@@ -6491,6 +6698,9 @@ msgstr ""
msgid "Notification setting - %{notification_title}"
msgstr ""
+msgid "Notification settings saved"
+msgstr ""
+
msgid "NotificationEvent|Close issue"
msgstr ""
@@ -6542,6 +6752,9 @@ msgstr ""
msgid "NotificationLevel|Watch"
msgstr ""
+msgid "NotificationSetting|Custom"
+msgstr ""
+
msgid "Notifications"
msgstr ""
@@ -6721,16 +6934,28 @@ msgstr ""
msgid "Password"
msgstr ""
+msgid "Password (optional)"
+msgstr ""
+
msgid "Password authentication is unavailable."
msgstr ""
+msgid "Password confirmation"
+msgstr ""
+
+msgid "Password successfully changed"
+msgstr ""
+
+msgid "Password was successfully updated. Please login with it"
+msgstr ""
+
msgid "Past due"
msgstr ""
msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
msgstr ""
-msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
+msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
msgid "Path"
@@ -6754,6 +6979,9 @@ msgstr ""
msgid "People without permission will never get a notification and won't be able to comment."
msgstr ""
+msgid "People without permission will never get a notification."
+msgstr ""
+
msgid "Perform advanced options such as changing path, transferring, or removing the group."
msgstr ""
@@ -6775,12 +7003,27 @@ msgstr ""
msgid "Personal project creation is not allowed. Please contact your administrator with questions"
msgstr ""
+msgid "Phabricator Server Import"
+msgstr ""
+
+msgid "Phabricator Server URL"
+msgstr ""
+
+msgid "Phabricator Tasks"
+msgstr ""
+
msgid "Pick a name"
msgstr ""
+msgid "Pin code"
+msgstr ""
+
msgid "Pipeline"
msgstr ""
+msgid "Pipeline ID (IID)"
+msgstr ""
+
msgid "Pipeline Schedule"
msgstr ""
@@ -7036,6 +7279,12 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please provide a valid email address."
+msgstr ""
+
+msgid "Please retype the email address."
+msgstr ""
+
msgid "Please select a file"
msgstr ""
@@ -7054,7 +7303,7 @@ msgstr ""
msgid "Please try again"
msgstr ""
-msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
+msgid "Please use this form to report to the admin users who create spam issues, comments or behave inappropriately."
msgstr ""
msgid "Please wait a moment, this page will automatically refresh when ready."
@@ -7069,21 +7318,51 @@ msgstr ""
msgid "Preferences saved."
msgstr ""
+msgid "Preferences|Behavior"
+msgstr ""
+
+msgid "Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout."
+msgstr ""
+
+msgid "Preferences|Choose what content you want to see on a project’s overview page."
+msgstr ""
+
+msgid "Preferences|Customize the appearance of the application header and navigation sidebar."
+msgstr ""
+
+msgid "Preferences|Default dashboard"
+msgstr ""
+
msgid "Preferences|Display time in 24-hour format"
msgstr ""
msgid "Preferences|For example: 30 mins ago."
msgstr ""
+msgid "Preferences|Layout width"
+msgstr ""
+
msgid "Preferences|Navigation theme"
msgstr ""
+msgid "Preferences|Project overview content"
+msgstr ""
+
+msgid "Preferences|Syntax highlighting theme"
+msgstr ""
+
msgid "Preferences|These settings will update how dates and times are displayed for you."
msgstr ""
msgid "Preferences|This feature is experimental and translations are not complete yet"
msgstr ""
+msgid "Preferences|This setting allows you to customize the appearance of the syntax."
+msgstr ""
+
+msgid "Preferences|This setting allows you to customize the behavior of the system layout and default views."
+msgstr ""
+
msgid "Preferences|Time display"
msgstr ""
@@ -7144,12 +7423,18 @@ msgstr ""
msgid "Private projects can be created in your personal namespace with:"
msgstr ""
+msgid "Proceed"
+msgstr ""
+
msgid "Profile"
msgstr ""
msgid "Profile Settings"
msgstr ""
+msgid "ProfileSession|on"
+msgstr ""
+
msgid "Profiles| You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account. Once you confirm %{deleteAccount}, it cannot be undone or recovered."
msgstr ""
@@ -7204,6 +7489,9 @@ msgstr ""
msgid "Profiles|Click on icon to activate signin with one of the following services"
msgstr ""
+msgid "Profiles|Commit email"
+msgstr ""
+
msgid "Profiles|Connect"
msgstr ""
@@ -7216,6 +7504,9 @@ msgstr ""
msgid "Profiles|Current status"
msgstr ""
+msgid "Profiles|Default notification email"
+msgstr ""
+
msgid "Profiles|Delete Account"
msgstr ""
@@ -7264,6 +7555,9 @@ msgstr ""
msgid "Profiles|Invalid username"
msgstr ""
+msgid "Profiles|Key"
+msgstr ""
+
msgid "Profiles|Learn more"
msgstr ""
@@ -7279,6 +7573,9 @@ msgstr ""
msgid "Profiles|No file chosen"
msgstr ""
+msgid "Profiles|Notification email"
+msgstr ""
+
msgid "Profiles|Organization"
msgstr ""
@@ -7288,6 +7585,9 @@ msgstr ""
msgid "Profiles|Position and size your new avatar"
msgstr ""
+msgid "Profiles|Primary email"
+msgstr ""
+
msgid "Profiles|Private contributions"
msgstr ""
@@ -7297,6 +7597,9 @@ msgstr ""
msgid "Profiles|Public Avatar"
msgstr ""
+msgid "Profiles|Public email"
+msgstr ""
+
msgid "Profiles|Remove avatar"
msgstr ""
@@ -7339,7 +7642,7 @@ msgstr ""
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr ""
-msgid "Profiles|Typically starts with \"ssh-rsa …\""
+msgid "Profiles|Typically starts with \"ssh-ed25519 …\" or \"ssh-rsa …\""
msgstr ""
msgid "Profiles|Update profile settings"
@@ -7696,6 +7999,9 @@ msgstr ""
msgid "Projects"
msgstr ""
+msgid "Projects (%{count})"
+msgstr ""
+
msgid "Projects Successfully Retrieved"
msgstr ""
@@ -7924,6 +8230,9 @@ msgstr ""
msgid "Real-time features"
msgstr ""
+msgid "Receive notifications about your own activity"
+msgstr ""
+
msgid "Recent Project Activity"
msgstr ""
@@ -7933,6 +8242,9 @@ msgstr ""
msgid "Recent searches"
msgstr ""
+msgid "Recovery Codes"
+msgstr ""
+
msgid "Reference:"
msgstr ""
@@ -7944,21 +8256,36 @@ msgstr[1] ""
msgid "Regenerate key"
msgstr ""
+msgid "Regenerate recovery codes"
+msgstr ""
+
msgid "Regex pattern"
msgstr ""
+msgid "Register"
+msgstr ""
+
msgid "Register / Sign In"
msgstr ""
+msgid "Register Two-Factor Authenticator"
+msgstr ""
+
msgid "Register U2F device"
msgstr ""
+msgid "Register Universal Two-Factor (U2F) Device"
+msgstr ""
+
msgid "Register and see your runners for this group."
msgstr ""
msgid "Register and see your runners for this project."
msgstr ""
+msgid "Register with two-factor app"
+msgstr ""
+
msgid "Registry"
msgstr ""
@@ -8034,12 +8361,6 @@ msgstr ""
msgid "Remove spent time"
msgstr ""
-msgid "Remove this label? Are you sure?"
-msgstr ""
-
-msgid "Remove this label? This will affect all projects within the group. Are you sure?"
-msgstr ""
-
msgid "Remove time estimate"
msgstr ""
@@ -8094,7 +8415,7 @@ msgstr ""
msgid "Reply to this email directly or %{view_it_on_gitlab}."
msgstr ""
-msgid "Report abuse to GitLab"
+msgid "Report abuse to admin"
msgstr ""
msgid "Reporting"
@@ -8175,6 +8496,9 @@ msgstr ""
msgid "Require users to prove ownership of custom domains"
msgstr ""
+msgid "Resend confirmation email"
+msgstr ""
+
msgid "Resend invite"
msgstr ""
@@ -8202,9 +8526,15 @@ msgstr ""
msgid "Resolved all discussions."
msgstr ""
+msgid "Resolved by %{name}"
+msgstr ""
+
msgid "Resolved by %{resolvedByName}"
msgstr ""
+msgid "Resolves IP addresses once and uses them to submit requests"
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -8235,6 +8565,9 @@ msgstr ""
msgid "Retry this job in order to create the necessary resources."
msgstr ""
+msgid "Retry update"
+msgstr ""
+
msgid "Retry verification"
msgstr ""
@@ -8336,6 +8669,9 @@ msgstr ""
msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
msgstr ""
+msgid "SSH Key"
+msgstr ""
+
msgid "SSH Keys"
msgstr ""
@@ -8345,6 +8681,9 @@ msgstr ""
msgid "SSH host keys"
msgstr ""
+msgid "SSH keys allow you to establish a secure connection between your computer and GitLab."
+msgstr ""
+
msgid "SSH public key"
msgstr ""
@@ -8372,6 +8711,9 @@ msgstr ""
msgid "Save comment"
msgstr ""
+msgid "Save password"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr ""
@@ -8429,6 +8771,9 @@ msgstr ""
msgid "Search for a group"
msgstr ""
+msgid "Search for a user"
+msgstr ""
+
msgid "Search for projects, issues, etc."
msgstr ""
@@ -8507,6 +8852,9 @@ msgstr ""
msgid "Select Archive Format"
msgstr ""
+msgid "Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes."
+msgstr ""
+
msgid "Select a group to invite"
msgstr ""
@@ -8555,6 +8903,9 @@ msgstr ""
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
+msgid "Send confirmation email"
+msgstr ""
+
msgid "Send email"
msgstr ""
@@ -8636,6 +8987,9 @@ msgstr ""
msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
msgstr ""
+msgid "Service"
+msgstr ""
+
msgid "Service Templates"
msgstr ""
@@ -8753,6 +9107,9 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account."
+msgstr ""
+
msgid "Show all activity"
msgstr ""
@@ -8812,6 +9169,18 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
+msgid "SignUp|Name is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
+msgstr ""
+
+msgid "Signed in"
+msgstr ""
+
+msgid "Signed in with %{authentication} authentication"
+msgstr ""
+
msgid "Signing in using %{label} has been disabled"
msgstr ""
@@ -8923,6 +9292,9 @@ msgstr ""
msgid "Something went wrong. Please try again."
msgstr ""
+msgid "Something went wrong. Try again later."
+msgstr ""
+
msgid "Sorry, no projects matched your search"
msgstr ""
@@ -8977,6 +9349,9 @@ msgstr ""
msgid "SortOptions|Least popular"
msgstr ""
+msgid "SortOptions|Manual"
+msgstr ""
+
msgid "SortOptions|Milestone due date"
msgstr ""
@@ -9208,6 +9583,9 @@ msgstr ""
msgid "Subgroups and projects"
msgstr ""
+msgid "Subkeys"
+msgstr ""
+
msgid "Submit as spam"
msgstr ""
@@ -9265,6 +9643,69 @@ msgstr ""
msgid "Suggested change"
msgstr ""
+msgid "SuggestedColors|Bright green"
+msgstr ""
+
+msgid "SuggestedColors|Dark grayish cyan"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate orange"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate pink"
+msgstr ""
+
+msgid "SuggestedColors|Dark moderate violet"
+msgstr ""
+
+msgid "SuggestedColors|Feijoa"
+msgstr ""
+
+msgid "SuggestedColors|Lime green"
+msgstr ""
+
+msgid "SuggestedColors|Moderate blue"
+msgstr ""
+
+msgid "SuggestedColors|Pure red"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Slightly desaturated green"
+msgstr ""
+
+msgid "SuggestedColors|Soft orange"
+msgstr ""
+
+msgid "SuggestedColors|Soft red"
+msgstr ""
+
+msgid "SuggestedColors|Strong pink"
+msgstr ""
+
+msgid "SuggestedColors|Strong red"
+msgstr ""
+
+msgid "SuggestedColors|Strong yellow"
+msgstr ""
+
+msgid "SuggestedColors|UA blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark desaturated blue"
+msgstr ""
+
+msgid "SuggestedColors|Very dark lime green"
+msgstr ""
+
+msgid "SuggestedColors|Very pale orange"
+msgstr ""
+
msgid "Sunday"
msgstr ""
@@ -9412,6 +9853,9 @@ msgstr ""
msgid "Team"
msgstr ""
+msgid "Team domain"
+msgstr ""
+
msgid "Template"
msgstr ""
@@ -9480,6 +9924,9 @@ msgstr ""
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr ""
+msgid "The certificate will be shown here once it has been obtained from Let's Encrypt. This process may take up to an hour to complete."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
@@ -9567,6 +10014,9 @@ msgstr ""
msgid "The name %{entryName} is already taken in this directory."
msgstr ""
+msgid "The number of changes to be fetched from GitLab when cloning a repository. This can speed up Pipelines execution. Keep empty or set to 0 to disable shallow clone by default and make GitLab CI fetch all branches and tags each time."
+msgstr ""
+
msgid "The number of times an upload record could not find its file"
msgstr ""
@@ -9687,6 +10137,18 @@ msgstr ""
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
+msgid "There are no GPG keys associated with this account."
+msgstr ""
+
+msgid "There are no GPG keys with access to your account."
+msgstr ""
+
+msgid "There are no SSH keys associated with this account."
+msgstr ""
+
+msgid "There are no SSH keys with access to your account."
+msgstr ""
+
msgid "There are no archived projects yet"
msgstr ""
@@ -9723,6 +10185,9 @@ msgstr ""
msgid "There was a problem communicating with your device."
msgstr ""
+msgid "There was a problem sending the confirmation email"
+msgstr ""
+
msgid "There was an error %{message} todo."
msgstr ""
@@ -9765,6 +10230,9 @@ msgstr ""
msgid "Third party offers"
msgstr ""
+msgid "This %{issuableDisplayName} is locked. Only project members can comment."
+msgstr ""
+
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
@@ -9789,6 +10257,9 @@ msgstr ""
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr ""
+msgid "This certificate is automatically managed by Let's Encrypt"
+msgstr ""
+
msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
msgstr ""
@@ -9843,13 +10314,19 @@ msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
msgstr ""
+msgid "This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize."
+msgstr ""
+
+msgid "This is a security log of important events involving your account."
+msgstr ""
+
msgid "This is the author's first Merge Request to this project."
msgstr ""
-msgid "This issue is confidential"
+msgid "This is your current session"
msgstr ""
-msgid "This issue is confidential and locked."
+msgid "This issue is confidential"
msgstr ""
msgid "This issue is locked."
@@ -9984,6 +10461,12 @@ msgstr ""
msgid "This will remove the fork relationship to source project"
msgstr ""
+msgid "Thursday"
+msgstr ""
+
+msgid "Time based: Yes"
+msgstr ""
+
msgid "Time before an issue gets scheduled"
msgstr ""
@@ -10168,6 +10651,9 @@ msgstr ""
msgid "Title"
msgstr ""
+msgid "Title:"
+msgstr ""
+
msgid "Titles and Filenames"
msgstr ""
@@ -10183,6 +10669,9 @@ msgstr ""
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr ""
+msgid "To add the entry manually, provide the following details to the application on your phone."
+msgstr ""
+
msgid "To define internal users, first enable new users set to external"
msgstr ""
@@ -10225,6 +10714,9 @@ msgstr ""
msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
msgstr ""
+msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there."
+msgstr ""
+
msgid "To start serving your jobs you can add Runners to your group"
msgstr ""
@@ -10387,9 +10879,21 @@ msgstr ""
msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
msgstr ""
+msgid "Tuesday"
+msgstr ""
+
msgid "Twitter"
msgstr ""
+msgid "Two-Factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication"
+msgstr ""
+
+msgid "Two-factor Authentication Recovery codes"
+msgstr ""
+
msgid "Two-factor Authentication has been disabled for this user"
msgstr ""
@@ -10399,6 +10903,9 @@ msgstr ""
msgid "Type"
msgstr ""
+msgid "U2F Devices (%{length})"
+msgstr ""
+
msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr ""
@@ -10537,6 +11044,9 @@ msgstr ""
msgid "UpdateProject|Project could not be updated!"
msgstr ""
+msgid "Updated"
+msgstr ""
+
msgid "Updating"
msgstr ""
@@ -10579,6 +11089,12 @@ msgstr ""
msgid "Use <code>%{native_redirect_uri}</code> for local tests"
msgstr ""
+msgid "Use a hardware device to add the second factor of authentication."
+msgstr ""
+
+msgid "Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
+msgstr ""
+
msgid "Use group milestones to manage issues from multiple projects in the same milestone."
msgstr ""
@@ -10717,6 +11233,15 @@ msgstr ""
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
msgstr ""
+msgid "Username (optional)"
+msgstr ""
+
+msgid "Username is already taken."
+msgstr ""
+
+msgid "Username is available."
+msgstr ""
+
msgid "Users"
msgstr ""
@@ -10726,6 +11251,21 @@ msgstr ""
msgid "Users were successfully added."
msgstr ""
+msgid "UsersSelect|%{name} + %{length} more"
+msgstr ""
+
+msgid "UsersSelect|Any User"
+msgstr ""
+
+msgid "UsersSelect|Assignee"
+msgstr ""
+
+msgid "UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}"
+msgstr ""
+
+msgid "UsersSelect|Unassigned"
+msgstr ""
+
msgid "Using required encryption strategy when encrypted field is missing!"
msgstr ""
@@ -10864,6 +11404,9 @@ msgstr ""
msgid "We heard back from your U2F device. You have been authenticated."
msgstr ""
+msgid "We sent you an email with reset password instructions"
+msgstr ""
+
msgid "We want to be sure it is you, please confirm you are not a robot."
msgstr ""
@@ -10879,6 +11422,9 @@ msgstr ""
msgid "Webhooks Help"
msgstr ""
+msgid "Wednesday"
+msgstr ""
+
msgid "Welcome to your Issue Board!"
msgstr ""
@@ -11100,7 +11646,7 @@ msgstr ""
msgid "You are attempting to update a file that has changed since you started editing it."
msgstr ""
-msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr ""
msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
@@ -11112,6 +11658,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are not allowed to unlink your primary login account"
+msgstr ""
+
msgid "You are now impersonating %{username}"
msgstr ""
@@ -11154,6 +11703,12 @@ msgstr ""
msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
msgstr ""
+msgid "You can invite a new member to <strong>%{project_name}</strong>."
+msgstr ""
+
+msgid "You can invite another group to <strong>%{project_name}</strong>."
+msgstr ""
+
msgid "You can move around the graph by using the arrow keys."
msgstr ""
@@ -11178,9 +11733,15 @@ msgstr ""
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
+msgid "You can see your chat accounts."
+msgstr ""
+
msgid "You can set up jobs to only use Runners with specific tags. Separate tags with commas."
msgstr ""
+msgid "You can specify notification level per group or per project."
+msgstr ""
+
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
@@ -11211,6 +11772,12 @@ msgstr ""
msgid "You do not have permission to leave this %{namespaceType}."
msgstr ""
+msgid "You don't have any U2F devices registered yet."
+msgstr ""
+
+msgid "You don't have any active chat names."
+msgstr ""
+
msgid "You don't have any applications"
msgstr ""
@@ -11256,6 +11823,12 @@ msgstr ""
msgid "You must have permission to create a project in a namespace before forking."
msgstr ""
+msgid "You must provide a valid current password"
+msgstr ""
+
+msgid "You must provide your current password in order to change it."
+msgstr ""
+
msgid "You need permission."
msgstr ""
@@ -11331,24 +11904,45 @@ msgstr ""
msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
msgstr ""
+msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
+msgstr ""
+
msgid "YouTube"
msgstr ""
+msgid "Your Commit Email will be used for web based operations, such as edits and merges."
+msgstr ""
+
msgid "Your Conversational Development Index gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
msgstr ""
+msgid "Your Default Notification Email will be used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set."
+msgstr ""
+
+msgid "Your GPG keys (%{count})"
+msgstr ""
+
msgid "Your Groups"
msgstr ""
msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure"
msgstr ""
+msgid "Your Primary Email will be used for avatar detection."
+msgstr ""
+
msgid "Your Projects (default)"
msgstr ""
msgid "Your Projects' Activity"
msgstr ""
+msgid "Your Public Email will be displayed on your public profile."
+msgstr ""
+
+msgid "Your SSH keys (%{count})"
+msgstr ""
+
msgid "Your Todos"
msgstr ""
@@ -11394,9 +11988,6 @@ msgstr ""
msgid "Your comment could not be updated! Please check your network connection and try again."
msgstr ""
-msgid "Your comment will not be visible to the public."
-msgstr ""
-
msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
msgstr ""
@@ -11475,6 +12066,9 @@ msgstr ""
msgid "commented on %{link_to_project}"
msgstr ""
+msgid "commit %{commit_id}"
+msgstr ""
+
msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
msgstr ""
@@ -11490,6 +12084,9 @@ msgstr ""
msgid "customize"
msgstr ""
+msgid "date must not be after 9999-12-31"
+msgstr ""
+
msgid "day"
msgid_plural "days"
msgstr[0] ""
@@ -11733,6 +12330,9 @@ msgstr ""
msgid "mrWidget|Merge failed."
msgstr ""
+msgid "mrWidget|Merge failed: %{mergeError}. Please try again."
+msgstr ""
+
msgid "mrWidget|Merge locally"
msgstr ""
@@ -11997,6 +12597,12 @@ msgstr ""
msgid "verify ownership"
msgstr ""
+msgid "via %{closed_via}"
+msgstr ""
+
+msgid "via merge request %{link}"
+msgstr ""
+
msgid "view it on GitLab"
msgstr ""
diff --git a/package.json b/package.json
index 0cbc178a923..ab7268642ec 100644
--- a/package.json
+++ b/package.json
@@ -23,9 +23,10 @@
"stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.* ee/app/assets/stylesheets/**/*.* !**/vendors/** --custom-formatter node_modules/stylelint-error-string-formatter",
"stylelint-file": "node node_modules/stylelint/bin/stylelint.js",
"stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js",
- "test": "yarn jest && yarn karma",
+ "test": "node scripts/frontend/test",
"webpack": "NODE_OPTIONS=\"--max-old-space-size=3584\" webpack --config config/webpack.config.js",
- "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js"
+ "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js",
+ "webpack-vrt": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.review_toolbar.js"
},
"dependencies": {
"@babel/core": "^7.4.4",
@@ -36,10 +37,12 @@
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.4.4",
"@gitlab/csslab": "^1.9.0",
- "@gitlab/svgs": "^1.60.0",
- "@gitlab/ui": "^3.10.0",
+ "@gitlab/svgs": "^1.63.0",
+ "@gitlab/ui": "^3.11.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
+ "apollo-link": "^1.2.11",
+ "apollo-link-batch-http": "^1.2.11",
"apollo-upload-client": "^10.0.0",
"at.js": "^1.5.4",
"autosize": "^4.0.0",
@@ -53,7 +56,7 @@
"clipboard": "^1.7.1",
"codesandbox-api": "^0.0.20",
"compression-webpack-plugin": "^2.0.0",
- "core-js": "^2.4.1",
+ "core-js": "^3.1.3",
"cropper": "^2.3.0",
"css-loader": "^1.0.0",
"d3": "^4.13.0",
@@ -143,7 +146,7 @@
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.2.0",
- "@gitlab/eslint-config": "^1.5.0",
+ "@gitlab/eslint-config": "^1.6.0",
"@vue/test-utils": "^1.0.0-beta.25",
"axios-mock-adapter": "^1.15.0",
"babel-jest": "^24.1.0",
diff --git a/qa/Gemfile b/qa/Gemfile
index 64215b24cf1..12994b85322 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -10,3 +10,4 @@ gem 'airborne', '~> 0.2.13'
gem 'nokogiri', '~> 1.10.3'
gem 'rspec-retry', '~> 0.6.1'
gem 'faker', '~> 1.6', '>= 1.6.6'
+gem 'knapsack', '~> 1.17'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index a06c88b6f0a..6b0635ed0e2 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -39,6 +39,8 @@ GEM
domain_name (~> 0.5)
i18n (0.9.1)
concurrent-ruby (~> 1.0)
+ knapsack (1.17.1)
+ rake
launchy (2.4.3)
addressable (~> 2.3)
method_source (0.9.0)
@@ -102,6 +104,7 @@ DEPENDENCIES
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
faker (~> 1.6, >= 1.6.6)
+ knapsack (~> 1.17)
nokogiri (~> 1.10.3)
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
diff --git a/qa/README.md b/qa/README.md
index 002ad4c65f5..ef6f202464d 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -1,6 +1,6 @@
# GitLab QA - End-to-end tests for GitLab
-This directory contains [end-to-end tests](doc/development/testing_guide/end_to_end_tests.md)
+This directory contains [end-to-end tests](../../../doc/development/testing_guide/end_to_end/index.md)
for GitLab. It includes the test framework and the tests themselves.
The tests can be found in `qa/specs/features` (not to be confused with the unit
@@ -29,7 +29,7 @@ verify coupling between page objects implemented as a part of GitLab QA
and corresponding views / partials / selectors in CE / EE.
Whenever `qa:selectors` job fails in your merge request, you are supposed to
-fix [page objects](qa/page/README.md). You should also trigger end-to-end tests
+fix [page objects](../doc/development/testing_guide/end_to_end/page_objects.md). You should also trigger end-to-end tests
using `package-and-qa` manual action, to test if everything works fine.
## How can I use it?
@@ -49,10 +49,10 @@ will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blo
### Writing tests
-- [Writing tests from scratch tutorial](docs/WRITING_TESTS_FROM_SCRATCH.md)
- - [Best practices](docs/BEST_PRACTICES.md)
- - [Using page objects](qa/page/README.md)
- - [Guidelines](docs/GUIDELINES.md)
+- [Writing tests from scratch tutorial](../doc/development/testing_guide/end_to_end/quick_start_guide.md)
+ - [Best practices](../doc/development/testing_guide/best_practices.md)
+ - [Using page objects](../doc/development/testing_guide/end_to_end/page_objects.md)
+ - [Guidelines](../doc/development/testing_guide/index.md)
### Running specific tests
diff --git a/qa/docs/GUIDELINES.md b/qa/docs/GUIDELINES.md
deleted file mode 100644
index 9db52cd07e6..00000000000
--- a/qa/docs/GUIDELINES.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Style guide for writing GUI tests
-
-This document describes the conventions used at GitLab for writing GUI tests using the GitLab QA project.
-
-## `click_` versus `go_to_`
-
-### When to use `click_`?
-
-When clicking in a single link to navigate, use `click_`.
-
-E.g.:
-
-```ruby
-def click_ci_cd_pipelines
- within_sidebar do
- click_element :link_pipelines
- end
-end
-```
-
-From a testing perspective, if we want to check that clicking a link, or a button (a single interaction) is working as intended, we would want the test to read as:
-
-- Click a certain element
-- Verify the action took place
-
-### When to use `go_to_`?
-
-When interacting with multiple elements to go to a page, use `go_to_`.
-
-E.g.:
-
-```ruby
-def go_to_operations_environments
- hover_operations do
- within_submenu do
- click_element(:operations_environments_link)
- end
- end
-end
-```
-
-`go_to_` fits the definition of interacting with multiple elements very well given it's more of a meta-navigation action that includes multiple interactions.
-
-Notice that in the above example, before clicking the `:operations_environments_link`, another element is hovered over.
-
-> We can create these methods as helpers to abstract multi-step navigation.
diff --git a/qa/docs/WRITING_TESTS_FROM_SCRATCH.md b/qa/docs/WRITING_TESTS_FROM_SCRATCH.md
deleted file mode 100644
index f91286c232e..00000000000
--- a/qa/docs/WRITING_TESTS_FROM_SCRATCH.md
+++ /dev/null
@@ -1,482 +0,0 @@
-# Writing end-to-end tests step-by-step
-
-In this tutorial, you will find different examples, and the steps involved, in the creation of end-to-end (_e2e_) tests for GitLab CE and GitLab EE, using GitLab QA.
-
-> When referring to end-to-end tests in this document, this means testing a specific feature end-to-end, such as a user logging in, the creation of a project, the management of labels, breaking down epics into sub-epics and issues, etc.
-
-## Important information before we start writing tests
-
-It's important to understand that end-to-end tests of isolated features, such as the ones described in the above note, doesn't mean that everything needs to happen through the GUI.
-
-If you don't exactly understand what we mean by **not everything needs to happen through the GUI,** please make sure you've read the [best practices](./BEST_PRACTICES.md) before moving on.
-
-## This document covers the following items:
-
-0. Identifying if end-to-end tests are really needed
-1. Identifying the [DevOps stage](https://about.gitlab.com/stages-devops-lifecycle/) of the feature that you are going to cover with end-to-end tests
-2. Creating the skeleton of the test file (`*_spec.rb`)
-3. The [MVC](https://about.gitlab.com/handbook/values/#minimum-viable-change-mvc) of the test cases logic
-4. Extracting duplicated code into methods
-5. Tests' pre-conditions (`before :all` and `before`) using resources and [Page Objects](./qa/page/README.md)
-6. Optimizing the test suite
-7. Using and implementing resources
-8. Moving elements definitions and its methods to [Page Objects](./qa/page/README.md)
- - Adding testability to the application
-
-### 0. Are end-to-end tests needed?
-
-At GitLab we respect the [test pyramid](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/testing_guide/testing_levels.md), and so, we recommend to check the code coverage of a specific feature before writing end-to-end tests.
-
-Sometimes you may notice that there is already a good coverage in other test levels, and we can stay confident that if we break a feature, we will still have quick feedback about it, even without having end-to-end tests.
-
-If after this analysis you still think that end-to-end tests are needed, keep reading.
-
-### 1. Identifying the DevOps stage
-
-The GitLab QA end-to-end tests are organized by the different [stages in the DevOps lifecycle](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/specs/features/browser_ui), and so, if you are creating tests for issue creation, for instance, you would locate the spec files under the `qa/qa/specs/features/browser_ui/2_plan/` directory since issue creation is part of the Plan stage.
-
- In another case of a test for listing merged merge requests (MRs), the test should go under the `qa/qa/specs/features/browser_ui/3_create/` directory since merge request is a feature from the Create stage.
-
-> There may be sub-directories inside the stages directories, for different features. For example: `.../browser_ui/2_plan/ee_epics/` and `.../browser_ui/2_plan/issues/`.
-
-Now, let's say we want to create tests for the [scoped labels](https://about.gitlab.com/2019/04/22/gitlab-11-10-released/#scoped-labels) feature, available on GitLab EE Premium (this feature is part of the Plan stage.)
-
-> Because these tests are for a feature available only on GitLab EE, we need to create them in the [EE repository](https://gitlab.com/gitlab-org/gitlab-ee).
-
-Since [there is no specific directory for this feature](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/qa/qa/specs/features/browser_ui/2_plan), we should create a sub-directory for it.
-
-Under `.../browser_ui/2_plan/`, let's create a sub-directory called `ee_scoped_labels/`.
-
-> Notice that since this feature is only available for GitLab EE we prefix the sub-directory with `ee_`.
-
-### 2. Test skeleton
-
-Inside the newly created sub-directory, let's create a file describing the test suite (e.g. `editing_scoped_labels_spec.rb`.)
-
-#### The `context` and `describe` blocks
-
-Specs have an outer `context` that indicates the DevOps stage. The next level is the `describe` block, that briefly states the subject of the test suite. See the following example:
-
-```ruby
-module QA
- context 'Plan' do
- describe 'Editing scoped labels properties on issues' do
- end
- end
-end
-```
-
-#### The `it` blocks
-
-Every test suite is composed by at least one `it` block, and a good way to start writing end-to-end tests is by typing test cases descriptions as `it` blocks. Take a look at the following example:
-
-```ruby
-module QA
- context 'Plan' do
- describe 'Editing scoped labels properties on issues' do
- it 'replaces an existing label if it has the same key' do
- end
-
- it 'keeps both scoped labels when adding a label with a different key' do
- end
- end
- end
-end
-```
-
-### 3. Test cases MVC
-
-For the [MVC](https://about.gitlab.com/handbook/values/#minimum-viable-change-mvc) of our test cases, let's say that we already have the application in the state needed for the tests, and then let's focus on the logic of the test cases only.
-
-To evolve the test cases drafted on step 2, let's imagine that the user is already logged in a GitLab EE instance, they already have at least a Premium license in use, there is already a project created, there is already an issue opened in the project, the issue already has a scoped label (e.g. `foo::bar`), there are other scoped labels (for the same scope and for a different scope, e.g. `foo::baz` and `bar::bah`), and finally, the user is already on the issue's page. Let's also suppose that for every test case the application is in a clean state, meaning that one test case won't affect another.
-
-> Note: there are different approaches to create an application state for end-to-end tests. Some of them are very time consuming and subject to failures, such as when using the GUI for all the pre-conditions of the tests. On the other hand, other approaches are more efficient, such as using the public APIs. The latter is more efficient since it doesn't depend on the GUI. We won't focus on this part yet, but it's good to keep it in mind.
-
-Let's now focus on the first test case.
-
-```ruby
-it 'keeps the latest scoped label when adding a label with the same key of an existing one, but with a different value' do
- # This implementation is only for tutorial purposes. We normally encapsulate elements in Page Objects.
- page.find('.block.labels .edit-link').click
- page.find('.dropdown-menu-labels .dropdown-input-field').send_keys ['foo::baz', :enter]
- page.find('#content-body').click
- page.refresh
-
- scoped_label = page.find('.qa-labels-block .scoped-label-wrapper')
-
- expect(scoped_label).to have_content('foo::baz')
- expect(scoped_label).not_to have_content('foo::bar')
- expect(page).to have_content('added foo::baz label and removed foo::bar')
-end
-```
-
-> Notice that the test itself is simple. The most challenging part is the creation of the application state, which will be covered later.
-
-> The exemplified test cases' MVC is not enough for the change to be submitted in an MR, but they help on building up the test logic. The reason is that we do not want to use locators directly in the tests, and tests **must** use [Page Objects](./qa/page/README.md) before they can be merged.
-
-Below are the steps that the test covers:
-
-1. The test finds the 'Edit' link for the labels and clicks on it
-2. Then it fills in the 'Assign labels' input field with the value 'foo::baz' and press enter
-3. Then it clicks in the content body to apply the label and refreshes the page
-4. Finally the expectation that the previous scoped label was removed and that the new one was added happens
-
-Let's now see how the second test case would look like.
-
-```ruby
-it 'keeps both scoped labels when adding a label with a different key' do
- # This implementation is only for tutorial purposes. We normally encapsulate elements in Page Objects.
- page.find('.block.labels .edit-link').click
- page.find('.dropdown-menu-labels .dropdown-input-field').send_keys ['bar::bah', :enter]
- page.find('#content-body').click
- page.refresh
-
- scoped_labels = page.all('.qa-labels-block .scoped-label-wrapper')
-
- expect(scoped_labels.first).to have_content('bar::bah')
- expect(scoped_labels.last).to have_content('foo::ba')
- expect(page).to have_content('added bar::bah')
- expect(page).to have_content('added foo::ba')
-end
-```
-
-> Note that elements are always located using CSS selectors, and a good practice is to add test specific attribute:value for elements (this is called adding testability to the application and we will talk more about it later.)
-
-Below are the steps that the test covers:
-
-1. The test finds the 'Edit' link for the labels and clicks on it
-2. Then it fills in the 'Assign labels' input field with the value 'bar::bah' and press enter
-3. Then it clicks in the content body to apply the label and refreshes the page
-4. Finally the expectation that the both scoped labels are present happens
-
-> Similar to the previous test, this one is also very straight forward, but there is some code duplication. Let's address it.
-
-### 4. Extracting duplicated code
-
-If we refactor the tests created on step 3 we could come up with something like this:
-
-```ruby
-it 'keeps the latest scoped label when adding a label with the same key of an existing one, but with a different value' do
- select_label_and_refresh 'foo::baz'
-
- expect(page).to have_content('added foo::baz')
- expect(page).to have_content('and removed foo::bar')
-
- scoped_label = page.find('.qa-labels-block .scoped-label-wrapper')
-
- expect(scoped_label).to have_content('foo::baz')
- expect(scoped_label).not_to have_content('foo::bar')
-end
-
-it 'keeps both scoped label when adding a label with a different key' do
- select_label_and_refresh 'bar::bah'
-
- expect(page).to have_content('added bar::bah')
- expect(page).to have_content('added foo::ba')
-
- scoped_labels = page.all('.qa-labels-block .scoped-label-wrapper')
-
- expect(scoped_labels.first).to have_content('bar::bah')
- expect(scoped_labels.last).to have_content('foo::ba')
-end
-
-def select_label_and_refresh(label)
- page.find('.block.labels .edit-link').click
- page.find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]
- page.find('#content-body').click
- page.refresh
-end
-```
-
-By creating a reusable `select_label_and_refresh` method, we remove the code duplication, and later we can move this method to a Page Object class that will be created for easier maintenance purposes.
-
-> Notice that the reusable method is created in the bottom of the file. The reason for that is that reading the code should be similar to reading a newspaper, where high-level information is at the top, like the title and summary of the news, while low level, or more specific information, is at the bottom.
-
-### 5. Tests' pre-conditions using resources and Page Objects
-
-In this section, we will address the previously mentioned subject of creating the application state for the tests, using the `before :all` and `before` blocks, together with resources and Page Objects.
-
-#### `before :all`
-
-A pre-condition for the entire test suite is defined in the `before :all` block.
-
-For our test suite example, some things that could happen before the entire test suite starts are:
-
-- The user logging in;
-- A premium license already being set up;
-- A project being created with an issue and labels already setup.
-
-> In case of a test suite with only one `it` block it's ok to use only the `before` block (see below) with all the test's pre-conditions.
-
-#### `before`
-
-A pre-condition for each test case is defined in the `before` block.
-
-For our test cases samples, what we need is that for every test the issue page is opened, and there is only one scoped label applied to it.
-
-#### Implementation
-
-In the following code we will focus on the test suite and the test cases' pre-conditions only:
-
-```ruby
-module QA
- context 'Plan' do
- describe 'Editing scoped labels properties on issues' do
- before :all do
- project = Resource::Project.fabricate_via_api! do |resource|
- resource.name = 'scoped-labels-project'
- end
-
- @foo_bar_scoped_label = 'foo::bar'
-
- @issue = Resource::Issue.fabricate_via_api! do |issue|
- issue.project = project
- issue.title = 'Issue to test the scoped labels'
- issue.labels = @foo_bar_scoped_label
- end
-
- @labels = ['foo::baz', 'bar::bah']
- @labels.each do |label|
- Resource::Label.fabricate_via_api! do |l|
- l.project = project.id
- l.title = label
- end
- end
-
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
- end
-
- before do
- Page::Project::Issue::Show.perform do |issue_page|
- @issue.visit!
- end
- end
-
- it 'keeps the latest scoped label when adding a label with the same key of an existing one, but with a different value' do
- ...
- end
-
- it 'keeps both scoped labels when adding a label with a different key' do
- ...
- end
-
- def select_label_and_refresh(label)
- ...
- end
- end
- end
-end
-```
-
-In the `before :all` block we create all the application state needed for the tests to run. We do that by fabricating resources via APIs (`project`, `@issue`, and `@labels`), by using the `Runtime::Browser.visit` method to go to the login page, and by performing a `sign_in_using_credentials` from the `Login` Page Object.
-
-> When creating the resources, notice that when calling the `fabricate_via_api` method, we pass some attribute:values, like `name` for the `project` resoruce, `project`, `title`, and `labels` for the the issue resource, and `project`, and `title` for `label` resources.
-
-> What's important to understand here is that by creating the application state mostly using the public APIs we save a lot of time in the test suite setup stage.
-
-> Soon we will cover the use of the already existing resources' methods and the creation of your own `fabricate_via_api` methods for resources where this is still not available, but first, let's optimize our implementation.
-
-### 6. Optimization
-
-As already mentioned in the [best practices](./BEST_PRACTICES.md) document, end-to-end tests are very costly in terms of execution time, and it's our responsibility as software engineers to ensure that we optimize them as max as possible.
-
-> Differently than unit tests, that exercise every little piece of the application in isolation, usually having only one assertion per test, and being very fast to run, end-to-end tests can have more actions and assertions in a single test to help on speeding up the test's feedback since they are much slower when comparing to unit tests.
-
-Some improvements that we could make in our test suite to optimize its time to run are:
-
-1. Having a single test case (an `it` block) that exercise both scenarios to avoid "wasting" time in the tests' pre-conditions, instead of having two different test cases.
-2. Moving all the pre-conditions to the `before` block since there will be only one `it` block.
-3. Making the selection of labels more performant by allowing for the selection of more than one label in the same reusable method.
-
-Let's look at a suggestion that addresses the above points, one by one:
-
-```ruby
-module QA
- context 'Plan' do
- describe 'Editing scoped labels properties on issues' do
- before do
- project = Resource::Project.fabricate_via_api! do |resource|
- resource.name = 'scoped-labels-project'
- end
-
- @foo_bar_scoped_label = 'foo::bar'
-
- @issue = Resource::Issue.fabricate_via_api! do |issue|
- issue.project = project
- issue.title = 'Issue to test the scoped labels'
- issue.labels = @foo_bar_scoped_label
- end
-
- @labels = ['foo::baz', 'bar::bah']
- @labels.each do |label|
- Resource::Label.fabricate_via_api! do |l|
- l.project = project.id
- l.title = label
- end
- end
-
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
- Page::Project::Issue::Show.perform do |issue_page|
- @issue.visit!
- end
- end
-
- it 'correctly applies the scoped labels depending if they are from the same or a different scope' do
- select_labels_and_refresh @labels
-
- scoped_labels = page.all('.qa-labels-block .scoped-label-wrapper')
-
- expect(page).to have_content("added #{@foo_bar_scoped_label}")
- expect(page).to have_content("added #{@labels[1]} #{@labels[0]} labels and removed #{@foo_bar_scoped_label}")
- expect(scoped_labels.count).to eq(2)
- expect(scoped_labels.first).to have_content(@labels[1])
- expect(scoped_labels.last).to have_content(@labels[0])
- end
-
- def select_labels_and_refresh(labels)
- find('.block.labels .edit-link').click
- labels.each do |label|
- find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]
- end
- find('#content-body').click
- refresh
- end
- end
- end
- end
-```
-
-As you can see, now all the pre-conditions from the `before :all` block were moved to the `before` block, addressing point 2.
-
-To address point 1, we changed the test implementation from two `it` blocks into a single one that exercises both scenarios. Now the new test description is: `'correctly applies the scoped labels depending if they are from the same or a different scope'`. It's a long description, but it describes well what the test does.
-
-> Notice that the implementation of the new and unique `it` block had to change a little bit. Below we describe in details what it does.
-
-1. At the same time, it selects two scoped labels, one from the same scope of the one already applied in the issue during the setup phase (in the `before` block), and another one from a different scope.
-2. It runs the assertions that the labels where correctly added and removed; that only two labels are applied; and that those are the correct ones, and that they are shown in the right order.
-
-Finally, the `select_label_and_refresh` method is changed to `select_labels_and_refresh`, which accepts an array of labels instead of a single label, and it iterates on them for faster label selection (this is what is used in step 1 explained above.)
-
-### 7. Resources
-
-You can think of resources as anything that can be created on GitLab CE or EE, either through the GUI, the API, or the CLI.
-
-With that in mind, resources can be a project, an epic, an issue, a label, a commit, etc.
-
-As you saw in the tests' pre-conditions and the optimization sections, we're already creating some of these resources, and we are doing that by calling the `fabricate_via_api!` method.
-
-> We could be using the `fabricate!` method instead, which would use the `fabricate_via_api!` method if it exists, and fallback to GUI fabrication otherwise, but we recommend being explicit to make it clear what the test does. Also, we always recommend fabricating resources via API since this makes tests faster and more reliable.
-
-For our test suite example, the [project resource](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/project.rb#L55) already had a `fabricate_via_api!` method available, while other resources don't have it, so we will have to create them, like for the issue and label resources. Also, we will have to make a small change in the project resource to expose its `id` attribute so that we can refer to it when fabricating the issue.
-
-#### Implementation
-
-Following we describe the changes needed in every of the before-mentioned resource files.
-
-**Project resource**
-
-Let's start with the smallest change.
-
-In the [project resource](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/project.rb), let's expose its `id` attribute.
-
-Add the following `attribute :id` right below the [`attribute :description`](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/project.rb#L11).
-
-> This line is needed to allow for issues and labels to be automatically added to a project when fabricating them via API.
-
-**Issue resource**
-
-Now, let's make it possible to create an issue resource through the API.
-
-First, in the [issue resource](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb), let's expose its labels attribute.
-
-Add the following `attribute :labels` right below the [`attribute :title`](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb#L15).
-
-> This line is needed to allow for labels to be automatically added to an issue when fabricating it via API.
-
-Next, add the following code right below the [`fabricate!`](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb#L27) method.
-
-```ruby
-def api_get_path
- "/projects/#{project.id}/issues/#{id}"
-end
-
-def api_post_path
- "/projects/#{project.id}/issues"
-end
-
-def api_post_body
- {
- title: title,
- labels: [labels]
- }
-end
-```
-
-By defining the `api_get_path` method, we allow the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to get a single issue.
-
-> This `GET` path can be found in the [public API documentation](https://docs.gitlab.com/ee/api/issues.html#single-issue).
-
-By defining the `api_post_path` method, we allow the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to create a new issue in a specific project.
-
-> This `POST` path can be found in the [public API documentation](https://docs.gitlab.com/ee/api/issues.html#new-issue).
-
-By defining the `api_post_body` method, we allow the [`ApiFabricator.api_post`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/api_fabricator.rb#L68) method to know which data to send when making the `POST` request.
-
-> Notice that we pass both `title` and `labels` attributes in the `api_post_body`, where `labels` receives an array of labels, and [`title` is required](https://docs.gitlab.com/ee/api/issues.html#new-issue).
-
-**Label resource**
-
-Finally, let's make it possible to create label resources through the API.
-
-Add the following code right below the [`fabricate!`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/label.rb#L36) method.
-
-```ruby
-def resource_web_url(resource)
- super
-rescue ResourceURLMissingError
- # this particular resource does not expose a web_url property
-end
-
-def api_get_path
- raise NotImplementedError, "The Labels API doesn't expose a single-resource endpoint so this method cannot be properly implemented."
-end
-
-def api_post_path
- "/projects/#{project}/labels"
-end
-
-def api_post_body
- {
- name: @title,
- color: @color
- }
-end
-```
-
-By defining the `resource_web_url(resource)` method, we override the one from the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb#L44) module. We do that to avoid failing the test due to this particular resource not exposing a `web_url` property.
-
-By defining the `api_get_path` method, we **would** allow for the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to get a single label, but since there's no path available for that in the publich API, we raise a `NotImplementedError` instead.
-
-By defining the `api_post_path` method, we allow for the [`ApiFabricator `](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to create a new label in a specific project.
-
-By defining the `api_post_body` method, we we allow for the [`ApiFabricator.api_post`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/api_fabricator.rb#L68) method to know which data to send when making the `POST` request.
-
-> Notice that we pass both `name` and `color` attributes in the `api_post_body` since [those are required](https://docs.gitlab.com/ee/api/labels.html#create-a-new-label).
-
-### 8. Page Objects
-
-> Page Objects are auto-loaded in the `qa/qa.rb` file and available in all the test files (`*_spec.rb`).
-
-Page Objects are used in end-to-end tests for maintenance reasons, where page's elements and methods are defined to be reused in any test.
-
-Take a look at [this document that specifically details the usage of Page Objects](./qa/page/README.md).
-
-Now, let's go back to our examples.
-
-...
-
-#### Adding testability
-
-TBD.
diff --git a/qa/knapsack/gitlab-ce/review-qa-all_master_report.json b/qa/knapsack/gitlab-ce/review-qa-all_master_report.json
new file mode 100644
index 00000000000..f147346ba0f
--- /dev/null
+++ b/qa/knapsack/gitlab-ce/review-qa-all_master_report.json
@@ -0,0 +1,42 @@
+{
+ "qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb": 9.697327613830566,
+ "qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 46.54227638244629,
+ "qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb": 10.214765310287476,
+ "qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb": 7.882027864456177,
+ "qa/specs/features/api/3_create/repository/files_spec.rb": 5.015859127044678,
+ "qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 12.772682905197144,
+ "qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 29.76174831390381,
+ "qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb": 22.800872802734375,
+ "qa/specs/features/browser_ui/1_manage/login/register_spec.rb": 22.320587396621704,
+ "qa/specs/features/api/1_manage/users_spec.rb": 0.6089541912078857,
+ "qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 0.9618203639984131,
+ "qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 13.403101205825806,
+ "qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb": 8.810423135757446,
+ "qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 7.730542182922363,
+ "qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb": 16.18057894706726,
+ "qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb": 8.31815505027771,
+ "qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 9.48607873916626,
+ "qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 19.552733182907104,
+ "qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb": 17.273863554000854,
+ "qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb": 8.281434059143066,
+ "qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb": 18.047621726989746,
+ "qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 7.422840595245361,
+ "qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb": 3.438166856765747,
+ "qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 18.679633855819702,
+ "qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 27.943300485610962,
+ "qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb": 39.17585229873657,
+ "qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 40.09336972236633,
+ "qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 3.705310821533203,
+ "qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb": 5.812374591827393,
+ "qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 92.46774697303772,
+ "qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb": 100.28881478309631,
+ "qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 23.710937023162842,
+ "qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb": 20.58603596687317,
+ "qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb": 25.460349321365356,
+ "qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 19.459370374679565,
+ "qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 6.731764793395996,
+ "qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 15.342933893203735,
+ "qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 11.280649185180664,
+ "qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 57.48992609977722,
+ "qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 32.5517954826355
+} \ No newline at end of file
diff --git a/qa/qa.rb b/qa/qa.rb
index f580691f952..944dcc31917 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -130,6 +130,7 @@ module QA
autoload :View, 'qa/page/view'
autoload :Element, 'qa/page/element'
autoload :Validator, 'qa/page/validator'
+ autoload :Validatable, 'qa/page/validatable'
module Main
autoload :Login, 'qa/page/main/login'
diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb
index d7748a976f0..018a1eb1bfc 100644
--- a/qa/qa/ce/strategy.rb
+++ b/qa/qa/ce/strategy.rb
@@ -12,8 +12,9 @@ module QA
def perform_before_hooks
# The login page could take some time to load the first time it is visited.
# We visit the login page and wait for it to properly load only once before the tests.
- QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
- QA::Page::Main::Login.perform(&:assert_page_loaded)
+ QA::Support::Retrier.retry_on_exception do
+ QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
+ end
end
end
end
diff --git a/qa/qa/fixtures/auto_devops_rack/Dockerfile b/qa/qa/fixtures/auto_devops_rack/Dockerfile
new file mode 100644
index 00000000000..1f59c23ea88
--- /dev/null
+++ b/qa/qa/fixtures/auto_devops_rack/Dockerfile
@@ -0,0 +1,9 @@
+FROM ruby:2.6.3-alpine
+ADD ./ /app/
+WORKDIR /app
+ENV RACK_ENV production
+ENV PORT 5000
+EXPOSE 5000
+
+RUN bundle install
+CMD ["bundle","exec", "rackup", "-p", "5000"]
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index c395e5f6011..d247a273637 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -8,6 +8,7 @@ module QA
prepend Support::Page::Logging if Runtime::Env.debug?
include Capybara::DSL
include Scenario::Actable
+ extend Validatable
extend SingleForwardable
ElementNotFound = Class.new(RuntimeError)
@@ -77,8 +78,12 @@ module QA
page.evaluate_script('xhr.status') == 200
end
- def find_element(name, text: nil, wait: Capybara.default_max_wait_time)
- find(element_selector_css(name), wait: wait, text: text)
+ def find_element(name, **kwargs)
+ find(element_selector_css(name), kwargs)
+ end
+
+ def active_element?(name)
+ find_element(name, class: 'active')
end
def all_elements(name)
@@ -93,8 +98,10 @@ module QA
find_element(name).set(false)
end
- def click_element(name)
+ # replace with (..., page = self.class)
+ def click_element(name, page = nil)
find_element(name).click
+ page.validate_elements_present! if page
end
def fill_element(name, content)
@@ -129,6 +136,15 @@ module QA
has_no_css?('.fa-spinner', wait: Capybara.default_max_wait_time)
end
+ def wait_for_animated_element(name)
+ # It would be ideal if we could detect when the animation is complete
+ # but in some cases there's nothing we can easily access via capybara
+ # so instead we wait for the element, and then we wait a little longer
+ raise ElementNotFound, %Q(Couldn't find element named "#{name}") unless has_element?(name)
+
+ sleep 1
+ end
+
def within_element(name, text: nil)
page.within(element_selector_css(name), text: text) do
yield
diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb
index d92e71467fe..7a01320901d 100644
--- a/qa/qa/page/element.rb
+++ b/qa/qa/page/element.rb
@@ -1,28 +1,41 @@
# frozen_string_literal: true
+require 'active_support/core_ext/array/extract_options'
+
module QA
module Page
class Element
- attr_reader :name
+ attr_reader :name, :attributes
- def initialize(name, pattern = nil)
+ def initialize(name, *options)
@name = name
- @pattern = pattern || selector
+ @attributes = options.extract_options!
+ @attributes[:pattern] ||= selector
+
+ options.each do |option|
+ if option.is_a?(String) || option.is_a?(Regexp)
+ @attributes[:pattern] = option
+ end
+ end
end
def selector
"qa-#{@name.to_s.tr('_', '-')}"
end
+ def required?
+ !!@attributes[:required]
+ end
+
def selector_css
".#{selector}"
end
def expression
- if @pattern.is_a?(String)
- @_regexp ||= Regexp.new(Regexp.escape(@pattern))
+ if @attributes[:pattern].is_a?(String)
+ @_regexp ||= Regexp.new(Regexp.escape(@attributes[:pattern]))
else
- @pattern
+ @attributes[:pattern]
end
end
diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb
index dd9a9e054e1..e42de7d65c5 100644
--- a/qa/qa/page/file/form.rb
+++ b/qa/qa/page/file/form.rb
@@ -38,6 +38,8 @@ module QA
def commit_changes
click_on 'Commit changes'
+
+ finished_loading?
end
def select_template(template_type, template)
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 99b3d1b83d3..8970eeb6678 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -39,19 +39,7 @@ module QA
end
view 'app/views/layouts/devise.html.haml' do
- element :login_page
- end
-
- def assert_page_loaded
- unless page_loaded?
- raise QA::Runtime::Browser::NotRespondingError, "Login page did not load at #{QA::Page::Main::Login.perform(&:current_url)}"
- end
- end
-
- def page_loaded?
- wait(max: 60) do
- has_element?(:login_page)
- end
+ element :login_page, required: true
end
def sign_in_using_credentials(user = nil)
@@ -159,7 +147,7 @@ module QA
fill_element :login_field, user.username
fill_element :password_field, user.password
- click_element :sign_in_button
+ click_element :sign_in_button, Page::Main::Menu
end
def set_initial_password_if_present
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index e98d531c86e..5eb24d2d2ba 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -10,15 +10,15 @@ module QA
end
view 'app/views/layouts/header/_default.html.haml' do
- element :navbar
- element :user_avatar
+ element :navbar, required: true
+ element :user_avatar, required: true
element :user_menu, '.dropdown-menu' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/layouts/nav/_dashboard.html.haml' do
element :admin_area_link
- element :projects_dropdown
- element :groups_dropdown
+ element :projects_dropdown, required: true
+ element :groups_dropdown, required: true
element :snippets_link
end
diff --git a/qa/qa/page/project/branches/show.rb b/qa/qa/page/project/branches/show.rb
index 762eacdab15..480fc7d78cb 100644
--- a/qa/qa/page/project/branches/show.rb
+++ b/qa/qa/page/project/branches/show.rb
@@ -28,9 +28,11 @@ module QA
finished_loading?
end
- def has_no_branch?(branch_name)
- within_element(:all_branches) do
- has_no_element?(:branch_name, text: branch_name, wait: Support::Waiter::DEFAULT_MAX_WAIT_TIME)
+ def has_no_branch?(branch_name, reload: false)
+ wait(reload: reload) do
+ within_element(:all_branches) do
+ has_no_element?(:branch_name, text: branch_name)
+ end
end
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 4f26ca5037c..defd85a5740 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -12,6 +12,7 @@ module QA
end
view 'app/views/projects/_new_project_fields.html.haml' do
+ element :initialize_with_readme_checkbox
element :project_namespace_select
element :project_namespace_field, 'namespaces_options' # rubocop:disable QA/ElementWithPattern
element :project_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
@@ -64,6 +65,10 @@ module QA
def click_github_link
click_link 'GitHub'
end
+
+ def enable_initialize_with_readme
+ check_element :initialize_with_readme_checkbox
+ end
end
end
end
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index 4096f57b7db..c81e13e9b91 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -8,8 +8,8 @@ module QA
class Show < Page::Base
view 'app/assets/javascripts/clusters/components/application_row.vue' do
element :application_row, 'js-cluster-application-row-${this.id}' # rubocop:disable QA/ElementWithPattern
- element :install_button, "s__('ClusterIntegration|Install')" # rubocop:disable QA/ElementWithPattern
- element :installed_button, "s__('ClusterIntegration|Installed')" # rubocop:disable QA/ElementWithPattern
+ element :install_button, "__('Install')" # rubocop:disable QA/ElementWithPattern
+ element :installed_button, "__('Installed')" # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/clusters/components/applications.vue' do
diff --git a/qa/qa/page/project/settings/ci_variables.rb b/qa/qa/page/project/settings/ci_variables.rb
index 567fe6f83c8..3621e618bf2 100644
--- a/qa/qa/page/project/settings/ci_variables.rb
+++ b/qa/qa/page/project/settings/ci_variables.rb
@@ -11,6 +11,7 @@ module QA
element :variable_row, '.ci-variable-row-body' # rubocop:disable QA/ElementWithPattern
element :variable_key, '.qa-ci-variable-input-key' # rubocop:disable QA/ElementWithPattern
element :variable_value, '.qa-ci-variable-input-value' # rubocop:disable QA/ElementWithPattern
+ element :variable_masked
end
view 'app/views/ci/variables/_index.html.haml' do
@@ -18,7 +19,7 @@ module QA
element :reveal_values, '.js-secret-value-reveal-button' # rubocop:disable QA/ElementWithPattern
end
- def fill_variable(key, value)
+ def fill_variable(key, value, masked)
keys = all_elements(:ci_variable_input_key)
index = keys.size - 1
@@ -32,6 +33,9 @@ module QA
# The code was inspired from:
# https://github.com/teamcapybara/capybara/blob/679548cea10773d45e32808f4d964377cfe5e892/lib/capybara/selenium/node.rb#L217
execute_script("arguments[0].value = #{value.to_json}", node)
+
+ masked_node = all_elements(:variable_masked)[index]
+ toggle_masked(masked_node, masked)
end
def save_variables
@@ -47,6 +51,24 @@ module QA
find('.qa-ci-variable-input-value').value
end
end
+
+ private
+
+ def toggle_masked(masked_node, masked)
+ wait(reload: false) do
+ masked_node.click
+
+ masked ? masked_enabled?(masked_node) : masked_disabled?(masked_node)
+ end
+ end
+
+ def masked_enabled?(masked_node)
+ masked_node[:class].include?('is-checked')
+ end
+
+ def masked_disabled?(masked_node)
+ !masked_enabled?(masked_node)
+ end
end
end
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index ff7cc04e352..b5a36862389 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -7,6 +7,10 @@ module QA
class Edit < Page::Base
include Page::Component::DropdownFilter
+ view 'app/assets/javascripts/ide/components/activity_bar.vue' do
+ element :commit_mode_tab
+ end
+
view 'app/assets/javascripts/ide/components/ide_tree.vue' do
element :new_file
end
@@ -17,6 +21,7 @@ module QA
view 'app/assets/javascripts/ide/components/new_dropdown/modal.vue' do
element :full_file_path
+ element :new_file_modal
element :template_list
end
@@ -42,12 +47,19 @@ module QA
def create_new_file_from_template(file_name, template)
click_element :new_file
+
+ # Wait for the modal animation to complete before clicking on the file name
+ wait_for_animated_element(:new_file_modal)
+
within_element(:template_list) do
click_on file_name
rescue Capybara::ElementNotFound
raise ElementNotFound, %Q(Couldn't find file template named "#{file_name}". Please confirm that it is a valid option.)
end
+ # Wait for the modal to fade out too
+ has_no_element?(:new_file_modal)
+
wait(reload: false) do
within_element(:file_templates_bar) do
click_element :file_template_dropdown
@@ -63,10 +75,16 @@ module QA
end
def commit_changes
+ # Clicking :begin_commit_button the first time switches from the
+ # edit to the commit view
+ click_element :begin_commit_button
+ active_element? :commit_mode_tab
+
+ # We need to click :begin_commit_button again
click_element :begin_commit_button
- # After clicking :begin_commit_button there is an animation that
- # hides :begin_commit_button and shows :commit_button
+ # After clicking :begin_commit_button the 2nd time there is an
+ # animation that hides :begin_commit_button and shows :commit_button
#
# Wait for the animation to complete before clicking :commit_button
# otherwise the click will quietly do nothing.
@@ -75,7 +93,10 @@ module QA
has_element?(:commit_button)
end
- # Retry the attempt to click :commit_button just in case part of the
+ # At this point we're ready to commit and the button should be
+ # labelled "Stage & Commit"
+ #
+ # Click :commit_button and keep retrying just in case part of the
# animation is still in process even when the buttons have the
# expected visibility.
commit_success_msg_shown = retry_until do
diff --git a/qa/qa/page/validatable.rb b/qa/qa/page/validatable.rb
new file mode 100644
index 00000000000..8467d261285
--- /dev/null
+++ b/qa/qa/page/validatable.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Validatable
+ PageValidationError = Class.new(StandardError)
+
+ def validate_elements_present!
+ base_page = self.new
+
+ elements.each do |element|
+ next unless element.required?
+
+ # TODO: this wait needs to be replaced by the wait class
+ unless base_page.has_element?(element.name, wait: 60)
+ raise Validatable::PageValidationError, "#{element.name} did not appear on #{self.name} as expected"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb
index 96f3917a8ab..613059b2d32 100644
--- a/qa/qa/page/view.rb
+++ b/qa/qa/page/view.rb
@@ -50,8 +50,8 @@ module QA
@elements = []
end
- def element(name, pattern = nil)
- @elements.push(Page::Element.new(name, pattern))
+ def element(name, *args)
+ @elements.push(Page::Element.new(name, *args))
end
end
end
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index 523d92c7ef3..283fc6cdbcb 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -15,6 +15,38 @@ module QA
def_delegators :evaluator, :attribute
+ def self.fabricate!(*args, &prepare_block)
+ fabricate_via_api!(*args, &prepare_block)
+ rescue NotImplementedError
+ fabricate_via_browser_ui!(*args, &prepare_block)
+ end
+
+ def self.fabricate_via_browser_ui!(*args, &prepare_block)
+ options = args.extract_options!
+ resource = options.fetch(:resource) { new }
+ parents = options.fetch(:parents) { [] }
+
+ do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
+ log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
+
+ current_url
+ end
+ end
+
+ def self.fabricate_via_api!(*args, &prepare_block)
+ options = args.extract_options!
+ resource = options.fetch(:resource) { new }
+ parents = options.fetch(:parents) { [] }
+
+ raise NotImplementedError unless resource.api_support?
+
+ resource.eager_load_api_client!
+
+ do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
+ log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
+ end
+ end
+
def fabricate!(*_args)
raise NotImplementedError
end
@@ -55,38 +87,6 @@ module QA
QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored."
end
- def self.fabricate!(*args, &prepare_block)
- fabricate_via_api!(*args, &prepare_block)
- rescue NotImplementedError
- fabricate_via_browser_ui!(*args, &prepare_block)
- end
-
- def self.fabricate_via_browser_ui!(*args, &prepare_block)
- options = args.extract_options!
- resource = options.fetch(:resource) { new }
- parents = options.fetch(:parents) { [] }
-
- do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
- log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
-
- current_url
- end
- end
-
- def self.fabricate_via_api!(*args, &prepare_block)
- options = args.extract_options!
- resource = options.fetch(:resource) { new }
- parents = options.fetch(:parents) { [] }
-
- raise NotImplementedError unless resource.api_support?
-
- resource.eager_load_api_client!
-
- do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
- log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
- end
- end
-
def self.do_fabricate!(resource:, prepare_block:, parents: [])
prepare_block.call(resource) if prepare_block
diff --git a/qa/qa/resource/ci_variable.rb b/qa/qa/resource/ci_variable.rb
index 341d3c1ed7e..b178a64b72d 100644
--- a/qa/qa/resource/ci_variable.rb
+++ b/qa/qa/resource/ci_variable.rb
@@ -3,7 +3,7 @@
module QA
module Resource
class CiVariable < Base
- attr_accessor :key, :value
+ attr_accessor :key, :value, :masked
attribute :project do
Project.fabricate! do |resource|
@@ -19,7 +19,7 @@ module QA
Page::Project::Settings::CICD.perform do |setting|
setting.expand_ci_variables do |page|
- page.fill_variable(key, value)
+ page.fill_variable(key, value, masked)
page.save_variables
end
@@ -49,7 +49,8 @@ module QA
def api_post_body
{
key: key,
- value: value
+ value: value,
+ masked: masked
}
end
end
diff --git a/qa/qa/resource/file.rb b/qa/qa/resource/file.rb
index 57e82ac19ad..ca74654bf90 100644
--- a/qa/qa/resource/file.rb
+++ b/qa/qa/resource/file.rb
@@ -3,9 +3,12 @@
module QA
module Resource
class File < Base
- attr_accessor :name,
+ attr_accessor :author_email,
+ :author_name,
+ :branch,
:content,
- :commit_message
+ :commit_message,
+ :name
attribute :project do
Project.fabricate! do |resource|
@@ -31,6 +34,30 @@ module QA
page.commit_changes
end
end
+
+ def resource_web_url(resource)
+ super
+ rescue ResourceURLMissingError
+ # this particular resource does not expose a web_url property
+ end
+
+ def api_get_path
+ "/projects/#{CGI.escape(project.path_with_namespace)}/repository/files/#{CGI.escape(@name)}"
+ end
+
+ def api_post_path
+ api_get_path
+ end
+
+ def api_post_body
+ {
+ branch: @branch || "master",
+ author_email: @author_email || Runtime::User.default_email,
+ author_name: @author_name || Runtime::User.username,
+ content: content,
+ commit_message: commit_message
+ }
+ end
end
end
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index c1a0cff86d8..d706439a891 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -7,6 +7,8 @@ module QA
class Project < Base
include Events::Project
+ attr_writer :initialize_with_readme
+
attribute :id
attribute :name
attribute :description
@@ -33,6 +35,7 @@ module QA
def initialize
@description = 'My awesome project'
+ @initialize_with_readme = false
end
def name=(raw_name)
@@ -49,6 +52,7 @@ module QA
page.choose_name(@name)
page.add_description(@description)
page.set_visibility('Public')
+ page.enable_initialize_with_readme if @initialize_with_readme
page.create_new_project
end
end
@@ -73,7 +77,8 @@ module QA
path: name,
name: name,
description: description,
- visibility: 'public'
+ visibility: 'public',
+ initialize_with_readme: @initialize_with_readme
}
end
diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb
index cad89ebb0bb..e98880ce195 100644
--- a/qa/qa/resource/repository/project_push.rb
+++ b/qa/qa/resource/repository/project_push.rb
@@ -32,8 +32,8 @@ module QA
def fabricate!
super
- project.visit!
project.wait_for_push @commit_message if @wait_for_push
+ project.visit!
end
end
end
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index 58de01705d7..40a3bc85195 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -25,15 +25,12 @@ module QA
private
def create_personal_access_token
- if @is_new_session
- Runtime::Browser.visit(@address, Page::Main::Login) { do_create_personal_access_token }
- else
- do_create_personal_access_token
- end
+ Runtime::Browser.visit(@address, Page::Main::Login) if @is_new_session
+ do_create_personal_access_token
end
def do_create_personal_access_token
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Resource::PersonalAccessToken.fabricate!.access_token
end
end
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 174a52bd376..3bf4b3bbbfb 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -33,6 +33,7 @@ module QA
def self.visit(address, page = nil, &block)
new.visit(address, page, &block)
+ page.validate_elements_present!
end
def self.configure!
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 03cae3c1fe6..82510dfa03c 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -136,6 +136,10 @@ module QA
ENV['GITLAB_QA_PASSWORD_2']
end
+ def knapsack?
+ !!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN'])
+ end
+
def ldap_username
@ldap_username ||= ENV['GITLAB_LDAP_USERNAME']
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
index b1d641b507f..67610b62ed7 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
@@ -4,16 +4,14 @@ module QA
context 'Manage', :orchestrated, :mattermost do
describe 'Mattermost login' do
it 'user logs into Mattermost using GitLab OAuth' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login) do
- Page::Main::Login.act { sign_in_using_credentials }
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
- Runtime::Browser.visit(:mattermost, Page::Mattermost::Login) do
- Page::Mattermost::Login.act { sign_in_using_oauth }
+ Runtime::Browser.visit(:mattermost, Page::Mattermost::Login)
+ Page::Mattermost::Login.perform(&:sign_in_using_oauth)
- Page::Mattermost::Main.perform do |page|
- expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
- end
- end
+ Page::Mattermost::Main.perform do |page|
+ expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
index f97b0e56ca2..530fc684437 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/53
- context 'Plan', :quarantine do
+ context 'Plan' do
describe 'issue suggestions' do
let(:issue_title) { 'Issue Lists are awesome' }
@@ -10,12 +9,12 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- project = Resource::Project.fabricate! do |resource|
+ project = Resource::Project.fabricate_via_api! do |resource|
resource.name = 'project-for-issue-suggestions'
resource.description = 'project for issue suggestions'
end
- Resource::Issue.fabricate! do |issue|
+ Resource::Issue.fabricate_via_browser_ui! do |issue|
issue.title = issue_title
issue.project = project
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
index 10bba98f704..2750b171a85 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
@@ -17,14 +17,7 @@ module QA
@project = Resource::Project.fabricate! do |project|
project.name = 'file-template-project'
project.description = 'Add file templates via the Files view'
- end
-
- # There's no 'New File' dropdown when the project is blank, so we first
- # add a dummy file so that the dropdown will appear
- Resource::File.fabricate! do |file|
- file.project = @project
- file.name = 'README.md'
- file.content = '# Readme'
+ project.initialize_with_readme = true
end
Page::Main::Menu.perform(&:sign_out)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
index cf6c24fa873..37a784248d4 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
@@ -84,7 +84,7 @@ module QA
page.refresh
Page::Project::Branches::Show.perform do |branches_view|
- expect(branches_view).to have_no_branch(second_branch)
+ expect(branches_view).to have_no_branch(second_branch, reload: true)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
index 46346d1b984..d345fbfe995 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
@@ -12,7 +12,7 @@ module QA
file_content = 'QA Test - File content'
commit_message_for_create = 'QA Test - Create new file'
- Resource::File.fabricate! do |file|
+ Resource::File.fabricate_via_browser_ui! do |file|
file.name = file_name
file.content = file_content
file.commit_message = commit_message_for_create
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index a544efb35ee..5bfafdfa041 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -26,6 +26,8 @@ module QA
Page::Main::Login.perform(&:sign_in_using_credentials)
set_file_size_limit('')
+
+ Page::Main::Menu.perform(&:sign_out)
end
it 'push successful when the file size is under the limit' do
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
index b7400cdca97..680c5e21fa4 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
@@ -3,13 +3,16 @@
module QA
context 'Create' do
# failure reported: https://gitlab.com/gitlab-org/quality/nightly/issues/42
- # also failing in staging until the fix is picked into the next release:
- # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24533
describe 'Commit data', :quarantine do
before(:context) do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
+ # Get the user's details to confirm they're included in the email patch
+ @user = Resource::User.fabricate_via_api! do |user|
+ user.username = Runtime::User.username
+ end
+
project_push = Resource::Repository::ProjectPush.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is a test project'
@@ -21,12 +24,13 @@ module QA
# add second file to repo to enable diff from initial commit
@commit_message = 'Add second file'
- Page::Project::Show.perform(&:create_new_file!)
- Page::File::Form.perform do |f|
- f.add_name('second')
- f.add_content('second file content')
- f.add_commit_message(@commit_message)
- f.commit_changes
+ Resource::File.fabricate_via_api! do |file|
+ file.project = @project
+ file.name = 'second'
+ file.content = 'second file content'
+ file.commit_message = @commit_message
+ file.author_name = @user.name
+ file.author_email = @user.public_email
end
end
@@ -42,15 +46,11 @@ module QA
end
it 'user views raw email patch' do
- user = Resource::User.fabricate_via_api! do |user|
- user.username = Runtime::User.username
- end
-
view_commit
Page::Project::Commit::Show.perform(&:select_email_patches)
- expect(page).to have_content("From: #{user.name} <#{user.public_email}>")
+ expect(page).to have_content("From: #{@user.name} <#{@user.public_email}>")
expect(page).to have_content('Subject: [PATCH] Add second file')
expect(page).to have_content('diff --git a/second b/second')
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index 66cd712afb0..078d3b2b5b1 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -17,16 +17,7 @@ module QA
@project = Resource::Project.fabricate! do |project|
project.name = 'file-template-project'
project.description = 'Add file templates via the Web IDE'
- end
- @project.visit!
-
- # Add a file via the regular Files view because the Web IDE isn't
- # available unless there is a file present
- Page::Project::Show.perform(&:create_first_new_file!)
- Page::File::Form.perform do |page|
- page.add_name('dummy')
- page.add_content('Enable the Web IDE')
- page.commit_changes
+ project.initialize_with_readme = true
end
Page::Main::Menu.perform(&:sign_out)
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
index 561a8895329..b060f15168c 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
@@ -16,6 +16,7 @@ module QA
resource.project = project
resource.key = 'VARIABLE_KEY'
resource.value = 'some_CI_variable'
+ resource.masked = false
end
project.visit!
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 609155da855..2fe4e4d9d1f 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -60,6 +60,7 @@ module QA
resource.project = @project
resource.key = deploy_key_name
resource.value = key.private_key
+ resource.masked = false
end
gitlab_ci = <<~YAML
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 0971e551db1..86ba5e819ba 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -9,8 +9,7 @@ module QA
Page::Main::Login.perform(&:sign_in_using_credentials)
end
- # Transient failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/68
- describe 'Auto DevOps support', :orchestrated, :kubernetes, :quarantine do
+ describe 'Auto DevOps support', :orchestrated, :kubernetes do
context 'when rbac is enabled' do
before(:all) do
@cluster = Service::KubernetesCluster.new.create!
@@ -34,6 +33,7 @@ module QA
resource.project = @project
resource.key = 'CODE_QUALITY_DISABLED'
resource.value = '1'
+ resource.masked = false
end
# Set an application secret CI variable (prefixed with K8S_SECRET_)
@@ -41,6 +41,7 @@ module QA
resource.project = @project
resource.key = 'K8S_SECRET_OPTIONAL_MESSAGE'
resource.value = 'you_can_see_this_variable'
+ resource.masked = false
end
# Connect K8s cluster
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index 306913dafa6..f1cb9378de8 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require 'rspec/core'
+require 'rspec/expectations'
+require 'knapsack'
module QA
module Specs
@@ -32,10 +34,25 @@ module QA
end
args.push(options)
- args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} }
Runtime::Browser.configure!
+ if Runtime::Env.knapsack?
+ allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
+
+ QA::Runtime::Logger.info ''
+ QA::Runtime::Logger.info 'Report specs:'
+ QA::Runtime::Logger.info allocator.report_node_tests.join(', ')
+ QA::Runtime::Logger.info ''
+ QA::Runtime::Logger.info 'Leftover specs:'
+ QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ')
+ QA::Runtime::Logger.info ''
+
+ args.push(['--', allocator.node_tests])
+ else
+ args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} }
+ end
+
RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
abort if status.nonzero?
end
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
index ff505fdbddd..02ebd96ad49 100644
--- a/qa/qa/support/page/logging.rb
+++ b/qa/qa/support/page/logging.rb
@@ -33,11 +33,8 @@ module QA
exists
end
- def find_element(name, text: nil, wait: Capybara.default_max_wait_time)
- msg = ["finding :#{name}"]
- msg << %Q(with text "#{text}") if text
- msg << "(wait: #{wait})"
- log(msg.compact.join(' '))
+ def find_element(name, **kwargs)
+ log("finding :#{name} with args #{kwargs}")
element = super
@@ -56,8 +53,11 @@ module QA
elements
end
- def click_element(name)
- log("clicking :#{name}")
+ def click_element(name, page = nil)
+ msg = ["clicking :#{name}"]
+ msg << ", expecting to be at #{page.class}" if page
+
+ log(msg.compact.join(' '))
super
end
@@ -119,6 +119,12 @@ module QA
loaded
end
+ def wait_for_animated_element(name)
+ log("waiting for animated element: #{name}")
+
+ super
+ end
+
def within_element(name)
log("within element :#{name}")
diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb
index d5d6dff69da..f746fe06e40 100644
--- a/qa/spec/page/element_spec.rb
+++ b/qa/spec/page/element_spec.rb
@@ -50,4 +50,60 @@ describe QA::Page::Element do
expect(subject.matches?('some_name selector')).to be false
end
end
+
+ describe 'attributes' do
+ context 'element with no args' do
+ subject { described_class.new(:something) }
+
+ it 'defaults pattern to #selector' do
+ expect(subject.attributes[:pattern]).to eq 'qa-something'
+ expect(subject.attributes[:pattern]).to eq subject.selector
+ end
+
+ it 'is not required by default' do
+ expect(subject.required?).to be false
+ end
+ end
+
+ context 'element with a pattern' do
+ subject { described_class.new(:something, /link_to 'something'/) }
+
+ it 'has an attribute[pattern] of the pattern' do
+ expect(subject.attributes[:pattern]).to eq /link_to 'something'/
+ end
+
+ it 'is not required by default' do
+ expect(subject.required?).to be false
+ end
+ end
+
+ context 'element with requirement; no pattern' do
+ subject { described_class.new(:something, required: true) }
+
+ it 'has an attribute[pattern] of the selector' do
+ expect(subject.attributes[:pattern]).to eq 'qa-something'
+ expect(subject.attributes[:pattern]).to eq subject.selector
+ end
+
+ it 'is required' do
+ expect(subject.required?).to be true
+ end
+ end
+
+ context 'element with requirement and pattern' do
+ subject { described_class.new(:something, /link_to 'something_else_entirely'/, required: true) }
+
+ it 'has an attribute[pattern] of the passed pattern' do
+ expect(subject.attributes[:pattern]).to eq /link_to 'something_else_entirely'/
+ end
+
+ it 'is required' do
+ expect(subject.required?).to be true
+ end
+
+ it 'has a selector of the name' do
+ expect(subject.selector).to eq 'qa-something'
+ end
+ end
+ end
end
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
index 99e96b81a51..092c6a17c9c 100644
--- a/qa/spec/page/logging_spec.rb
+++ b/qa/spec/page/logging_spec.rb
@@ -64,11 +64,21 @@ describe QA::Support::Page::Logging do
it 'logs find_element with text' do
expect { subject.find_element(:element, text: 'foo') }
- .to output(/finding :element with text "foo"/).to_stdout_from_any_process
+ .to output(/finding :element with args {:text=>"foo"}/).to_stdout_from_any_process
expect { subject.find_element(:element, text: 'foo') }
.to output(/found :element/).to_stdout_from_any_process
end
+ it 'logs find_element with wait' do
+ expect { subject.find_element(:element, wait: 0) }
+ .to output(/finding :element with args {:wait=>0}/).to_stdout_from_any_process
+ end
+
+ it 'logs find_element with class' do
+ expect { subject.find_element(:element, class: 'active') }
+ .to output(/finding :element with args {:class=>\"active\"}/).to_stdout_from_any_process
+ end
+
it 'logs click_element' do
expect { subject.click_element(:element) }
.to output(/clicking :element/).to_stdout_from_any_process
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index 04085efe2ce..2560695ef2e 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -168,6 +168,30 @@ describe QA::Runtime::Env do
end
end
+ describe '.knapsack?' do
+ it 'returns true if KNAPSACK_GENERATE_REPORT is defined' do
+ stub_env('KNAPSACK_GENERATE_REPORT', 'true')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_REPORT_PATH is defined' do
+ stub_env('KNAPSACK_REPORT_PATH', '/a/path')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_TEST_FILE_PATTERN is defined' do
+ stub_env('KNAPSACK_TEST_FILE_PATTERN', '/a/**/pattern')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns false if neither KNAPSACK_GENERATE_REPORT nor KNAPSACK_REPORT_PATH nor KNAPSACK_TEST_FILE_PATTERN are defined' do
+ expect(described_class.knapsack?).to be_falsey
+ end
+ end
+
describe '.require_github_access_token!' do
it 'raises ArgumentError if GITHUB_ACCESS_TOKEN is not defined' do
stub_env('GITHUB_ACCESS_TOKEN', nil)
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index a368ffba711..f25dbf3a8ab 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -3,6 +3,11 @@
require_relative '../qa'
require 'rspec/retry'
+if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK']
+ require 'knapsack'
+ Knapsack::Adapters::RSpecAdapter.bind
+end
+
%w[helpers shared_examples].each do |d|
Dir[::File.join(__dir__, d, '**', '*.rb')].each { |f| require f }
end
diff --git a/rubocop/cop/qa/element_with_pattern.rb b/rubocop/cop/qa/element_with_pattern.rb
index 9d80946f1ba..d14eeaaeaf3 100644
--- a/rubocop/cop/qa/element_with_pattern.rb
+++ b/rubocop/cop/qa/element_with_pattern.rb
@@ -1,18 +1,21 @@
+# frozen_string_literal: true
+
require_relative '../../qa_helpers'
module RuboCop
module Cop
module QA
- # This cop checks for the usage of factories in migration specs
+ # This cop checks for the usage of patterns in QA elements
#
# @example
#
# # bad
- # let(:user) { create(:user) }
+ # element :some_element, "link_to 'something'"
+ # element :some_element, /link_to 'something'/
#
# # good
- # let(:users) { table(:users) }
- # let(:user) { users.create!(name: 'User 1', username: 'user1') }
+ # element :some_element
+ # element :some_element, required: true
class ElementWithPattern < RuboCop::Cop::Cop
include QAHelpers
@@ -22,10 +25,13 @@ module RuboCop
return unless in_qa_file?(node)
return unless method_name(node).to_s == 'element'
- element_name, pattern = node.arguments
- return unless pattern
+ element_name, *args = node.arguments
+
+ return if args.first.nil?
- add_offense(node, location: pattern.source_range, message: MESSAGE % "qa-#{element_name.value.to_s.tr('_', '-')}")
+ args.first.each_node(:str) do |arg|
+ add_offense(arg, message: MESSAGE % "qa-#{element_name.value.to_s.tr('_', '-')}")
+ end
end
private
diff --git a/scripts/frontend/test.js b/scripts/frontend/test.js
new file mode 100755
index 00000000000..dab7176f8c1
--- /dev/null
+++ b/scripts/frontend/test.js
@@ -0,0 +1,114 @@
+#!/usr/bin/env node
+
+const { spawn } = require('child_process');
+const { EOL } = require('os');
+const program = require('commander');
+const chalk = require('chalk');
+
+const JEST_ROUTE = 'spec/frontend';
+const KARMA_ROUTE = 'spec/javascripts';
+const COMMON_ARGS = ['--colors'];
+const JEST_ARGS = ['--passWithNoTests'];
+const KARMA_ARGS = ['--no-fail-on-empty-test-suite'];
+const SUCCESS_CODE = 0;
+
+program
+ .version('0.1.0')
+ .usage('[options] <file ...>')
+ .option('-p, --parallel', 'Run tests suites in parallel')
+ .parse(process.argv);
+
+const isSuccess = code => code === SUCCESS_CODE;
+
+const combineExitCodes = codes => {
+ const firstFail = codes.find(x => !isSuccess(x));
+
+ return firstFail === undefined ? SUCCESS_CODE : firstFail;
+};
+
+const skipIfFail = fn => code => (isSuccess(code) ? fn() : code);
+
+const endWithEOL = str => (str[str.length - 1] === '\n' ? str : `${str}${EOL}`);
+
+const runTests = paths => {
+ if (program.parallel) {
+ return Promise.all([runJest(paths), runKarma(paths)]).then(combineExitCodes);
+ } else {
+ return runJest(paths).then(skipIfFail(() => runKarma(paths)));
+ }
+};
+
+const spawnYarnScript = (cmd, args) => {
+ return new Promise((resolve, reject) => {
+ const proc = spawn('yarn', ['run', cmd, ...args]);
+ const output = data => {
+ const text = data
+ .toString()
+ .split(/\r?\n/g)
+ .map((line, idx, { length }) =>
+ idx === length - 1 && !line ? line : `${chalk.gray(cmd)}: ${line}`,
+ )
+ .join(EOL);
+
+ return endWithEOL(text);
+ };
+
+ proc.stdout.on('data', data => {
+ process.stdout.write(output(data));
+ });
+
+ proc.stderr.on('data', data => {
+ process.stderr.write(output(data));
+ });
+
+ proc.on('close', code => {
+ process.stdout.write(output(`exited with code ${code}`));
+
+ // We resolve even on a failure code because a `reject` would cause
+ // Promise.all to reject immediately (without waiting for other promises)
+ // to finish.
+ resolve(code);
+ });
+ });
+};
+
+const runJest = args => {
+ return spawnYarnScript('jest', [...JEST_ARGS, ...COMMON_ARGS, ...toJestArgs(args)]);
+};
+
+const runKarma = args => {
+ return spawnYarnScript('karma', [...KARMA_ARGS, ...COMMON_ARGS, ...toKarmaArgs(args)]);
+};
+
+const replacePath = to => path =>
+ path
+ .replace(JEST_ROUTE, to)
+ .replace(KARMA_ROUTE, to)
+ .replace('app/assets/javascripts', to);
+
+const replacePathForJest = replacePath(JEST_ROUTE);
+
+const replacePathForKarma = replacePath(KARMA_ROUTE);
+
+const toJestArgs = paths => paths.map(replacePathForJest);
+
+const toKarmaArgs = paths =>
+ paths.reduce((acc, path) => acc.concat('-f', replacePathForKarma(path)), []);
+
+const main = paths => {
+ runTests(paths).then(code => {
+ console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
+ if (isSuccess(code)) {
+ console.log(chalk.bgGreen(chalk.black('All tests passed :)')));
+ } else {
+ console.log(chalk.bgRed(chalk.white(`Some tests failed :(`)));
+ }
+ console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
+
+ if (!isSuccess(code)) {
+ process.exit(code);
+ }
+ });
+};
+
+main(program.args);
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 58b74f2f07d..9b0d5d4f719 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -5,6 +5,7 @@ export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
export BUNDLE_INSTALL_FLAGS="--without=production --jobs=$(nproc) --path=vendor --retry=3 --quiet"
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
+ bundle --version
bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
fi
@@ -16,12 +17,10 @@ cp config/gitlab.yml.example config/gitlab.yml
sed -i 's/bin_path: \/usr\/bin\/git/bin_path: \/usr\/local\/bin\/git/' config/gitlab.yml
# Determine the database by looking at the job name.
-# For example, we'll get pg if the job is `rspec-pg 19 20`
-export GITLAB_DATABASE=$(echo $CI_JOB_NAME | cut -f1 -d' ' | cut -f2 -d-)
-
-# This would make the default database postgresql, and we could also use
-# pg to mean postgresql.
-if [ "$GITLAB_DATABASE" != 'mysql' ]; then
+# This would make the default database postgresql.
+if [[ "${CI_JOB_NAME#*mysql}" != "$CI_JOB_NAME" ]]; then
+ export GITLAB_DATABASE='mysql'
+else
export GITLAB_DATABASE='postgresql'
fi
diff --git a/spec/controllers/acme_challenges_controller_spec.rb b/spec/controllers/acme_challenges_controller_spec.rb
new file mode 100644
index 00000000000..cee06bed27b
--- /dev/null
+++ b/spec/controllers/acme_challenges_controller_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AcmeChallengesController do
+ describe '#show' do
+ let!(:acme_order) { create(:pages_domain_acme_order) }
+
+ def make_request(domain, token)
+ get(:show, params: { domain: domain, token: token })
+ end
+
+ before do
+ make_request(domain, token)
+ end
+
+ context 'with right domain and token' do
+ let(:domain) { acme_order.pages_domain.domain }
+ let(:token) { acme_order.challenge_token }
+
+ it 'renders acme challenge file content' do
+ expect(response.body).to eq(acme_order.challenge_file_content)
+ end
+ end
+
+ context 'when domain is invalid' do
+ let(:domain) { 'somewrongdomain.com' }
+ let(:token) { acme_order.challenge_token }
+
+ it 'renders not found' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when token is invalid' do
+ let(:domain) { acme_order.pages_domain.domain }
+ let(:token) { 'wrongtoken' }
+
+ it 'renders not found' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 5ecd1b6b7c8..40669ec5451 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -691,4 +691,38 @@ describe ApplicationController do
end
end
end
+
+ context 'Gitlab::Session' do
+ controller(described_class) do
+ prepend_before_action do
+ authenticate_sessionless_user!(:rss)
+ end
+
+ def index
+ if Gitlab::Session.current
+ head :created
+ else
+ head :not_found
+ end
+ end
+ end
+
+ it 'is set on web requests' do
+ sign_in(user)
+
+ get :index
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ context 'with sessionless user' do
+ it 'is not set' do
+ personal_access_token = create(:personal_access_token, user: user)
+
+ get :index, format: :atom, params: { private_token: personal_access_token.token }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
diff --git a/spec/controllers/concerns/import_url_params_spec.rb b/spec/controllers/concerns/import_url_params_spec.rb
new file mode 100644
index 00000000000..adbe6e5d3bf
--- /dev/null
+++ b/spec/controllers/concerns/import_url_params_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ImportUrlParams do
+ let(:import_url_params) do
+ controller = OpenStruct.new(params: params).extend(described_class)
+ controller.import_url_params
+ end
+
+ context 'empty URL' do
+ let(:params) do
+ ActionController::Parameters.new(project: {
+ title: 'Test'
+ })
+ end
+
+ it 'returns empty hash' do
+ expect(import_url_params).to eq({})
+ end
+ end
+
+ context 'url and password separately provided' do
+ let(:params) do
+ ActionController::Parameters.new(project: {
+ import_url: 'https://url.com',
+ import_url_user: 'user', import_url_password: 'password'
+ })
+ end
+
+ describe '#import_url_params' do
+ it 'returns hash with import_url' do
+ expect(import_url_params).to eq(
+ import_url: "https://user:password@url.com"
+ )
+ end
+ end
+ end
+
+ context 'url with provided empty credentials' do
+ let(:params) do
+ ActionController::Parameters.new(project: {
+ import_url: 'https://user:password@url.com',
+ import_url_user: '', import_url_password: ''
+ })
+ end
+
+ describe '#import_url_params' do
+ it 'does not change the url' do
+ expect(import_url_params).to eq(
+ import_url: "https://user:password@url.com"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 1cd08200552..47d7e278183 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -141,6 +141,28 @@ describe GroupsController do
end
describe 'POST #create' do
+ it 'allows creating a group' do
+ sign_in(user)
+
+ expect do
+ post :create, params: { group: { name: 'new_group', path: "new_group" } }
+ end.to change { Group.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+
+ context 'authorization' do
+ it 'allows an admin to create a group' do
+ sign_in(create(:admin))
+
+ expect do
+ post :create, params: { group: { name: 'new_group', path: "new_group" } }
+ end.to change { Group.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
context 'when creating subgroups', :nested_groups do
[true, false].each do |can_create_group_status|
context "and can_create_group is #{can_create_group_status}" do
diff --git a/spec/controllers/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb
index f1e0923f316..f7c813576aa 100644
--- a/spec/controllers/import/fogbugz_controller_spec.rb
+++ b/spec/controllers/import/fogbugz_controller_spec.rb
@@ -11,6 +11,44 @@ describe Import::FogbugzController do
sign_in(user)
end
+ describe 'POST #callback' do
+ let(:token) { FFaker::Lorem.characters(8) }
+ let(:uri) { 'https://example.com' }
+ let(:xml_response) { %Q(<?xml version=\"1.0\" encoding=\"UTF-8\"?><response><token><![CDATA[#{token}]]></token></response>) }
+
+ it 'attempts to contact Fogbugz server' do
+ stub_request(:post, "https://example.com/api.asp").to_return(status: 200, body: xml_response, headers: {})
+
+ post :callback, params: { uri: uri, email: 'test@example.com', password: 'mypassword' }
+
+ expect(session[:fogbugz_token]).to eq(token)
+ expect(session[:fogbugz_uri]).to eq(uri)
+ expect(response).to redirect_to(new_user_map_import_fogbugz_path)
+ end
+ end
+
+ describe 'POST #create_user_map' do
+ let(:user_map) do
+ {
+ "2" => {
+ "name" => "Test User",
+ "email" => "testuser@example.com",
+ "gitlab_user" => "3"
+ }
+ }
+ end
+
+ it 'stores the user map in the session' do
+ client = double(user_map: {})
+ expect(controller).to receive(:client).and_return(client)
+
+ post :create_user_map, params: { users: user_map }
+
+ expect(session[:fogbugz_user_map]).to eq(user_map)
+ expect(response).to redirect_to(status_import_fogbugz_path)
+ end
+ end
+
describe 'GET status' do
before do
@repo = OpenStruct.new(name: 'vim')
diff --git a/spec/controllers/import/phabricator_controller_spec.rb b/spec/controllers/import/phabricator_controller_spec.rb
new file mode 100644
index 00000000000..85085a8e996
--- /dev/null
+++ b/spec/controllers/import/phabricator_controller_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Import::PhabricatorController do
+ let(:current_user) { create(:user) }
+
+ before do
+ sign_in current_user
+ end
+
+ describe 'GET #new' do
+ subject { get :new }
+
+ context 'when the import source is not available' do
+ before do
+ stub_feature_flags(phabricator_import: true)
+ stub_application_setting(import_sources: [])
+ end
+
+ it { is_expected.to have_gitlab_http_status(404) }
+ end
+
+ context 'when the feature is disabled' do
+ before do
+ stub_feature_flags(phabricator_import: false)
+ stub_application_setting(import_sources: ['phabricator'])
+ end
+
+ it { is_expected.to have_gitlab_http_status(404) }
+ end
+
+ context 'when the import is available' do
+ before do
+ stub_feature_flags(phabricator_import: true)
+ stub_application_setting(import_sources: ['phabricator'])
+ end
+
+ it { is_expected.to have_gitlab_http_status(200) }
+ end
+ end
+
+ describe 'POST #create' do
+ subject(:post_create) { post :create, params: params }
+
+ context 'with valid params' do
+ let(:params) do
+ { path: 'phab-import',
+ name: 'Phab import',
+ phabricator_server_url: 'https://phabricator.example.com',
+ api_token: 'hazaah',
+ namespace_id: current_user.namespace_id }
+ end
+
+ it 'creates a project to import' do
+ expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer|
+ expect(importer).to receive(:execute)
+ end
+
+ expect { post_create }.to change { current_user.namespace.projects.reload.size }.from(0).to(1)
+
+ expect(current_user.namespace.projects.last).to be_import
+ end
+ end
+
+ context 'when an import param is missing' do
+ let(:params) do
+ { path: 'phab-import',
+ name: 'Phab import',
+ phabricator_server_url: nil,
+ api_token: 'hazaah',
+ namespace_id: current_user.namespace_id }
+ end
+
+ it 'does not create the project' do
+ expect { post_create }.not_to change { current_user.namespace.projects.reload.size }
+ end
+ end
+
+ context 'when a project param is missing' do
+ let(:params) do
+ { phabricator_server_url: 'https://phabricator.example.com',
+ api_token: 'hazaah',
+ namespace_id: current_user.namespace_id }
+ end
+
+ it 'does not create the project' do
+ expect { post_create }.not_to change { current_user.namespace.projects.reload.size }
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index de1b9dc0bf3..d463619ad0b 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -39,7 +39,7 @@ describe Projects::AvatarsController do
end
context 'when the avatar is stored in lfs' do
- it_behaves_like 'repository lfs file load' do
+ it_behaves_like 'a controller that can serve LFS files' do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "files/lfs/#{filename}" }
end
diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb
index 323a32575af..cc6ac83ca38 100644
--- a/spec/controllers/projects/ci/lints_controller_spec.rb
+++ b/spec/controllers/projects/ci/lints_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Projects::Ci::LintsController do
+ include StubRequests
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
@@ -70,7 +72,7 @@ describe Projects::Ci::LintsController do
context 'with a valid gitlab-ci.yml' do
before do
- WebMock.stub_request(:get, remote_file_path).to_return(body: remote_file_content)
+ stub_full_request(remote_file_path).to_return(body: remote_file_content)
project.add_developer(user)
post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
index d232408b775..fdef9bc5638 100644
--- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
+++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
@@ -85,12 +85,12 @@ describe Projects::Environments::PrometheusApiController do
context 'with nil result' do
let(:service_result) { nil }
- it 'returns 202 accepted' do
+ it 'returns 204 no_content' do
get :proxy, params: environment_params
expect(json_response['status']).to eq('processing')
expect(json_response['message']).to eq('Not ready yet. Try again later.')
- expect(response).to have_gitlab_http_status(:accepted)
+ expect(response).to have_gitlab_http_status(:no_content)
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index d5eea5b0439..9699f2952f2 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -433,20 +433,6 @@ describe Projects::EnvironmentsController do
end
context 'when only one time param is provided' do
- context 'when :metrics_time_window feature flag is disabled' do
- before do
- stub_feature_flags(metrics_time_window: false)
- expect(environment).to receive(:additional_metrics).with(no_args).and_return(nil)
- end
-
- it 'returns a time-window agnostic response' do
- additional_metrics(start: '1552647300.651094')
-
- expect(response).to have_gitlab_http_status(204)
- expect(json_response).to eq({})
- end
- end
-
it 'raises an error when start is missing' do
expect { additional_metrics(end: '1552647300.651094') }
.to raise_error(ActionController::ParameterMissing)
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 8d88ee7dfd6..bdc81efe3bc 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -122,4 +122,19 @@ describe Projects::ImportsController do
end
end
end
+
+ describe 'POST #create' do
+ let(:params) { { import_url: 'https://github.com/vim/vim.git', import_url_user: 'user', import_url_password: 'password' } }
+ let(:project) { create(:project) }
+
+ before do
+ allow(RepositoryImportWorker).to receive(:perform_async)
+
+ post :create, params: { project: params, namespace_id: project.namespace.to_param, project_id: project }
+ end
+
+ it 'sets import_url to the project' do
+ expect(project.reload.import_url).to eq('https://user:password@github.com/vim/vim.git')
+ end
+ end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 0c46b43f080..32607fc5f56 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -130,6 +130,37 @@ describe Projects::IssuesController do
end
end
+ context 'with relative_position sorting' do
+ let!(:issue_list) { create_list(:issue, 2, project: project) }
+
+ before do
+ sign_in(user)
+ project.add_developer(user)
+ allow(Kaminari.config).to receive(:default_per_page).and_return(1)
+ end
+
+ it 'overrides the number allowed on the page' do
+ get :index,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ sort: 'relative_position'
+ }
+
+ expect(assigns(:issues).count).to eq 2
+ end
+
+ it 'allows the default number on the page' do
+ get :index,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }
+
+ expect(assigns(:issues).count).to eq 1
+ end
+ end
+
context 'external authorization' do
before do
sign_in user
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index bd30d4ee88b..490e9841492 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -101,7 +101,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
- describe 'GET show' do
+ describe 'GET show', :request_store do
let!(:job) { create(:ci_build, :failed, pipeline: pipeline) }
let!(:second_job) { create(:ci_build, :failed, pipeline: pipeline) }
let!(:third_job) { create(:ci_build, :failed) }
@@ -143,13 +143,24 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project.add_developer(user)
sign_in(user)
- allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+ allow_any_instance_of(Ci::Build)
+ .to receive(:merge_request)
+ .and_return(merge_request)
+ end
+
+ it 'does not serialize builds in exposed stages' do
+ get_show_json
- get_show(id: job.id, format: :json)
+ json_response.dig('pipeline', 'details', 'stages').tap do |stages|
+ expect(stages.map(&:keys).flatten)
+ .to eq %w[name title status path dropdown_path]
+ end
end
context 'when job failed' do
it 'exposes needed information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z})
@@ -159,6 +170,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
context 'when job is running' do
+ before do
+ get_show_json
+ end
+
context 'job is cancelable' do
let(:job) { create(:ci_build, :running, pipeline: pipeline) }
@@ -181,6 +196,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
context 'when job has artifacts' do
+ before do
+ get_show_json
+ end
+
context 'with not expiry date' do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
@@ -212,6 +231,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
it 'exposes empty state illustrations' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['status']['illustration']).to have_key('image')
@@ -224,6 +245,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :success, pipeline: pipeline) }
it 'does not exposes the deployment information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['deployment_status']).to be_nil
end
@@ -234,11 +257,20 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:environment) { create(:environment, project: project, name: 'staging', state: :available) }
let(:job) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) }
+ before do
+ create(:deployment, :success, environment: environment, project: project)
+ end
+
it 'exposes the deployment information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to match_schema('job/job_details')
- expect(json_response['deployment_status']["status"]).to eq 'creating'
- expect(json_response['deployment_status']["environment"]).not_to be_nil
+ expect(json_response.dig('deployment_status', 'status')).to eq 'creating'
+ expect(json_response.dig('deployment_status', 'environment')).not_to be_nil
+ expect(json_response.dig('deployment_status', 'environment', 'last_deployment')).not_to be_nil
+ expect(json_response.dig('deployment_status', 'environment', 'last_deployment'))
+ .not_to include('commit')
end
end
@@ -250,11 +282,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
sign_in(user)
-
- get_show(id: job.id, format: :json)
end
it 'user can edit runner' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runner']).to have_key('edit_path')
@@ -270,11 +302,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
sign_in(user)
-
- get_show(id: job.id, format: :json)
end
it 'user can not edit runner' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runner']).not_to have_key('edit_path')
@@ -289,11 +321,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
sign_in(user)
-
- get_show(id: job.id, format: :json)
end
it 'user can not edit runner' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runner']).not_to have_key('edit_path')
@@ -306,6 +338,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) }
it 'exposes needed information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false
@@ -319,6 +353,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :pending, pipeline: pipeline, runner: runner) }
it 'exposes needed information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['runners']['online']).to be false
@@ -328,6 +364,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
context 'settings_path' do
+ before do
+ get_show_json
+ end
+
context 'when user is developer' do
it 'settings_path is not available' do
expect(response).to have_gitlab_http_status(:ok)
@@ -354,6 +394,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when no trace is available' do
it 'has_trace is false' do
+ get_show_json
+
expect(response).to match_response_schema('job/job_details')
expect(json_response['has_trace']).to be false
end
@@ -363,17 +405,21 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :running, :trace_live, pipeline: pipeline) }
it "has_trace is true" do
+ get_show_json
+
expect(response).to match_response_schema('job/job_details')
expect(json_response['has_trace']).to be true
end
end
it 'exposes the stage the job belongs to' do
+ get_show_json
+
expect(json_response['stage']).to eq('test')
end
end
- context 'when requesting JSON job is triggered' do
+ context 'when requesting triggered job JSON' do
let!(:merge_request) { create(:merge_request, source_project: project) }
let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
@@ -383,15 +429,15 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project.add_developer(user)
sign_in(user)
- allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+ allow_any_instance_of(Ci::Build)
+ .to receive(:merge_request)
+ .and_return(merge_request)
end
context 'with no variables' do
- before do
- get_show(id: job.id, format: :json)
- end
-
it 'exposes trigger information' do
+ get_show_json
+
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['trigger']['short_token']).to eq 'toke'
@@ -408,7 +454,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
project.add_maintainer(user)
- get_show(id: job.id, format: :json)
+ get_show_json
end
it 'returns a job_detail' do
@@ -432,7 +478,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'user is not a mantainer' do
before do
- get_show(id: job.id, format: :json)
+ get_show_json
end
it 'returns a job_detail' do
@@ -456,6 +502,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
+ def get_show_json
+ expect { get_show(id: job.id, format: :json) }
+ .not_to change { Gitlab::GitalyClient.get_request_count }
+ end
+
def get_show(**extra_params)
params = {
namespace_id: project.namespace.to_param,
@@ -790,8 +841,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
it 'erases artifacts' do
- expect(job.artifacts_file.exists?).to be_falsey
- expect(job.artifacts_metadata.exists?).to be_falsey
+ expect(job.artifacts_file.present?).to be_falsey
+ expect(job.artifacts_metadata.present?).to be_falsey
end
it 'erases trace' do
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index f4a18dcba51..34cbf0c8723 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -412,7 +412,7 @@ describe Projects::MergeRequestsController do
end
end
- context 'when the pipeline succeeds is passed' do
+ context 'when merge when pipeline succeeds option is passed' do
let!(:head_pipeline) do
create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch, head_pipeline_of: merge_request)
end
@@ -429,8 +429,9 @@ describe Projects::MergeRequestsController do
it 'sets the MR to merge when the pipeline succeeds' do
service = double(:merge_when_pipeline_succeeds_service)
+ allow(service).to receive(:available_for?) { true }
- expect(MergeRequests::MergeWhenPipelineSucceedsService)
+ expect(AutoMerge::MergeWhenPipelineSucceedsService)
.to receive(:new).with(project, anything, anything)
.and_return(service)
expect(service).to receive(:execute).with(merge_request)
@@ -461,6 +462,30 @@ describe Projects::MergeRequestsController do
expect(json_response).to eq('status' => 'merge_when_pipeline_succeeds')
end
end
+
+ context 'when auto merge has not been enabled yet' do
+ it 'calls AutoMergeService#execute' do
+ expect_next_instance_of(AutoMergeService) do |service|
+ expect(service).to receive(:execute).with(merge_request, 'merge_when_pipeline_succeeds')
+ end
+
+ merge_when_pipeline_succeeds
+ end
+ end
+
+ context 'when auto merge has already been enabled' do
+ before do
+ merge_request.update!(auto_merge_enabled: true, merge_user: user)
+ end
+
+ it 'calls AutoMergeService#update' do
+ expect_next_instance_of(AutoMergeService) do |service|
+ expect(service).to receive(:update).with(merge_request)
+ end
+
+ merge_when_pipeline_succeeds
+ end
+ end
end
describe 'only_allow_merge_if_all_discussions_are_resolved? setting' do
@@ -713,9 +738,9 @@ describe Projects::MergeRequestsController do
end
end
- describe 'POST cancel_merge_when_pipeline_succeeds' do
+ describe 'POST cancel_auto_merge' do
subject do
- post :cancel_merge_when_pipeline_succeeds,
+ post :cancel_auto_merge,
params: {
format: :json,
namespace_id: merge_request.project.namespace.to_param,
@@ -725,14 +750,15 @@ describe Projects::MergeRequestsController do
xhr: true
end
- it 'calls MergeRequests::MergeWhenPipelineSucceedsService' do
- mwps_service = double
+ it 'calls AutoMergeService' do
+ auto_merge_service = double
- allow(MergeRequests::MergeWhenPipelineSucceedsService)
+ allow(AutoMergeService)
.to receive(:new)
- .and_return(mwps_service)
+ .and_return(auto_merge_service)
- expect(mwps_service).to receive(:cancel).with(merge_request)
+ allow(auto_merge_service).to receive(:available_strategies).with(merge_request)
+ expect(auto_merge_service).to receive(:cancel).with(merge_request)
subject
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index f8470a94f98..767cee7d54a 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -175,6 +175,40 @@ describe Projects::MilestonesController do
end
end
+ describe '#labels' do
+ render_views
+
+ context 'as json' do
+ let!(:guest) { create(:user, username: 'guest1') }
+ let!(:group) { create(:group, :public) }
+ let!(:project) { create(:project, :public, group: group) }
+ let!(:label) { create(:label, title: 'test_label_on_private_issue', project: project) }
+ let!(:confidential_issue) { create(:labeled_issue, confidential: true, project: project, milestone: milestone, labels: [label]) }
+
+ it 'does not render labels of private issues if user has no access' do
+ sign_in(guest)
+
+ get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.content_type).to eq 'application/json'
+
+ expect(json_response['html']).not_to include(label.title)
+ end
+
+ it 'does render labels of private issues if user has access' do
+ sign_in(user)
+
+ get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.content_type).to eq 'application/json'
+
+ expect(json_response['html']).to include(label.title)
+ end
+ end
+ end
+
context 'promotion succeeds' do
before do
group.add_developer(user)
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 3a89d8ce032..97acd47b4da 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -42,7 +42,7 @@ describe Projects::RawController do
end
end
- it_behaves_like 'repository lfs file load' do
+ it_behaves_like 'a controller that can serve LFS files' do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "be93687/files/lfs/#{filename}" }
end
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
index 782f5f272d9..18c594acae0 100644
--- a/spec/controllers/projects/serverless/functions_controller_spec.rb
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -8,9 +8,8 @@ describe Projects::Serverless::FunctionsController do
let(:user) { create(:user) }
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
let(:service) { cluster.platform_kubernetes }
- let(:project) { cluster.project}
+ let(:project) { cluster.project }
let(:namespace) do
create(:cluster_kubernetes_namespace,
@@ -30,17 +29,69 @@ describe Projects::Serverless::FunctionsController do
end
describe 'GET #index' do
- context 'empty cache' do
- it 'has no data' do
+ let(:expected_json) { { 'knative_installed' => knative_state, 'functions' => functions } }
+
+ context 'when cache is being read' do
+ let(:knative_state) { 'checking' }
+ let(:functions) { [] }
+
+ before do
get :index, params: params({ format: :json })
+ end
- expect(response).to have_gitlab_http_status(204)
+ it 'returns checking' do
+ expect(json_response).to eq expected_json
end
- it 'renders an html page' do
- get :index, params: params
+ it { expect(response).to have_gitlab_http_status(200) }
+ end
+
+ context 'when cache is ready' do
+ let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+ let(:knative_state) { true }
- expect(response).to have_gitlab_http_status(200)
+ before do
+ allow_any_instance_of(Clusters::Cluster)
+ .to receive(:knative_services_finder)
+ .and_return(knative_services_finder)
+ synchronous_reactive_cache(knative_services_finder)
+ stub_kubeclient_service_pods(
+ kube_response({ "kind" => "PodList", "items" => [] }),
+ namespace: namespace.namespace
+ )
+ end
+
+ context 'when no functions were found' do
+ let(:functions) { [] }
+
+ before do
+ stub_kubeclient_knative_services(
+ namespace: namespace.namespace,
+ response: kube_response({ "kind" => "ServiceList", "items" => [] })
+ )
+ get :index, params: params({ format: :json })
+ end
+
+ it 'returns checking' do
+ expect(json_response).to eq expected_json
+ end
+
+ it { expect(response).to have_gitlab_http_status(200) }
+ end
+
+ context 'when functions were found' do
+ let(:functions) { ["asdf"] }
+
+ before do
+ stub_kubeclient_knative_services(namespace: namespace.namespace)
+ get :index, params: params({ format: :json })
+ end
+
+ it 'returns functions' do
+ expect(json_response["functions"]).not_to be_empty
+ end
+
+ it { expect(response).to have_gitlab_http_status(200) }
end
end
end
@@ -56,11 +107,12 @@ describe Projects::Serverless::FunctionsController do
context 'valid data', :use_clean_rails_memory_store_caching do
before do
stub_kubeclient_service_pods
- stub_reactive_cache(knative,
+ stub_reactive_cache(cluster.knative_services_finder(project),
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- })
+ },
+ *cluster.knative_services_finder(project).cache_args)
end
it 'has a valid function name' do
@@ -88,11 +140,12 @@ describe Projects::Serverless::FunctionsController do
describe 'GET #index with data', :use_clean_rails_memory_store_caching do
before do
stub_kubeclient_service_pods
- stub_reactive_cache(knative,
+ stub_reactive_cache(cluster.knative_services_finder(project),
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- })
+ },
+ *cluster.knative_services_finder(project).cache_args)
end
it 'has data' do
@@ -100,11 +153,16 @@ describe Projects::Serverless::FunctionsController do
expect(response).to have_gitlab_http_status(200)
- expect(json_response).to contain_exactly(
- a_hash_including(
- "name" => project.name,
- "url" => "http://#{project.name}.#{namespace.namespace}.example.com"
- )
+ expect(json_response).to match(
+ {
+ "knative_installed" => "checking",
+ "functions" => [
+ a_hash_including(
+ "name" => project.name,
+ "url" => "http://#{project.name}.#{namespace.namespace}.example.com"
+ )
+ ]
+ }
)
end
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index db53e5bc8a4..117b9cf7915 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -109,7 +109,7 @@ describe Projects::Settings::CiCdController do
end
context 'when updating the auto_devops settings' do
- let(:params) { { auto_devops_attributes: { enabled: '', domain: 'mepmep.md' } } }
+ let(:params) { { auto_devops_attributes: { enabled: '' } } }
context 'following the instance default' do
let(:params) { { auto_devops_attributes: { enabled: '' } } }
@@ -200,6 +200,21 @@ describe Projects::Settings::CiCdController do
expect(response).to redirect_to(namespace_project_settings_ci_cd_path)
end
end
+
+ context 'when default_git_depth is not specified' do
+ let(:params) { { ci_cd_settings_attributes: { default_git_depth: 10 } } }
+
+ before do
+ project.ci_cd_settings.update!(default_git_depth: nil)
+ end
+
+ it 'set specified git depth' do
+ subject
+
+ project.reload
+ expect(project.default_git_depth).to eq(10)
+ end
+ end
end
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 7f1bbebd128..8d2412f97ef 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -292,6 +292,18 @@ describe ProjectsController do
end
describe 'GET edit' do
+ it 'allows an admin user to access the page' do
+ sign_in(create(:user, :admin))
+
+ get :edit,
+ params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it 'sets the badge API endpoint' do
sign_in(user)
project.add_maintainer(user)
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 2b9df71aa3a..89857a9d21b 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -4,15 +4,31 @@ require 'rails_helper'
describe SentNotificationsController do
let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:sent_notification) { create(:sent_notification, project: project, noteable: issue, recipient: user) }
+ let(:project) { create(:project, :public) }
+ let(:private_project) { create(:project, :private) }
+ let(:sent_notification) { create(:sent_notification, project: target_project, noteable: noteable, recipient: user) }
let(:issue) do
- create(:issue, project: project, author: user) do |issue|
- issue.subscriptions.create(user: user, project: project, subscribed: true)
+ create(:issue, project: target_project) do |issue|
+ issue.subscriptions.create(user: user, project: target_project, subscribed: true)
end
end
+ let(:confidential_issue) do
+ create(:issue, project: target_project, confidential: true) do |issue|
+ issue.subscriptions.create(user: user, project: target_project, subscribed: true)
+ end
+ end
+
+ let(:merge_request) do
+ create(:merge_request, source_project: target_project, target_project: target_project) do |mr|
+ mr.subscriptions.create(user: user, project: target_project, subscribed: true)
+ end
+ end
+
+ let(:noteable) { issue }
+ let(:target_project) { project }
+
describe 'GET unsubscribe' do
context 'when the user is not logged in' do
context 'when the force param is passed' do
@@ -34,20 +50,93 @@ describe SentNotificationsController do
end
context 'when the force param is not passed' do
+ render_views
+
before do
get(:unsubscribe, params: { id: sent_notification.reply_key })
end
- it 'does not unsubscribe the user' do
- expect(issue.subscribed?(user, project)).to be_truthy
+ shared_examples 'unsubscribing as anonymous' do
+ it 'does not unsubscribe the user' do
+ expect(noteable.subscribed?(user, target_project)).to be_truthy
+ end
+
+ it 'does not set the flash message' do
+ expect(controller).not_to set_flash[:notice]
+ end
+
+ it 'renders unsubscribe page' do
+ expect(response.status).to eq(200)
+ expect(response).to render_template :unsubscribe
+ end
end
- it 'does not set the flash message' do
- expect(controller).not_to set_flash[:notice]
+ context 'when project is public' do
+ context 'when unsubscribing from issue' do
+ let(:noteable) { issue }
+
+ it 'shows issue title' do
+ expect(response.body).to include(issue.title)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
+
+ context 'when unsubscribing from confidential issue' do
+ let(:noteable) { confidential_issue }
+
+ it 'does not show issue title' do
+ expect(response.body).not_to include(confidential_issue.title)
+ expect(response.body).to include(confidential_issue.to_reference)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
+
+ context 'when unsubscribing from merge request' do
+ let(:noteable) { merge_request }
+
+ it 'shows merge request title' do
+ expect(response.body).to include(merge_request.title)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
end
- it 'redirects to the login page' do
- expect(response).to render_template :unsubscribe
+ context 'when project is not public' do
+ let(:target_project) { private_project }
+
+ context 'when unsubscribing from issue' do
+ let(:noteable) { issue }
+
+ it 'shows issue title' do
+ expect(response.body).not_to include(issue.title)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
+
+ context 'when unsubscribing from confidential issue' do
+ let(:noteable) { confidential_issue }
+
+ it 'does not show issue title' do
+ expect(response.body).not_to include(confidential_issue.title)
+ expect(response.body).to include(confidential_issue.to_reference)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
+
+ context 'when unsubscribing from merge request' do
+ let(:noteable) { merge_request }
+
+ it 'shows merge request title' do
+ expect(response.body).not_to include(merge_request.title)
+ end
+
+ it_behaves_like 'unsubscribing as anonymous'
+ end
end
end
end
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 6bcff7f975c..9c4ddce5409 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -58,7 +58,26 @@ describe SessionsController do
it 'authenticates user correctly' do
post(:create, params: { user: user_params })
- expect(subject.current_user). to eq user
+ expect(subject.current_user).to eq user
+ end
+
+ context 'with password authentication disabled' do
+ before do
+ stub_application_setting(password_authentication_enabled_for_web: false)
+ end
+
+ it 'does not sign in the user' do
+ post(:create, params: { user: user_params })
+
+ expect(@request.env['warden']).not_to be_authenticated
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'returns status 403' do
+ post(:create, params: { user: user_params })
+
+ expect(response.status).to eq 403
+ end
end
it 'creates an audit log record' do
@@ -153,6 +172,19 @@ describe SessionsController do
end
end
+ context 'with password authentication disabled' do
+ before do
+ stub_application_setting(password_authentication_enabled_for_web: false)
+ end
+
+ it 'allows 2FA stage of non-password login' do
+ authenticate_2fa(otp_attempt: user.current_otp)
+
+ expect(@request.env['warden']).to be_authenticated
+ expect(subject.current_user).to eq user
+ end
+ end
+
##
# See #14900 issue
#
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index f8c494c159e..a473136b57b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -248,17 +248,6 @@ FactoryBot.define do
runner factory: :ci_runner
end
- trait :legacy_artifacts do
- after(:create) do |build, _|
- build.update!(
- legacy_artifacts_file: fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip'),
- legacy_artifacts_metadata: fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip')
- )
- end
- end
-
trait :artifacts do
after(:create) do |build|
create(:ci_job_artifact, :archive, job: build, expire_at: build.artifacts_expire_at)
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 2c76c22ba69..542fa9775cd 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -45,9 +45,12 @@ FactoryBot.define do
file_type :archive
file_format :zip
- after(:build) do |artifact, _|
- artifact.file = fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip')
+ transient do
+ file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), 'application/zip') }
+ end
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = evaluator.file
end
end
@@ -61,9 +64,12 @@ FactoryBot.define do
file_type :metadata
file_format :gzip
- after(:build) do |artifact, _|
- artifact.file = fixture_file_upload(
- Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip')
+ transient do
+ file { fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), 'application/x-gzip') }
+ end
+
+ after(:build) do |artifact, evaluator|
+ artifact.file = evaluator.file
end
end
diff --git a/spec/factories/ci/pipeline_schedule.rb b/spec/factories/ci/pipeline_schedule.rb
index b2b79807429..4b83ba2ac1b 100644
--- a/spec/factories/ci/pipeline_schedule.rb
+++ b/spec/factories/ci/pipeline_schedule.rb
@@ -7,6 +7,16 @@ FactoryBot.define do
description "pipeline schedule"
project
+ trait :every_minute do
+ cron '*/1 * * * *'
+ cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
+ end
+
+ trait :hourly do
+ cron '* */1 * * *'
+ cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
+ end
+
trait :nightly do
cron '0 1 * * *'
cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index e8df5094b83..0b6a43b13a9 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -95,7 +95,8 @@ FactoryBot.define do
end
trait :merge_when_pipeline_succeeds do
- merge_when_pipeline_succeeds true
+ auto_merge_enabled true
+ auto_merge_strategy AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
merge_user { author }
end
diff --git a/spec/factories/pages_domain_acme_orders.rb b/spec/factories/pages_domain_acme_orders.rb
new file mode 100644
index 00000000000..7f9ee1c8f9c
--- /dev/null
+++ b/spec/factories/pages_domain_acme_orders.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :pages_domain_acme_order do
+ pages_domain
+ url { 'https://example.com/' }
+ expires_at { 1.day.from_now }
+ challenge_token { 'challenge_token' }
+ challenge_file_content { 'filecontent' }
+
+ private_key { OpenSSL::PKey::RSA.new(4096).to_pem }
+
+ trait :expired do
+ expires_at { 1.day.ago }
+ end
+ end
+end
diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb
index 75ac7cc7687..1de42512402 100644
--- a/spec/factories/project_auto_devops.rb
+++ b/spec/factories/project_auto_devops.rb
@@ -2,7 +2,6 @@ FactoryBot.define do
factory :project_auto_devops do
project
enabled true
- domain "example.com"
deploy_strategy :continuous
trait :continuous_deployment do
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index 83cd686818c..f6c498f7a4c 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin Appearance' do
diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb
index 02f50d7e27f..1f83d04d9aa 100644
--- a/spec/features/admin/admin_browses_logs_spec.rb
+++ b/spec/features/admin/admin_browses_logs_spec.rb
@@ -13,5 +13,6 @@ describe 'Admin browses logs' do
expect(page).to have_link 'test.log'
expect(page).to have_link 'sidekiq.log'
expect(page).to have_link 'repocheck.log'
+ expect(page).to have_link 'kubernetes.log'
end
end
diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb
index 95d1fc5b57a..b5323a1c76d 100644
--- a/spec/features/admin/admin_sees_project_statistics_spec.rb
+++ b/spec/features/admin/admin_sees_project_statistics_spec.rb
@@ -15,7 +15,7 @@ describe "Admin > Admin sees project statistics" do
let(:project) { create(:project, :repository) }
it "shows project statistics" do
- expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes build artifacts, 0 Bytes LFS)")
+ expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes wikis, 0 Bytes build artifacts, 0 Bytes LFS)")
end
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index f9950b5b03f..93ccb03d822 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -302,22 +302,22 @@ describe 'Admin updates settings' do
group = create(:group)
page.within('.as-performance-bar') do
- check 'Enable the Performance Bar'
+ check 'Enable access to the Performance Bar'
fill_in 'Allowed group', with: group.path
click_on 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
- expect(find_field('Enable the Performance Bar')).to be_checked
+ expect(find_field('Enable access to the Performance Bar')).to be_checked
expect(find_field('Allowed group').value).to eq group.path
page.within('.as-performance-bar') do
- uncheck 'Enable the Performance Bar'
+ uncheck 'Enable access to the Performance Bar'
click_on 'Save changes'
end
expect(page).to have_content 'Application settings saved successfully'
- expect(find_field('Enable the Performance Bar')).not_to be_checked
+ expect(find_field('Enable access to the Performance Bar')).not_to be_checked
expect(find_field('Allowed group').value).to be_nil
end
@@ -332,16 +332,19 @@ describe 'Admin updates settings' do
end
context 'Network page' do
- it 'Enable outbound requests' do
+ it 'Changes Outbound requests settings' do
visit network_admin_application_settings_path
page.within('.as-outbound') do
check 'Allow requests to the local network from hooks and services'
+ # Enabled by default
+ uncheck 'Enforce DNS rebinding attack protection'
click_button 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
expect(Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services).to be true
+ expect(Gitlab::CurrentSettings.dns_rebinding_protection_enabled).to be false
end
end
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index dfa1c92ea49..d523e2992db 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Dashboard Issues Feed" do
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 87c0dc40e5c..b1798c11361 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -352,6 +352,8 @@ describe 'Issue Boards', :js do
page.within('.labels') do
click_link 'Edit'
+ wait_for_requests
+
click_link 'Create project label'
fill_in 'new_label_name', with: 'test label'
first('.suggest-colors-dropdown a').click
@@ -368,6 +370,8 @@ describe 'Issue Boards', :js do
page.within('.labels') do
click_link 'Edit'
+ wait_for_requests
+
click_link 'Create project label'
fill_in 'new_label_name', with: 'test label'
first('.suggest-colors-dropdown a').click
diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb
index d2e46d15730..683c57a97f8 100644
--- a/spec/features/clusters/cluster_detail_page_spec.rb
+++ b/spec/features/clusters/cluster_detail_page_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe 'Clusterable > Show page' do
+ include KubernetesHelpers
+
let(:current_user) { create(:user) }
let(:cluster_ingress_help_text_selector) { '.js-ingress-domain-help-text' }
let(:hide_modifier_selector) { '.hide' }
@@ -83,6 +85,7 @@ describe 'Clusterable > Show page' do
shared_examples 'editing a user-provided cluster' do
before do
+ stub_kubeclient_discover(cluster.platform.api_url)
clusterable.add_maintainer(current_user)
visit cluster_path
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 5c6c1c4fd15..2adeb37c98a 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -89,7 +89,7 @@ describe 'Commits' do
context 'Download artifacts' do
before do
- build.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
end
it do
@@ -119,7 +119,7 @@ describe 'Commits' do
context "when logged as reporter" do
before do
project.add_reporter(user)
- build.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
visit pipeline_path(pipeline)
end
@@ -141,7 +141,7 @@ describe 'Commits' do
project.update(
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
public_builds: false)
- build.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: build)
visit pipeline_path(pipeline)
end
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index d7692181453..f2ab5373d3d 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -25,4 +25,18 @@ describe 'Global search' do
expect(page).to have_selector('.gl-pagination .next')
end
end
+
+ it 'closes the dropdown on blur', :js do
+ visit dashboard_projects_path
+
+ fill_in 'search', with: "a"
+ dropdown = find('.js-dashboard-search-options')
+
+ expect(dropdown[:class]).to include 'show'
+
+ find('#search').send_keys(:backspace)
+ find('body').click
+
+ expect(dropdown[:class]).not_to include 'show'
+ end
end
diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb
index b661b5cbaef..84a8691a7f2 100644
--- a/spec/features/groups/clusters/user_spec.rb
+++ b/spec/features/groups/clusters/user_spec.rb
@@ -14,6 +14,7 @@ describe 'User Cluster', :js do
allow(Groups::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
end
context 'when user does not have a cluster and visits cluster index page' do
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index e1bc4eca619..59230d6891a 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group merge requests page' do
diff --git a/spec/features/instance_statistics/conversational_development_index_spec.rb b/spec/features/instance_statistics/conversational_development_index_spec.rb
index d8be554d734..713cd944f8c 100644
--- a/spec/features/instance_statistics/conversational_development_index_spec.rb
+++ b/spec/features/instance_statistics/conversational_development_index_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Conversational Development Index' do
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index 7b6e9cd66b2..225b858742d 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -76,7 +76,7 @@ describe 'issuable list' do
create(:issue, project: project, author: user)
else
create(:merge_request, source_project: project, source_branch: generate(:branch))
- source_branch = FFaker::Name.name
+ source_branch = FFaker::Lorem.characters(8)
pipeline = create(:ci_empty_pipeline, project: project, ref: source_branch, status: %w(running failed success).sample, sha: 'any')
create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: source_branch, head_pipeline: pipeline)
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index bc0ec58bd24..5bdd9113b06 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issues' do
@@ -208,7 +210,7 @@ describe 'Issues' do
let(:issue) { @issue }
it 'allows filtering by issues with no specified assignee' do
- visit project_issues_path(project, assignee_id: IssuableFinder::NONE)
+ visit project_issues_path(project, assignee_id: IssuableFinder::FILTER_NONE)
expect(page).to have_content 'foobar'
expect(page).not_to have_content 'barbaz'
diff --git a/spec/features/markdown/gitlab_flavored_markdown_spec.rb b/spec/features/markdown/gitlab_flavored_markdown_spec.rb
index 6997ca48427..8fda3c7193e 100644
--- a/spec/features/markdown/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/markdown/gitlab_flavored_markdown_spec.rb
@@ -20,8 +20,7 @@ describe "GitLab Flavored Markdown" do
let(:commit) { project.commit }
before do
- allow_any_instance_of(Commit).to receive(:title)
- .and_return("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details")
+ create_commit("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details", project, user, 'master')
end
it "renders title in commits#index" do
diff --git a/spec/features/merge_request/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb
index bcc11217389..d05ef2a8f12 100644
--- a/spec/features/merge_request/user_creates_merge_request_spec.rb
+++ b/spec/features/merge_request/user_creates_merge_request_spec.rb
@@ -8,8 +8,6 @@ describe "User creates a merge request", :js do
let(:user) { create(:user) }
before do
- stub_feature_flags(approval_rules: false)
-
project.add_maintainer(user)
sign_in(user)
end
diff --git a/spec/features/merge_request/user_merges_merge_request_spec.rb b/spec/features/merge_request/user_merges_merge_request_spec.rb
index 6539e6e9208..da15a4bda4b 100644
--- a/spec/features/merge_request/user_merges_merge_request_spec.rb
+++ b/spec/features/merge_request/user_merges_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User merges a merge request", :js do
diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
index 6e54aa6006b..586b3ba170d 100644
--- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
@@ -52,7 +52,7 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
# so we have to wait for asynchronous call to reload it
# and have_content expectation handles that.
#
- expect(page).to have_content "Pipeline ##{pipeline.id} running"
+ expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) running"
end
it_behaves_like 'Merge when pipeline succeeds activator'
@@ -74,11 +74,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
source_project: project,
title: 'Bug NS-04',
author: user,
- merge_user: user,
- merge_params: { force_remove_source_branch: '1' })
+ merge_user: user)
end
before do
+ merge_request.merge_params['force_remove_source_branch'] = '0'
+ merge_request.save!
click_link "Cancel automatic merge"
end
@@ -102,11 +103,11 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
context 'when merge when pipeline succeeds is enabled' do
let(:merge_request) do
- create(:merge_request_with_diffs, :simple, source_project: project,
- author: user,
- merge_user: user,
- title: 'MepMep',
- merge_when_pipeline_succeeds: true)
+ create(:merge_request_with_diffs, :simple, :merge_when_pipeline_succeeds,
+ source_project: project,
+ author: user,
+ merge_user: user,
+ title: 'MepMep')
end
let!(:build) do
create(:ci_build, pipeline: pipeline)
@@ -158,8 +159,8 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- page.within('.mr-widget-body') do
- expect(page).to have_content('Something went wrong')
+ page.within('.mr-section-container') do
+ expect(page).to have_content('Merge failed: Something went wrong')
end
end
end
@@ -177,8 +178,8 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- page.within('.mr-widget-body') do
- expect(page).to have_content('Something went wrong')
+ page.within('.mr-section-container') do
+ expect(page).to have_content('Merge failed: Something went wrong')
end
end
end
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index a32c6bdcf8f..0066e985fbb 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -160,7 +160,7 @@ describe 'Merge request > User sees merge widget', :js do
it 'shows head pipeline information' do
within '.ci-widget-content' do
- expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
"for #{pipeline.short_sha} " \
"on #{pipeline.ref}")
end
@@ -189,7 +189,7 @@ describe 'Merge request > User sees merge widget', :js do
it 'shows head pipeline information' do
within '.ci-widget-content' do
- expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
"for #{pipeline.short_sha} " \
"on #{merge_request.to_reference} " \
"with #{merge_request.source_branch}")
@@ -201,7 +201,7 @@ describe 'Merge request > User sees merge widget', :js do
it 'shows head pipeline information' do
within '.ci-widget-content' do
- expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
"for #{pipeline.short_sha} " \
"on #{merge_request.to_reference} " \
"with #{merge_request.source_branch}")
@@ -234,7 +234,7 @@ describe 'Merge request > User sees merge widget', :js do
it 'shows head pipeline information' do
within '.ci-widget-content' do
- expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
"for #{pipeline.short_sha} " \
"on #{merge_request.to_reference} " \
"with #{merge_request.source_branch} " \
@@ -248,7 +248,7 @@ describe 'Merge request > User sees merge widget', :js do
it 'shows head pipeline information' do
within '.ci-widget-content' do
- expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \
"for #{pipeline.short_sha} " \
"on #{merge_request.to_reference} " \
"with #{merge_request.source_branch} " \
@@ -314,7 +314,8 @@ describe 'Merge request > User sees merge widget', :js do
context 'view merge request with MWPS enabled but automatically merge fails' do
before do
merge_request.update(
- merge_when_pipeline_succeeds: true,
+ auto_merge_enabled: true,
+ auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
merge_user: merge_request.author,
merge_error: 'Something went wrong'
)
@@ -326,8 +327,8 @@ describe 'Merge request > User sees merge widget', :js do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- page.within('.mr-widget-body') do
- expect(page).to have_content('Something went wrong')
+ page.within('.mr-section-container') do
+ expect(page).to have_content('Merge failed: Something went wrong')
end
end
end
@@ -347,8 +348,8 @@ describe 'Merge request > User sees merge widget', :js do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
- page.within('.mr-widget-body') do
- expect(page).to have_content('Something went wrong')
+ page.within('.mr-section-container') do
+ expect(page).to have_content('Merge failed: Something went wrong')
end
end
end
diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index 5188dc3625f..dd8900a3698 100644
--- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -27,7 +27,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do
let(:artifacts_file2) { fixture_file_upload(File.join('spec/fixtures/dk.png'), 'image/png') }
before do
- create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file1)
+ job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+ create(:ci_job_artifact, :archive, file: artifacts_file1, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
end
@@ -35,7 +36,8 @@ describe 'Merge request < User sees mini pipeline graph', :js do
xit 'avoids repeated database queries' do
before = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
- create(:ci_build, :success, :trace_artifact, pipeline: pipeline, legacy_artifacts_file: artifacts_file2)
+ job = create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+ create(:ci_job_artifact, :archive, file: artifacts_file2, job: job)
create(:ci_build, :manual, pipeline: pipeline, when: 'manual')
after = ActiveRecord::QueryRecorder.new { visit_merge_request(format: :json, serializer: 'widget') }
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index bd91fae1453..2dee0e26954 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -33,7 +33,7 @@ describe 'Merge requests > User lists merge requests' do
end
it 'filters on no assignee' do
- visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
+ visit_merge_requests(project, assignee_id: IssuableFinder::FILTER_NONE)
expect(current_path).to eq(project_merge_requests_path(project))
expect(page).to have_content 'merge-test'
diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb
index 76abc640077..95685a3c7ff 100644
--- a/spec/features/project_variables_spec.rb
+++ b/spec/features/project_variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project variables', :js do
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index fe4f737a7da..31cc09ae911 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -12,6 +12,7 @@ describe 'User Cluster', :js do
allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
end
context 'when user does not have a cluster and visits cluster index page' do
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index a85e7333ba8..ce382c19fc1 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Clusters', :js do
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index 574a8aefd63..953517cdff9 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -61,7 +61,7 @@ describe 'User browses commits' do
it 'renders commit ci info' do
visit project_commit_path(project, sample_commit.id)
- expect(page).to have_content "Pipeline ##{pipeline.id} pending"
+ expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) pending"
end
end
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 6762460971f..44715261b8b 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -5,6 +5,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do
let(:project_maintainer) { project.owner }
before do
+ stub_feature_flags(vue_file_list: false)
project.repository.delete_file(project_maintainer, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master')
sign_in(project_maintainer)
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index dd2964c2186..69f8bd4d319 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -12,6 +12,7 @@ describe 'Projects > Files > User creates files' do
let(:user) { create(:user) }
before do
+ stub_feature_flags(vue_file_list: false)
stub_feature_flags(web_ide_default: false)
project.add_maintainer(user)
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index f76f9ba7577..9d74a96ab3d 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -12,7 +12,7 @@ describe 'Import/Export - project export integration test', :js do
let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
- let(:sensitive_words) { %w[pass secret token key encrypted] }
+ let(:sensitive_words) { %w[pass secret token key encrypted html] }
let(:safe_list) do
{
token: [ProjectHook, Ci::Trigger, CommitStatus],
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 28ae90bc0de..8d2b1fc7e30 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -47,7 +47,6 @@ describe 'Import/Export - project import integration test', :js do
expect(project.description).to eq("Foo Bar")
expect(project.issues).not_to be_empty
expect(project.merge_requests).not_to be_empty
- expect(project_hook_exists?(project)).to be true
expect(wiki_exists?(project)).to be true
expect(project.import_state.status).to eq('finished')
end
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index 6ce37297a7e..b5e711997a0 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -90,7 +90,7 @@ describe 'Project Jobs Permissions' do
before do
archive = fixture_file_upload('spec/fixtures/ci_build_artifacts.zip')
- job.update(legacy_artifacts_file: archive)
+ create(:ci_job_artifact, :archive, file: archive, job: job)
end
context 'when public access for jobs is disabled' do
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index 908c616f2fc..54b462da87a 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -28,8 +28,8 @@ describe 'User browses a job', :js do
expect(page).to have_no_css('.artifacts')
expect(build).not_to have_trace
- expect(build.artifacts_file.exists?).to be_falsy
- expect(build.artifacts_metadata.exists?).to be_falsy
+ expect(build.artifacts_file.present?).to be_falsy
+ expect(build.artifacts_metadata.present?).to be_falsy
expect(page).to have_content('Job has been erased')
end
diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb
index ebc20d15d67..bd6c73f4b85 100644
--- a/spec/features/projects/jobs/user_browses_jobs_spec.rb
+++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb
@@ -16,6 +16,12 @@ describe 'User browses jobs' do
visit(project_jobs_path(project))
end
+ it 'shows pipeline id and IID' do
+ page.within('td.pipeline-link') do
+ expect(page).to have_content("##{pipeline.id} (##{pipeline.iid})")
+ end
+ end
+
it 'shows the coverage' do
page.within('td.coverage') do
expect(page).to have_content('99.9%')
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 9cf04fe13b4..03562bd382e 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -129,7 +129,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
visit project_job_path(project, job)
within '.js-pipeline-info' do
- expect(page).to have_content("Pipeline ##{pipeline.id} for #{pipeline.ref}")
+ expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) for #{pipeline.ref}")
end
end
@@ -314,7 +314,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context "Download artifacts", :js do
before do
- job.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: job)
visit project_job_path(project, job)
end
@@ -338,8 +338,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context 'Artifacts expire date', :js do
before do
- job.update(legacy_artifacts_file: artifacts_file,
- artifacts_expire_at: expire_at)
+ create(:ci_job_artifact, :archive, file: artifacts_file, expire_at: expire_at, job: job)
+ job.update!(artifacts_expire_at: expire_at)
visit project_job_path(project, job)
end
@@ -936,8 +936,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
find('.js-cancel-job').click
end
- it 'loads the page and shows no controls' do
- expect(page).not_to have_content 'Retry'
+ it 'loads the page and shows all needed controls' do
+ expect(page).to have_content 'Retry'
end
end
end
@@ -946,7 +946,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context "Job from project", :js do
before do
job.run!
- job.drop!(:script_failure)
+ job.cancel!
visit project_job_path(project, job)
wait_for_requests
@@ -981,7 +981,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
describe "GET /:project/jobs/:id/download", :js do
before do
- job.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: job)
visit project_job_path(project, job)
click_link 'Download'
@@ -989,7 +989,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
context "Build from other project" do
before do
- job2.update(legacy_artifacts_file: artifacts_file)
+ create(:ci_job_artifact, :archive, file: artifacts_file, job: job2)
end
it do
diff --git a/spec/features/projects/labels/user_promotes_label_spec.rb b/spec/features/projects/labels/user_promotes_label_spec.rb
new file mode 100644
index 00000000000..fdecafd4c50
--- /dev/null
+++ b/spec/features/projects/labels/user_promotes_label_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User promotes label' do
+ set(:group) { create(:group) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project, namespace: group) }
+ set(:label) { create(:label, project: project) }
+
+ context 'when user can admin group labels' do
+ before do
+ group.add_developer(user)
+ sign_in(user)
+ visit(project_labels_path(project))
+ end
+
+ it "shows label promote button" do
+ expect(page).to have_selector('.js-promote-project-label-button')
+ end
+ end
+
+ context 'when user cannot admin group labels' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ visit(project_labels_path(project))
+ end
+
+ it "does not show label promote button" do
+ expect(page).not_to have_selector('.js-promote-project-label-button')
+ end
+ end
+end
diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb
index b0ce03a1c31..c231e54decd 100644
--- a/spec/features/projects/labels/user_removes_labels_spec.rb
+++ b/spec/features/projects/labels/user_removes_labels_spec.rb
@@ -21,8 +21,11 @@ describe "User removes labels" do
page.first(".label-list-item") do
first('.js-label-options-dropdown').click
first(".remove-row").click
- first(:link, "Delete label").click
end
+
+ expect(page).to have_content("#{label.title} will be permanently deleted from #{project.name}. This cannot be undone.")
+
+ first(:link, "Delete label").click
end
expect(page).to have_content("Label was removed").and have_no_content(label.title)
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index b54ea929978..033e1afe866 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -63,6 +63,36 @@ describe 'New project' do
end
end
end
+
+ context 'when group visibility is private but default is internal' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ visit new_project_path(namespace_id: group.id)
+
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
+ end
+ end
+
+ context 'when group visibility is public but user requests private' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
+ end
+ end
end
context 'Readme selector' do
diff --git a/spec/features/projects/pages_lets_encrypt_spec.rb b/spec/features/projects/pages_lets_encrypt_spec.rb
new file mode 100644
index 00000000000..baa217cbe58
--- /dev/null
+++ b/spec/features/projects/pages_lets_encrypt_spec.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe "Pages with Let's Encrypt", :https_pages_enabled do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:role) { :maintainer }
+ let(:certificate_pem) do
+ <<~PEM
+ -----BEGIN CERTIFICATE-----
+ MIICGzCCAYSgAwIBAgIBATANBgkqhkiG9w0BAQUFADAbMRkwFwYDVQQDExB0ZXN0
+ LWNlcnRpZmljYXRlMB4XDTE2MDIxMjE0MzIwMFoXDTIwMDQxMjE0MzIwMFowGzEZ
+ MBcGA1UEAxMQdGVzdC1jZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+ gYkCgYEApL4J9L0ZxFJ1hI1LPIflAlAGvm6ZEvoT4qKU5Xf2JgU7/2geNR1qlNFa
+ SvCc08Knupp5yTgmvyK/Xi09U0N82vvp4Zvr/diSc4A/RA6Mta6egLySNT438kdT
+ nY2tR5feoTLwQpX0t4IMlwGQGT5h6Of2fKmDxzuwuyffcIHqLdsCAwEAAaNvMG0w
+ DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUxl9WSxBprB0z0ibJs3rXEk0+95AwCwYD
+ VR0PBAQDAgXgMBEGCWCGSAGG+EIBAQQEAwIGQDAeBglghkgBhvhCAQ0EERYPeGNh
+ IGNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4GBAGC4T8SlFHK0yPSa+idGLQFQ
+ joZp2JHYvNlTPkRJ/J4TcXxBTJmArcQgTIuNoBtC+0A/SwdK4MfTCUY4vNWNdese
+ 5A4K65Nb7Oh1AdQieTBHNXXCdyFsva9/ScfQGEl7p55a52jOPs0StPd7g64uvjlg
+ YHi2yesCrOvVXt+lgPTd
+ -----END CERTIFICATE-----
+ PEM
+ end
+
+ let(:certificate_key) do
+ <<~KEY
+ -----BEGIN PRIVATE KEY-----
+ MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKS+CfS9GcRSdYSN
+ SzyH5QJQBr5umRL6E+KilOV39iYFO/9oHjUdapTRWkrwnNPCp7qaeck4Jr8iv14t
+ PVNDfNr76eGb6/3YknOAP0QOjLWunoC8kjU+N/JHU52NrUeX3qEy8EKV9LeCDJcB
+ kBk+Yejn9nypg8c7sLsn33CB6i3bAgMBAAECgYA2D26w80T7WZvazYr86BNMePpd
+ j2mIAqx32KZHzt/lhh40J/SRtX9+Kl0Y7nBoRR5Ja9u/HkAIxNxLiUjwg9r6cpg/
+ uITEF5nMt7lAk391BuI+7VOZZGbJDsq2ulPd6lO+C8Kq/PI/e4kXcIjeH6KwQsuR
+ 5vrXfBZ3sQfflaiN4QJBANBt8JY2LIGQF8o89qwUpRL5vbnKQ4IzZ5+TOl4RLR7O
+ AQpJ81tGuINghO7aunctb6rrcKJrxmEH1whzComybrMCQQDKV49nOBudRBAIgG4K
+ EnLzsRKISUHMZSJiYTYnablof8cKw1JaQduw7zgrUlLwnroSaAGX88+Jw1f5n2Lh
+ Vlg5AkBDdUGnrDLtYBCDEQYZHblrkc7ZAeCllDOWjxUV+uMqlCv8A4Ey6omvY57C
+ m6I8DkWVAQx8VPtozhvHjUw80rZHAkB55HWHAM3h13axKG0htCt7klhPsZHpx6MH
+ EPjGlXIT+aW2XiPmK3ZlCDcWIenE+lmtbOpI159Wpk8BGXs/s/xBAkEAlAY3ymgx
+ 63BDJEwvOb2IaP8lDDxNsXx9XJNVvQbv5n15vNsLHbjslHfAhAbxnLQ1fLhUPqSi
+ nNp/xedE1YxutQ==
+ -----END PRIVATE KEY-----
+ KEY
+ end
+
+ before do
+ allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
+ project.add_role(user, role)
+ sign_in(user)
+ project.namespace.update(owner: user)
+ allow_any_instance_of(Project).to receive(:pages_deployed?) { true }
+ end
+
+ context 'when the page_auto_ssl feature flag is enabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: true)
+ end
+
+ context 'when the auto SSL management is initially disabled' do
+ let(:domain) do
+ create(:pages_domain, auto_ssl_enabled: false, project: project)
+ end
+
+ it 'enables auto SSL and dynamically updates the form accordingly', :js do
+ visit edit_project_pages_domain_path(project, domain)
+
+ expect(domain.auto_ssl_enabled).to eq false
+
+ expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'false'
+ expect(page).to have_field 'Certificate (PEM)', type: 'textarea'
+ expect(page).to have_field 'Key (PEM)', type: 'textarea'
+
+ find('.js-auto-ssl-toggle-container .project-feature-toggle').click
+
+ expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'true'
+ expect(page).not_to have_field 'Certificate (PEM)', type: 'textarea'
+ expect(page).not_to have_field 'Key (PEM)', type: 'textarea'
+ expect(page).to have_content "The certificate will be shown here once it has been obtained from Let's Encrypt. This process may take up to an hour to complete."
+
+ click_on 'Save Changes'
+
+ expect(domain.reload.auto_ssl_enabled).to eq true
+ end
+ end
+
+ context 'when the auto SSL management is initially enabled' do
+ let(:domain) do
+ create(:pages_domain, auto_ssl_enabled: true, project: project)
+ end
+
+ it 'disables auto SSL and dynamically updates the form accordingly', :js do
+ visit edit_project_pages_domain_path(project, domain)
+
+ expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'true'
+ expect(page).to have_field 'Certificate (PEM)', type: 'textarea', disabled: true
+ expect(page).not_to have_field 'Key (PEM)', type: 'textarea'
+
+ find('.js-auto-ssl-toggle-container .project-feature-toggle').click
+
+ expect(find("#pages_domain_auto_ssl_enabled", visible: false).value).to eq 'false'
+ expect(page).to have_field 'Certificate (PEM)', type: 'textarea'
+ expect(page).to have_field 'Key (PEM)', type: 'textarea'
+
+ fill_in 'Certificate (PEM)', with: certificate_pem
+ fill_in 'Key (PEM)', with: certificate_key
+
+ click_on 'Save Changes'
+
+ expect(domain.reload.auto_ssl_enabled).to eq false
+ end
+ end
+ end
+
+ context 'when the page_auto_ssl feature flag is disabled' do
+ let(:domain) do
+ create(:pages_domain, auto_ssl_enabled: false, project: project)
+ end
+
+ before do
+ stub_feature_flags(pages_auto_ssl: false)
+
+ visit edit_project_pages_domain_path(project, domain)
+ end
+
+ it "does not render the Let's Encrypt field", :js do
+ expect(page).not_to have_selector '.js-auto-ssl-toggle-container'
+ end
+ end
+end
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index f564ae34f11..9bb0ba81ef5 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -1,6 +1,7 @@
+# frozen_string_literal: true
require 'spec_helper'
-describe 'Pages' do
+shared_examples 'pages domain editing' do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:role) { :maintainer }
@@ -287,10 +288,17 @@ describe 'Pages' do
:ci_build,
project: project,
pipeline: pipeline,
- ref: 'HEAD',
- legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')),
- legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta'))
- )
+ ref: 'HEAD')
+ end
+
+ let!(:artifact) do
+ create(:ci_job_artifact, :archive,
+ file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), job: ci_build)
+ end
+
+ let!(:metadata) do
+ create(:ci_job_artifact, :metadata,
+ file: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')), job: ci_build)
end
before do
@@ -311,3 +319,21 @@ describe 'Pages' do
end
end
end
+
+describe 'Pages' do
+ context 'when pages_auto_ssl feature flag is disabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: false)
+ end
+
+ include_examples 'pages domain editing'
+ end
+
+ context 'when pages_auto_ssl feature flag is enabled' do
+ before do
+ stub_feature_flags(pages_auto_ssl: true)
+ end
+
+ include_examples 'pages domain editing'
+ end
+end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index b1a705f09ce..24041a51383 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -225,7 +225,7 @@ describe 'Pipeline Schedules', :js do
context 'when active is true and next_run_at is NULL' do
before do
create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule|
- pipeline_schedule.update_attribute(:cron, nil) # Consequently next_run_at will be nil
+ pipeline_schedule.update_attribute(:next_run_at, nil) # Consequently next_run_at will be nil
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index a1115b514d3..1de153db41c 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Pipeline', :js do
@@ -115,11 +117,11 @@ describe 'Pipeline', :js do
end
end
- it 'cancels the running build and does not show retry button' do
+ it 'cancels the running build and shows retry button' do
find('#ci-badge-deploy .ci-action-icon-container').click
page.within('#ci-badge-deploy') do
- expect(page).not_to have_css('.js-icon-retry')
+ expect(page).to have_css('.js-icon-retry')
end
end
end
@@ -133,11 +135,11 @@ describe 'Pipeline', :js do
end
end
- it 'cancels the preparing build and does not show retry button' do
+ it 'cancels the preparing build and shows retry button' do
find('#ci-badge-deploy .ci-action-icon-container').click
page.within('#ci-badge-deploy') do
- expect(page).not_to have_css('.js-icon-retry')
+ expect(page).to have_css('.js-icon-retry')
end
end
end
@@ -328,6 +330,12 @@ describe 'Pipeline', :js do
expect(page).not_to have_link(pipeline.ref)
expect(page).to have_content(pipeline.ref)
end
+
+ it 'does not render render raw HTML to the pipeline ref' do
+ page.within '.pipeline-info' do
+ expect(page).not_to have_content('<span class="ref-name"')
+ end
+ end
end
context 'when pipeline is detached merge request pipeline' do
diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb
index e14934b1672..9865dbbfb3c 100644
--- a/spec/features/projects/serverless/functions_spec.rb
+++ b/spec/features/projects/serverless/functions_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe 'Functions', :js do
include KubernetesHelpers
+ include ReactiveCachingHelpers
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -13,44 +14,70 @@ describe 'Functions', :js do
gitlab_sign_in(user)
end
- context 'when user does not have a cluster and visits the serverless page' do
+ shared_examples "it's missing knative installation" do
before do
visit project_serverless_functions_path(project)
end
- it 'sees an empty state' do
+ it 'sees an empty state require Knative installation' do
expect(page).to have_link('Install Knative')
expect(page).to have_selector('.empty-state')
end
end
+ context 'when user does not have a cluster and visits the serverless page' do
+ it_behaves_like "it's missing knative installation"
+ end
+
context 'when the user does have a cluster and visits the serverless page' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- before do
- visit project_serverless_functions_path(project)
- end
-
- it 'sees an empty state' do
- expect(page).to have_link('Install Knative')
- expect(page).to have_selector('.empty-state')
- end
+ it_behaves_like "it's missing knative installation"
end
context 'when the user has a cluster and knative installed and visits the serverless page' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
- let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
- let(:project) { knative.cluster.project }
+ let(:project) { cluster.project }
+ let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+ let(:namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ cluster_project: cluster.cluster_project,
+ project: cluster.cluster_project.project)
+ end
before do
- stub_kubeclient_knative_services
- stub_kubeclient_service_pods
+ allow_any_instance_of(Clusters::Cluster)
+ .to receive(:knative_services_finder)
+ .and_return(knative_services_finder)
+ synchronous_reactive_cache(knative_services_finder)
+ stub_kubeclient_knative_services(stub_get_services_options)
+ stub_kubeclient_service_pods(nil, namespace: namespace.namespace)
visit project_serverless_functions_path(project)
end
- it 'sees an empty listing of serverless functions' do
- expect(page).to have_selector('.empty-state')
+ context 'when there are no functions' do
+ let(:stub_get_services_options) do
+ {
+ namespace: namespace.namespace,
+ response: kube_response({ "kind" => "ServiceList", "items" => [] })
+ }
+ end
+
+ it 'sees an empty listing of serverless functions' do
+ expect(page).to have_selector('.empty-state')
+ expect(page).not_to have_selector('.content-list')
+ end
+ end
+
+ context 'when there are functions' do
+ let(:stub_get_services_options) { { namespace: namespace.namespace } }
+
+ it 'does not see an empty listing of serverless functions' do
+ expect(page).not_to have_selector('.empty-state')
+ expect(page).to have_selector('.content-list')
+ end
end
end
end
diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb
index dc0278370aa..df33d215602 100644
--- a/spec/features/projects/settings/forked_project_settings_spec.rb
+++ b/spec/features/projects/settings/forked_project_settings_spec.rb
@@ -7,7 +7,6 @@ describe 'Projects > Settings > For a forked project', :js do
let(:forked_project) { fork_project(original_project, user) }
before do
- stub_feature_flags(approval_rules: false)
original_project.add_maintainer(user)
forked_project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb
index af56cb0d4ee..d96e243d96b 100644
--- a/spec/features/projects/settings/operations_settings_spec.rb
+++ b/spec/features/projects/settings/operations_settings_spec.rb
@@ -46,6 +46,9 @@ describe 'Projects > Settings > For a forked project', :js do
wait_for_requests
+ within '.js-error-tracking-settings' do
+ click_button('Expand')
+ end
expect(page).to have_content('Sentry API URL')
expect(page.body).to include('Error Tracking')
expect(page).to have_button('Connect')
@@ -86,6 +89,9 @@ describe 'Projects > Settings > For a forked project', :js do
wait_for_requests
+ within '.js-error-tracking-settings' do
+ click_button('Expand')
+ end
check('Active')
fill_in('error-tracking-api-host', with: 'http://sentry.example.com')
fill_in('error-tracking-token', with: 'token')
diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
index 28d52f25f56..0739726f52c 100644
--- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
@@ -51,7 +51,7 @@ describe 'Projects > Settings > User manages merge request settings' do
end
it 'shows the Merge Requests settings that do not depend on Builds feature' do
- expect(page).not_to have_content 'Pipelines must succeed'
+ expect(page).to have_content 'Pipelines must succeed'
expect(page).to have_content 'All discussions must be resolved'
within('.sharing-permissions-form') do
diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
index 24777788248..46586b891e7 100644
--- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb
+++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
@@ -5,6 +5,7 @@ describe 'Projects > Show > Collaboration links' do
let(:user) { create(:user) }
before do
+ stub_feature_flags(vue_file_list: false)
project.add_developer(user)
sign_in(user)
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 4fe45311b2d..b5112758475 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project' do
@@ -6,7 +8,6 @@ describe 'Project' do
before do
stub_feature_flags(vue_file_list: false)
- stub_feature_flags(approval_rules: false)
end
describe 'creating from template' do
diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index a198e65046f..044a47567be 100644
--- a/spec/features/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Profile access" do
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index 957c3cfc583..1a9caf0ffbb 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -25,6 +25,13 @@ describe 'Signup' do
expect(find('.username')).not_to have_css '.gl-field-error-outline'
end
+ it 'does not show an error border if the username length is not longer than 255 characters' do
+ fill_in 'new_user_username', with: 'u' * 255
+ wait_for_requests
+
+ expect(find('.username')).not_to have_css '.gl-field-error-outline'
+ end
+
it 'shows an error border if the username already exists' do
existing_user = create(:user)
@@ -41,6 +48,20 @@ describe 'Signup' do
expect(find('.username')).to have_css '.gl-field-error-outline'
end
+ it 'shows an error border if the username is longer than 255 characters' do
+ fill_in 'new_user_username', with: 'u' * 256
+ wait_for_requests
+
+ expect(find('.username')).to have_css '.gl-field-error-outline'
+ end
+
+ it 'shows an error message if the username is longer than 255 characters' do
+ fill_in 'new_user_username', with: 'u' * 256
+ wait_for_requests
+
+ expect(page).to have_content("Username is too long (maximum is 255 characters).")
+ end
+
it 'shows an error message on submit if the username contains special characters' do
fill_in 'new_user_username', with: 'new$user!username'
wait_for_requests
@@ -67,14 +88,35 @@ describe 'Signup' do
before do
visit root_path
click_link 'Register'
- simulate_input('#new_user_name', 'Ehsan 🦋')
+ end
+
+ it 'does not show an error border if the user\'s fullname length is not longer than 128 characters' do
+ fill_in 'new_user_name', with: 'u' * 128
+
+ expect(find('.name')).not_to have_css '.gl-field-error-outline'
end
it 'shows an error border if the user\'s fullname contains an emoji' do
+ simulate_input('#new_user_name', 'Ehsan 🦋')
+
+ expect(find('.name')).to have_css '.gl-field-error-outline'
+ end
+
+ it 'shows an error border if the user\'s fullname is longer than 128 characters' do
+ fill_in 'new_user_name', with: 'n' * 129
+
expect(find('.name')).to have_css '.gl-field-error-outline'
end
+ it 'shows an error message if the user\'s fullname is longer than 128 characters' do
+ fill_in 'new_user_name', with: 'n' * 129
+
+ expect(page).to have_content("Name is too long (maximum is 128 characters).")
+ end
+
it 'shows an error message if the username contains emojis' do
+ simulate_input('#new_user_name', 'Ehsan 🦋')
+
expect(page).to have_content("Invalid input, please avoid emojis")
end
end
diff --git a/spec/finders/clusters/knative_services_finder_spec.rb b/spec/finders/clusters/knative_services_finder_spec.rb
new file mode 100644
index 00000000000..b731c2bd6bf
--- /dev/null
+++ b/spec/finders/clusters/knative_services_finder_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::KnativeServicesFinder do
+ include KubernetesHelpers
+ include ReactiveCachingHelpers
+
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:service) { cluster.platform_kubernetes }
+ let(:project) { cluster.cluster_project.project }
+ let(:namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ cluster_project: cluster.cluster_project,
+ project: project)
+ end
+
+ before do
+ stub_kubeclient_knative_services(namespace: namespace.namespace)
+ stub_kubeclient_service_pods(
+ kube_response(
+ kube_knative_pods_body(
+ project.name, namespace.namespace
+ )
+ ),
+ namespace: namespace.namespace
+ )
+ end
+
+ shared_examples 'a cached data' do
+ it 'has an unintialized cache' do
+ is_expected.to be_blank
+ end
+
+ context 'when using synchronous reactive cache' do
+ before do
+ synchronous_reactive_cache(cluster.knative_services_finder(project))
+ end
+
+ context 'when there are functions for cluster namespace' do
+ it { is_expected.not_to be_blank }
+ end
+
+ context 'when there are no functions for cluster namespace' do
+ before do
+ stub_kubeclient_knative_services(
+ namespace: namespace.namespace,
+ response: kube_response({ "kind" => "ServiceList", "items" => [] })
+ )
+ stub_kubeclient_service_pods(
+ kube_response({ "kind" => "PodList", "items" => [] }),
+ namespace: namespace.namespace
+ )
+ end
+
+ it { is_expected.to be_blank }
+ end
+ end
+ end
+
+ describe '#service_pod_details' do
+ subject { cluster.knative_services_finder(project).service_pod_details(project.name) }
+
+ it_behaves_like 'a cached data'
+ end
+
+ describe '#services' do
+ subject { cluster.knative_services_finder(project).services }
+
+ it_behaves_like 'a cached data'
+ end
+
+ describe '#knative_detected' do
+ subject { cluster.knative_services_finder(project).knative_detected }
+ before do
+ synchronous_reactive_cache(cluster.knative_services_finder(project))
+ end
+
+ context 'when knative is installed' do
+ before do
+ stub_kubeclient_discover(service.api_url)
+ end
+
+ it { is_expected.to be_truthy }
+ it "discovers knative installation" do
+ expect { subject }
+ .to change { cluster.kubeclient.knative_client.discovered }
+ .from(false)
+ .to(true)
+ end
+ end
+
+ context 'when knative is not installed' do
+ before do
+ stub_kubeclient_discover_knative_not_found(service.api_url)
+ end
+
+ it { is_expected.to be_falsy }
+ it "does not discover knative installation" do
+ expect { subject }.not_to change { cluster.kubeclient.knative_client.discovered }
+ end
+ end
+ end
+end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 89fdaceaa9f..bf38d083ca6 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -241,14 +241,6 @@ describe IssuesFinder do
end
end
- context 'filtering by legacy No+Label' do
- let(:params) { { label_name: Label::NONE } }
-
- it 'returns issues with no labels' do
- expect(issues).to contain_exactly(issue1, issue3, issue4)
- end
- end
-
context 'filtering by any label' do
let(:params) { { label_name: described_class::FILTER_ANY } }
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index db48f00cd74..83348457caa 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
describe MembersFinder, '#execute' do
- let(:group) { create(:group) }
- let(:nested_group) { create(:group, :access_requestable, parent: group) }
- let(:project) { create(:project, namespace: nested_group) }
- let(:user1) { create(:user) }
- let(:user2) { create(:user) }
- let(:user3) { create(:user) }
- let(:user4) { create(:user) }
+ set(:group) { create(:group) }
+ set(:nested_group) { create(:group, :access_requestable, parent: group) }
+ set(:project) { create(:project, namespace: nested_group) }
+ set(:user1) { create(:user) }
+ set(:user2) { create(:user) }
+ set(:user3) { create(:user) }
+ set(:user4) { create(:user) }
it 'returns members for project and parent groups', :nested_groups do
nested_group.request_access(user1)
@@ -31,4 +31,34 @@ describe MembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member2, member3])
end
+
+ context 'when include_invited_groups_members == true', :nested_groups do
+ subject { described_class.new(project, user2).execute(include_invited_groups_members: true) }
+
+ set(:linked_group) { create(:group, :public, :access_requestable) }
+ set(:nested_linked_group) { create(:group, parent: linked_group) }
+ set(:linked_group_member) { linked_group.add_developer(user1) }
+ set(:nested_linked_group_member) { nested_linked_group.add_developer(user2) }
+
+ it 'includes all the invited_groups members including members inherited from ancestor groups', :nested_groups do
+ create(:project_group_link, project: project, group: nested_linked_group)
+
+ expect(subject).to contain_exactly(linked_group_member, nested_linked_group_member)
+ end
+
+ it 'includes all the invited_groups members' do
+ create(:project_group_link, project: project, group: linked_group)
+
+ expect(subject).to contain_exactly(linked_group_member)
+ end
+
+ it 'excludes group_members not visible to the user' do
+ create(:project_group_link, project: project, group: linked_group)
+ private_linked_group = create(:group, :private)
+ private_linked_group.add_developer(user3)
+ create(:project_group_link, project: project, group: private_linked_group)
+
+ expect(subject).to contain_exactly(linked_group_member)
+ end
+ end
end
diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb
index 3ad38207da4..8aea45b457c 100644
--- a/spec/finders/projects/serverless/functions_finder_spec.rb
+++ b/spec/finders/projects/serverless/functions_finder_spec.rb
@@ -10,7 +10,7 @@ describe Projects::Serverless::FunctionsFinder do
let(:user) { create(:user) }
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
- let(:project) { cluster.project}
+ let(:project) { cluster.project }
let(:namespace) do
create(:cluster_kubernetes_namespace,
@@ -23,9 +23,45 @@ describe Projects::Serverless::FunctionsFinder do
project.add_maintainer(user)
end
+ describe '#installed' do
+ it 'when reactive_caching is still fetching data' do
+ expect(described_class.new(project).knative_installed).to eq 'checking'
+ end
+
+ context 'when reactive_caching has finished' do
+ let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+
+ before do
+ allow_any_instance_of(Clusters::Cluster)
+ .to receive(:knative_services_finder)
+ .and_return(knative_services_finder)
+ synchronous_reactive_cache(knative_services_finder)
+ end
+
+ context 'when knative is not installed' do
+ it 'returns false' do
+ stub_kubeclient_discover_knative_not_found(service.api_url)
+
+ expect(described_class.new(project).knative_installed).to eq false
+ end
+ end
+
+ context 'reactive_caching is finished and knative is installed' do
+ let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+
+ it 'returns true' do
+ stub_kubeclient_knative_services(namespace: namespace.namespace)
+ stub_kubeclient_service_pods(nil, namespace: namespace.namespace)
+
+ expect(described_class.new(project).knative_installed).to be true
+ end
+ end
+ end
+ end
+
describe 'retrieve data from knative' do
- it 'does not have knative installed' do
- expect(described_class.new(project).execute).to be_empty
+ context 'does not have knative installed' do
+ it { expect(described_class.new(project).execute).to be_empty }
end
context 'has knative installed' do
@@ -38,22 +74,24 @@ describe Projects::Serverless::FunctionsFinder do
it 'there are functions', :use_clean_rails_memory_store_caching do
stub_kubeclient_service_pods
- stub_reactive_cache(knative,
+ stub_reactive_cache(cluster.knative_services_finder(project),
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- })
+ },
+ *cluster.knative_services_finder(project).cache_args)
expect(finder.execute).not_to be_empty
end
it 'has a function', :use_clean_rails_memory_store_caching do
stub_kubeclient_service_pods
- stub_reactive_cache(knative,
+ stub_reactive_cache(cluster.knative_services_finder(project),
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- })
+ },
+ *cluster.knative_services_finder(project).cache_args)
result = finder.service(cluster.environment_scope, cluster.project.name)
expect(result).not_to be_empty
@@ -84,20 +122,4 @@ describe Projects::Serverless::FunctionsFinder do
end
end
end
-
- describe 'verify if knative is installed' do
- context 'knative is not installed' do
- it 'does not have knative installed' do
- expect(described_class.new(project).installed?).to be false
- end
- end
-
- context 'knative is installed' do
- let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
-
- it 'does have knative installed' do
- expect(described_class.new(project).installed?).to be true
- end
- end
- end
end
diff --git a/spec/fixtures/api/schemas/environment.json b/spec/fixtures/api/schemas/environment.json
index 9a10ab18c30..5b1e3c049fa 100644
--- a/spec/fixtures/api/schemas/environment.json
+++ b/spec/fixtures/api/schemas/environment.json
@@ -31,7 +31,11 @@
"last_deployment": {
"oneOf": [
{ "type": "null" },
- { "$ref": "deployment.json" }
+ { "$ref": "deployment.json" },
+ {
+ "name": { "type": "string" },
+ "build_path": { "type": "string" }
+ }
]
}
},
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
index 2d0af57ec2c..33393805464 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
@@ -2,7 +2,8 @@
"type": "object",
"required": [
"unit",
- "label"
+ "label",
+ "prometheus_endpoint_path"
],
"oneOf": [
{ "required": ["query"] },
@@ -14,7 +15,8 @@
"query": { "type": "string" },
"unit": { "type": "string" },
"label": { "type": "string" },
- "track": { "type": "string" }
+ "track": { "type": "string" },
+ "prometheus_endpoint_path": { "type": "string" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/phabricator_responses/auth_failed.json b/spec/fixtures/phabricator_responses/auth_failed.json
new file mode 100644
index 00000000000..50e57c0ba49
--- /dev/null
+++ b/spec/fixtures/phabricator_responses/auth_failed.json
@@ -0,0 +1 @@
+{"result":null,"error_code":"ERR-INVALID-AUTH","error_info":"API token \"api-token\" has the wrong length. API tokens should be 32 characters long."}
diff --git a/spec/fixtures/phabricator_responses/maniphest.search.json b/spec/fixtures/phabricator_responses/maniphest.search.json
new file mode 100644
index 00000000000..6a965007d0c
--- /dev/null
+++ b/spec/fixtures/phabricator_responses/maniphest.search.json
@@ -0,0 +1,98 @@
+{
+ "result": {
+ "data": [
+ {
+ "id": 283,
+ "type": "TASK",
+ "phid": "PHID-TASK-fswfs3wkowjb6cyyxtyx",
+ "fields": {
+ "name": "Things are slow",
+ "description": {
+ "raw": "Things are slow but should be fast!"
+ },
+ "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "ownerPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "status": {
+ "value": "resolved",
+ "name": "Resolved",
+ "color": null
+ },
+ "priority": {
+ "value": 100,
+ "subpriority": 8589934592,
+ "name": "Super urgent",
+ "color": "pink"
+ },
+ "points": null,
+ "subtype": "default",
+ "closerPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "dateClosed": 1374657042,
+ "spacePHID": null,
+ "dateCreated": 1374616241,
+ "dateModified": 1374657044,
+ "policy": {
+ "view": "users",
+ "interact": "users",
+ "edit": "users"
+ },
+ "custom.field-1": null,
+ "custom.field-2": null,
+ "custom.field-3": null
+ },
+ "attachments": {}
+ },
+ {
+ "id": 284,
+ "type": "TASK",
+ "phid": "PHID-TASK-5f73nyq5sjeh4cbmcsnb",
+ "fields": {
+ "name": "Things are broken",
+ "description": {
+ "raw": "Things are broken and should be fixed"
+ },
+ "authorPHID": "PHID-USER-nrtht5wijwbxquens3qr",
+ "ownerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot",
+ "status": {
+ "value": "resolved",
+ "name": "Resolved",
+ "color": null
+ },
+ "priority": {
+ "value": 100,
+ "subpriority": 8589803520,
+ "name": "Super urgent",
+ "color": "pink"
+ },
+ "points": null,
+ "subtype": "default",
+ "closerPHID": "PHID-USER-h425fsrixz4gjxiyr7ot",
+ "dateClosed": 1375049556,
+ "spacePHID": null,
+ "dateCreated": 1374616578,
+ "dateModified": 1375049556,
+ "policy": {
+ "view": "users",
+ "interact": "users",
+ "edit": "users"
+ },
+ "custom.field-1": null,
+ "custom.field-2": null,
+ "custom.field-3": null
+ },
+ "attachments": {}
+ }
+ ],
+ "maps": {},
+ "query": {
+ "queryKey": null
+ },
+ "cursor": {
+ "limit": "2",
+ "after": "284",
+ "before": null,
+ "order": null
+ }
+ },
+ "error_code": null,
+ "error_info": null
+}
diff --git a/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
new file mode 100644
index 00000000000..1e62d020026
--- /dev/null
+++ b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
@@ -0,0 +1,422 @@
+{
+ "version": "2.1",
+ "vulnerabilities": [
+ {
+ "category": "dependency_scanning",
+ "name": "Vulnerabilities in libxml2",
+ "message": "Vulnerabilities in libxml2 in nokogiri",
+ "description": " The version of libxml2 packaged with Nokogiri contains several vulnerabilities.\r\n Nokogiri has mitigated these issues by upgrading to libxml 2.9.5.\r\n\r\n It was discovered that a type confusion error existed in libxml2. An\r\n attacker could use this to specially construct XML data that\r\n could cause a denial of service or possibly execute arbitrary\r\n code. (CVE-2017-0663)\r\n\r\n It was discovered that libxml2 did not properly validate parsed entity\r\n references. An attacker could use this to specially construct XML\r\n data that could expose sensitive information. (CVE-2017-7375)\r\n\r\n It was discovered that a buffer overflow existed in libxml2 when\r\n handling HTTP redirects. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-7376)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overflow in\r\n libxml2 when handling elements. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-9047)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overread\r\n in libxml2 when handling elements. An attacker could use this\r\n to specially construct XML data that could cause a denial of\r\n service. (CVE-2017-9048)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered multiple buffer overreads\r\n in libxml2 when handling parameter-entity references. An attacker\r\n could use these to specially construct XML data that could cause a\r\n denial of service. (CVE-2017-9049, CVE-2017-9050)",
+ "cve": "rails/Gemfile.lock:nokogiri:gemnasium:06565b64-486d-4326-b906-890d9915804d",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
+ "value": "06565b64-486d-4326-b906-890d9915804d",
+ "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
+ },
+ {
+ "type": "usn",
+ "name": "USN-3424-1",
+ "value": "USN-3424-1",
+ "url": "https://usn.ubuntu.com/3424-1/"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Infinite recursion in parameter entities",
+ "message": "Infinite recursion in parameter entities in nokogiri",
+ "description": "libxml2 incorrectly handles certain parameter entities. An attacker can leverage this with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.",
+ "cve": "rails/Gemfile.lock:nokogiri:gemnasium:6a0d56f6-2441-492a-9b14-edb95ac31919",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-6a0d56f6-2441-492a-9b14-edb95ac31919",
+ "value": "6a0d56f6-2441-492a-9b14-edb95ac31919",
+ "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2017-16932",
+ "value": "CVE-2017-16932",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932"
+ },
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1714"
+ },
+ {
+ "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-16932.html"
+ },
+ {
+ "url": "https://usn.ubuntu.com/usn/usn-3504-1/"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Denial of Service",
+ "message": "Denial of Service in nokogiri",
+ "description": "libxml2 incorrectly handles certain files. An attacker can use this issue with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.\r\n\r\n",
+ "cve": "rails/Gemfile.lock:nokogiri:gemnasium:78658378-bd8f-4d79-81c8-07c419302426",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-78658378-bd8f-4d79-81c8-07c419302426",
+ "value": "78658378-bd8f-4d79-81c8-07c419302426",
+ "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2017-15412",
+ "value": "CVE-2017-15412",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412"
+ },
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1714"
+ },
+ {
+ "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-15412.html"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Bypass of a protection mechanism in libxslt",
+ "message": "Bypass of a protection mechanism in libxslt in nokogiri",
+ "description": "libxslt through 1.1.33 allows bypass of a protection mechanism because callers of xsltCheckRead and xsltCheckWrite permit access even upon receiving a -1 error code. xsltCheckRead can return -1 for a crafted URL that is not actually invalid and is subsequently loaded. Vendored version of libxslt has been patched to remediate this vulnerability. Note that this patch is not yet (as of 2019-04-22) in an upstream release of libxslt.",
+ "cve": "rails/Gemfile.lock:nokogiri:gemnasium:1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version if using vendored version of libxslt OR update the system library libxslt to a fixed version",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
+ "value": "1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
+ "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2019-11068",
+ "value": "CVE-2019-11068",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068"
+ },
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1892"
+ },
+ {
+ "url": "https://people.canonical.com/~ubuntu-security/cve/CVE-2019-11068"
+ },
+ {
+ "url": "https://security-tracker.debian.org/tracker/CVE-2019-11068"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Regular Expression Denial of Service",
+ "message": "Regular Expression Denial of Service in debug",
+ "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
+ "cve": "yarn/yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest versions.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "yarn/yarn.lock",
+ "dependency": {
+ "package": {
+ "name": "debug"
+ },
+ "version": "1.0.5"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
+ "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/visionmedia/debug/issues/501"
+ },
+ {
+ "url": "https://github.com/visionmedia/debug/pull/504"
+ },
+ {
+ "url": "https://nodesecurity.io/advisories/534"
+ }
+ ]
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
+ "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
+ "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
+ "cve": "yarn/yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
+ "severity": "Unknown",
+ "solution": "Upgrade to fixed version.\r\n",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "yarn/yarn.lock",
+ "dependency": {
+ "package": {
+ "name": "saml2-js"
+ },
+ "version": "1.5.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
+ "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
+ "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2017-11429",
+ "value": "CVE-2017-11429",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
+ },
+ {
+ "url": "https://github.com/Clever/saml2/issues/127"
+ },
+ {
+ "url": "https://www.kb.cert.org/vuls/id/475445"
+ }
+ ]
+ }
+ ],
+ "remediations": [],
+ "dependency_files": [
+ {
+ "path": "rails/Gemfile.lock",
+ "package_manager": "bundler",
+ "dependencies": [
+ {
+ "package": {
+ "name": "mini_portile2"
+ },
+ "version": "2.2.0"
+ },
+ {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ ]
+ },
+ {
+ "path": "yarn/yarn.lock",
+ "package_manager": "yarn",
+ "dependencies": [
+ {
+ "package": {
+ "name": "async"
+ },
+ "version": "0.2.10"
+ },
+ {
+ "package": {
+ "name": "async"
+ },
+ "version": "1.5.2"
+ },
+ {
+ "package": {
+ "name": "debug"
+ },
+ "version": "1.0.5"
+ },
+ {
+ "package": {
+ "name": "ejs"
+ },
+ "version": "0.8.8"
+ },
+ {
+ "package": {
+ "name": "ms"
+ },
+ "version": "2.0.0"
+ },
+ {
+ "package": {
+ "name": "node-forge"
+ },
+ "version": "0.2.24"
+ },
+ {
+ "package": {
+ "name": "saml2-js"
+ },
+ "version": "1.5.0"
+ },
+ {
+ "package": {
+ "name": "sax"
+ },
+ "version": "1.2.4"
+ },
+ {
+ "package": {
+ "name": "underscore"
+ },
+ "version": "1.9.1"
+ },
+ {
+ "package": {
+ "name": "underscore"
+ },
+ "version": "1.6.0"
+ },
+ {
+ "package": {
+ "name": "xml-crypto"
+ },
+ "version": "0.8.5"
+ },
+ {
+ "package": {
+ "name": "xml-encryption"
+ },
+ "version": "0.7.4"
+ },
+ {
+ "package": {
+ "name": "xml2js"
+ },
+ "version": "0.4.19"
+ },
+ {
+ "package": {
+ "name": "xmlbuilder"
+ },
+ "version": "2.1.0"
+ },
+ {
+ "package": {
+ "name": "xmlbuilder"
+ },
+ "version": "9.0.7"
+ },
+ {
+ "package": {
+ "name": "xmldom"
+ },
+ "version": "0.1.19"
+ },
+ {
+ "package": {
+ "name": "xmldom"
+ },
+ "version": "0.1.27"
+ },
+ {
+ "package": {
+ "name": "xpath.js"
+ },
+ "version": "1.1.0"
+ },
+ {
+ "package": {
+ "name": "xpath"
+ },
+ "version": "0.0.5"
+ }
+ ]
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/master/gl-dast-report.json b/spec/fixtures/security-reports/master/gl-dast-report.json
index 3a308bf047e..df459d9419d 100644
--- a/spec/fixtures/security-reports/master/gl-dast-report.json
+++ b/spec/fixtures/security-reports/master/gl-dast-report.json
@@ -1,40 +1,42 @@
{
- "site": {
- "alerts": [
- {
- "sourceid": "3",
- "wascid": "15",
- "cweid": "16",
- "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
- "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
- "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
- "count": "2",
- "pluginid": "10021",
- "alert": "X-Content-Type-Options Header Missing",
- "name": "X-Content-Type-Options Header Missing",
- "riskcode": "1",
- "confidence": "2",
- "riskdesc": "Low (Medium)",
- "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
- "instances": [
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
- }
- ]
- }
- ],
- "@ssl": "false",
- "@port": "80",
- "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
- "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
+ "site": [
+ {
+ "alerts": [
+ {
+ "sourceid": "3",
+ "wascid": "15",
+ "cweid": "16",
+ "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
+ "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
+ "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
+ "count": "2",
+ "pluginid": "10021",
+ "alert": "X-Content-Type-Options Header Missing",
+ "name": "X-Content-Type-Options Header Missing",
+ "riskcode": "1",
+ "confidence": "2",
+ "riskdesc": "Low (Medium)",
+ "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
+ "instances": [
+ {
+ "param": "X-Content-Type-Options",
+ "method": "GET",
+ "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
+ },
+ {
+ "param": "X-Content-Type-Options",
+ "method": "GET",
+ "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
+ }
+ ]
+ }
+ ],
+ "@ssl": "false",
+ "@port": "80",
+ "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
+ "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
+ }
+ ],
"@generated": "Fri, 13 Apr 2018 09:22:01",
"@version": "2.7.0"
}
diff --git a/spec/javascripts/activities_spec.js b/spec/frontend/activities_spec.js
index 23b6de7e4e0..d14be3a1f26 100644
--- a/spec/javascripts/activities_spec.js
+++ b/spec/frontend/activities_spec.js
@@ -1,7 +1,6 @@
/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow */
import $ from 'jquery';
-import 'vendor/jquery.endless-scroll';
import Activities from '~/activities';
import Pager from '~/pager';
@@ -40,7 +39,7 @@ describe('Activities', () => {
beforeEach(() => {
loadFixtures(fixtureTemplate);
- spyOn(Pager, 'init').and.stub();
+ jest.spyOn(Pager, 'init').mockImplementation(() => {});
new Activities();
});
diff --git a/spec/javascripts/api_spec.js b/spec/frontend/api_spec.js
index 805bb10bda6..6010488d9e0 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -264,7 +264,7 @@ describe('Api', () => {
const namespace = 'some namespace';
const project = 'some project';
const labelData = { some: 'data' };
- const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/labels`;
+ const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/-/labels`;
const expectedData = {
label: labelData,
};
@@ -459,7 +459,7 @@ describe('Api', () => {
dummyProjectPath,
)}/repository/branches`;
- spyOn(axios, 'post').and.callThrough();
+ jest.spyOn(axios, 'post');
mock.onPost(expectedUrl).replyOnce(200, {
name: branch,
diff --git a/spec/javascripts/autosave_spec.js b/spec/frontend/autosave_spec.js
index dcb1c781591..4d9c8f96d62 100644
--- a/spec/javascripts/autosave_spec.js
+++ b/spec/frontend/autosave_spec.js
@@ -1,16 +1,19 @@
import $ from 'jquery';
import Autosave from '~/autosave';
import AccessorUtilities from '~/lib/utils/accessor';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
describe('Autosave', () => {
+ useLocalStorageSpy();
+
let autosave;
const field = $('<textarea></textarea>');
const key = 'key';
describe('class constructor', () => {
beforeEach(() => {
- spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').and.returnValue(true);
- spyOn(Autosave.prototype, 'restore');
+ jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true);
+ jest.spyOn(Autosave.prototype, 'restore').mockImplementation(() => {});
});
it('should set .isLocalStorageAvailable', () => {
@@ -27,8 +30,6 @@ describe('Autosave', () => {
field,
key,
};
-
- spyOn(window.localStorage, 'getItem');
});
describe('if .isLocalStorageAvailable is `false`', () => {
@@ -55,7 +56,7 @@ describe('Autosave', () => {
});
it('triggers jquery event', () => {
- spyOn(autosave.field, 'trigger').and.callThrough();
+ jest.spyOn(autosave.field, 'trigger').mockImplementation(() => {});
Autosave.prototype.restore.call(autosave);
@@ -77,7 +78,7 @@ describe('Autosave', () => {
});
it('does not trigger event', () => {
- spyOn(field, 'trigger').and.callThrough();
+ jest.spyOn(field, 'trigger');
expect(field.trigger).not.toHaveBeenCalled();
});
@@ -86,11 +87,9 @@ describe('Autosave', () => {
describe('save', () => {
beforeEach(() => {
- autosave = jasmine.createSpyObj('autosave', ['reset']);
+ autosave = { reset: jest.fn() };
autosave.field = field;
field.val('value');
-
- spyOn(window.localStorage, 'setItem');
});
describe('if .isLocalStorageAvailable is `false`', () => {
@@ -123,8 +122,6 @@ describe('Autosave', () => {
autosave = {
key,
};
-
- spyOn(window.localStorage, 'removeItem');
});
describe('if .isLocalStorageAvailable is `false`', () => {
diff --git a/spec/frontend/boards/modal_store_spec.js b/spec/frontend/boards/modal_store_spec.js
index 3257a3fb8a3..4dd27e94d97 100644
--- a/spec/frontend/boards/modal_store_spec.js
+++ b/spec/frontend/boards/modal_store_spec.js
@@ -1,7 +1,7 @@
/* global ListIssue */
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import Store from '~/boards/stores/modal_store';
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index 73897107f67..6de06a9e2d5 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -1,5 +1,10 @@
import Clusters from '~/clusters/clusters_bundle';
-import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX, APPLICATIONS } from '~/clusters/constants';
+import {
+ APPLICATION_STATUS,
+ INGRESS_DOMAIN_SUFFIX,
+ APPLICATIONS,
+ RUNNER,
+} from '~/clusters/constants';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { loadHTMLFixture } from 'helpers/fixtures';
@@ -209,6 +214,22 @@ describe('Clusters', () => {
expect(cluster.errorContainer.classList.contains('hidden')).toBeFalsy();
});
});
+
+ describe('when cluster is unreachable', () => {
+ it('should show the unreachable warning container', () => {
+ cluster.updateContainer(null, 'unreachable');
+
+ expect(cluster.unreachableContainer.classList.contains('hidden')).toBe(false);
+ });
+ });
+
+ describe('when cluster has an authentication failure', () => {
+ it('should show the authentication failure warning container', () => {
+ cluster.updateContainer(null, 'authentication_failure');
+
+ expect(cluster.authenticationFailureContainer.classList.contains('hidden')).toBe(false);
+ });
+ });
});
describe('installApplication', () => {
@@ -337,4 +358,30 @@ describe('Clusters', () => {
});
});
});
+
+ describe('updateApplication', () => {
+ const params = { version: '1.0.0' };
+ let storeUpdateApplication;
+ let installApplication;
+
+ beforeEach(() => {
+ storeUpdateApplication = jest.spyOn(cluster.store, 'updateApplication');
+ installApplication = jest.spyOn(cluster.service, 'installApplication');
+
+ cluster.updateApplication({ id: RUNNER, params });
+ });
+
+ afterEach(() => {
+ storeUpdateApplication.mockRestore();
+ installApplication.mockRestore();
+ });
+
+ it('calls store updateApplication method', () => {
+ expect(storeUpdateApplication).toHaveBeenCalledWith(RUNNER);
+ });
+
+ it('sends installApplication request', () => {
+ expect(installApplication).toHaveBeenCalledWith(RUNNER, params);
+ });
+ });
});
diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js
index 7c781b72355..9f127ccb690 100644
--- a/spec/frontend/clusters/components/application_row_spec.js
+++ b/spec/frontend/clusters/components/application_row_spec.js
@@ -245,26 +245,26 @@ describe('Application Row', () => {
});
});
- describe('Upgrade button', () => {
+ describe('Update button', () => {
it('has indeterminate state on page load', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: null,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- expect(upgradeBtn).toBe(null);
+ expect(updateBtn).toBe(null);
});
- it('has enabled "Upgrade" when "upgradeAvailable" is true', () => {
+ it('has enabled "Update" when "updateAvailable" is true', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- upgradeAvailable: true,
+ updateAvailable: true,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- expect(upgradeBtn).not.toBe(null);
- expect(upgradeBtn.innerHTML).toContain('Upgrade');
+ expect(updateBtn).not.toBe(null);
+ expect(updateBtn.innerHTML).toContain('Update');
});
it('has enabled "Retry update" when update process fails', () => {
@@ -273,10 +273,10 @@ describe('Application Row', () => {
status: APPLICATION_STATUS.INSTALLED,
updateFailed: true,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- expect(upgradeBtn).not.toBe(null);
- expect(upgradeBtn.innerHTML).toContain('Retry update');
+ expect(updateBtn).not.toBe(null);
+ expect(updateBtn.innerHTML).toContain('Retry update');
});
it('has disabled "Updating" when APPLICATION_STATUS.UPDATING', () => {
@@ -284,53 +284,51 @@ describe('Application Row', () => {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_STATUS.UPDATING,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- expect(upgradeBtn).not.toBe(null);
- expect(vm.isUpgrading).toBe(true);
- expect(upgradeBtn.innerHTML).toContain('Updating');
+ expect(updateBtn).not.toBe(null);
+ expect(vm.isUpdating).toBe(true);
+ expect(updateBtn.innerHTML).toContain('Updating');
});
- it('clicking upgrade button emits event', () => {
+ it('clicking update button emits event', () => {
jest.spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_STATUS.INSTALLED,
- upgradeAvailable: true,
+ updateAvailable: true,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- upgradeBtn.click();
+ updateBtn.click();
- expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', {
+ expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', {
id: DEFAULT_APPLICATION_STATE.id,
params: {},
});
});
- it('clicking disabled upgrade button emits nothing', () => {
+ it('clicking disabled update button emits nothing', () => {
jest.spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_STATUS.UPDATING,
});
- const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+ const updateBtn = vm.$el.querySelector('.js-cluster-application-update-button');
- upgradeBtn.click();
+ updateBtn.click();
expect(eventHub.$emit).not.toHaveBeenCalled();
});
- it('displays an error message if application upgrade failed', () => {
+ it('displays an error message if application update failed', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
title: 'GitLab Runner',
status: APPLICATION_STATUS.INSTALLED,
updateFailed: true,
});
- const failureMessage = vm.$el.querySelector(
- '.js-cluster-application-upgrade-failure-message',
- );
+ const failureMessage = vm.$el.querySelector('.js-cluster-application-update-details');
expect(failureMessage).not.toBe(null);
expect(failureMessage.innerHTML).toContain(
@@ -338,7 +336,7 @@ describe('Application Row', () => {
);
});
- it('displays a success toast message if application upgrade was successful', () => {
+ it('displays a success toast message if application update was successful', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
title: 'GitLab Runner',
@@ -349,13 +347,13 @@ describe('Application Row', () => {
vm.updateSuccessful = true;
return vm.$nextTick(() => {
- expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner upgraded successfully.');
+ expect(vm.$toast.show).toHaveBeenCalledWith('GitLab Runner updated successfully.');
});
});
});
describe('Version', () => {
- it('displays a version number if application has been upgraded', () => {
+ it('displays a version number if application has been updated', () => {
const version = '0.1.45';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
@@ -363,15 +361,15 @@ describe('Application Row', () => {
updateSuccessful: true,
version,
});
- const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
+ const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-update-version');
- expect(upgradeDetails.innerHTML).toContain('Upgraded');
+ expect(updateDetails.innerHTML).toContain('Updated');
expect(versionEl).not.toBe(null);
expect(versionEl.innerHTML).toContain(version);
});
- it('contains a link to the chart repo if application has been upgraded', () => {
+ it('contains a link to the chart repo if application has been updated', () => {
const version = '0.1.45';
const chartRepo = 'https://gitlab.com/charts/gitlab-runner';
vm = mountComponent(ApplicationRow, {
@@ -381,13 +379,13 @@ describe('Application Row', () => {
chartRepo,
version,
});
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-update-version');
expect(versionEl.href).toEqual(chartRepo);
expect(versionEl.target).toEqual('_blank');
});
- it('does not display a version number if application upgrade failed', () => {
+ it('does not display a version number if application update failed', () => {
const version = '0.1.45';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
@@ -395,10 +393,10 @@ describe('Application Row', () => {
updateFailed: true,
version,
});
- const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
- const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
+ const updateDetails = vm.$el.querySelector('.js-cluster-application-update-details');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-update-version');
- expect(upgradeDetails.innerHTML).toContain('failed');
+ expect(updateDetails.innerHTML).toContain('failed');
expect(versionEl).toBe(null);
});
});
diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js
index 8bcf02f0a34..221ebb143be 100644
--- a/spec/frontend/clusters/components/applications_spec.js
+++ b/spec/frontend/clusters/components/applications_spec.js
@@ -1,9 +1,11 @@
import Vue from 'vue';
import applications from '~/clusters/components/applications.vue';
import { CLUSTER_TYPE } from '~/clusters/constants';
-import eventHub from '~/clusters/event_hub';
import mountComponent from 'helpers/vue_mount_component_helper';
import { APPLICATIONS_MOCK_STATE } from '../services/mock_data';
+import eventHub from '~/clusters/event_hub';
+import { shallowMount } from '@vue/test-utils';
+import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
describe('Applications', () => {
let vm;
@@ -277,73 +279,48 @@ describe('Applications', () => {
});
describe('Knative application', () => {
- describe('when installed', () => {
- describe('with ip address', () => {
- const props = {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- knative: {
- title: 'Knative',
- hostname: 'example.com',
- status: 'installed',
- externalIp: '1.1.1.1',
- },
- },
- };
- it('renders ip address with a clipboard button', () => {
- vm = mountComponent(Applications, props);
+ const propsData = {
+ applications: {
+ ...APPLICATIONS_MOCK_STATE,
+ knative: {
+ title: 'Knative',
+ hostname: 'example.com',
+ status: 'installed',
+ externalIp: '1.1.1.1',
+ installed: true,
+ },
+ },
+ };
+ const newHostname = 'newhostname.com';
+ let wrapper;
+ let knativeDomainEditor;
- expect(vm.$el.querySelector('.js-knative-endpoint').value).toEqual('1.1.1.1');
-
- expect(
- vm.$el
- .querySelector('.js-knative-endpoint-clipboard-btn')
- .getAttribute('data-clipboard-text'),
- ).toEqual('1.1.1.1');
- });
-
- it('renders domain & allows editing', () => {
- expect(vm.$el.querySelector('.js-knative-domainname').value).toEqual('example.com');
- expect(vm.$el.querySelector('.js-knative-domainname').getAttribute('readonly')).toBe(
- null,
- );
- });
-
- it('renders an update/save Knative domain button', () => {
- expect(vm.$el.querySelector('.js-knative-save-domain-button')).not.toBe(null);
- });
+ beforeEach(() => {
+ wrapper = shallowMount(Applications, { propsData });
+ jest.spyOn(eventHub, '$emit');
- it('emits event when clicking Save changes button', () => {
- jest.spyOn(eventHub, '$emit');
- vm = mountComponent(Applications, props);
+ knativeDomainEditor = wrapper.find(KnativeDomainEditor);
+ });
- const saveButton = vm.$el.querySelector('.js-knative-save-domain-button');
+ afterEach(() => {
+ wrapper.destroy();
+ });
- saveButton.click();
+ it('emits saveKnativeDomain event when knative domain editor emits save event', () => {
+ knativeDomainEditor.vm.$emit('save', newHostname);
- expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', {
- id: 'knative',
- params: { hostname: 'example.com' },
- });
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', {
+ id: 'knative',
+ params: { hostname: newHostname },
});
+ });
- describe('without ip address', () => {
- it('renders an input text with a loading icon and an alert text', () => {
- vm = mountComponent(Applications, {
- applications: {
- ...APPLICATIONS_MOCK_STATE,
- knative: {
- title: 'Knative',
- hostname: 'example.com',
- status: 'installed',
- },
- },
- });
+ it('emits setKnativeHostname event when knative domain editor emits change event', () => {
+ wrapper.find(KnativeDomainEditor).vm.$emit('set', newHostname);
- expect(vm.$el.querySelector('.js-knative-ip-loading-icon')).not.toBe(null);
- expect(vm.$el.querySelector('.js-no-knative-endpoint-message')).not.toBe(null);
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('setKnativeHostname', {
+ id: 'knative',
+ hostname: newHostname,
});
});
});
diff --git a/spec/frontend/clusters/components/knative_domain_editor_spec.js b/spec/frontend/clusters/components/knative_domain_editor_spec.js
new file mode 100644
index 00000000000..242b5701f8b
--- /dev/null
+++ b/spec/frontend/clusters/components/knative_domain_editor_spec.js
@@ -0,0 +1,141 @@
+import { shallowMount } from '@vue/test-utils';
+import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import { APPLICATION_STATUS } from '~/clusters/constants';
+
+const { UPDATING } = APPLICATION_STATUS;
+
+describe('KnativeDomainEditor', () => {
+ let wrapper;
+ let knative;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(KnativeDomainEditor, {
+ propsData: { ...props },
+ });
+ };
+
+ beforeEach(() => {
+ knative = {
+ title: 'Knative',
+ hostname: 'example.com',
+ installed: true,
+ };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('knative has an assigned IP address', () => {
+ beforeEach(() => {
+ knative.externalIp = '1.1.1.1';
+ createComponent({ knative });
+ });
+
+ it('renders ip address with a clipboard button', () => {
+ expect(wrapper.find('.js-knative-endpoint').exists()).toBe(true);
+ expect(wrapper.find('.js-knative-endpoint').element.value).toEqual(knative.externalIp);
+ });
+
+ it('displays ip address clipboard button', () => {
+ expect(wrapper.find('.js-knative-endpoint-clipboard-btn').attributes('text')).toEqual(
+ knative.externalIp,
+ );
+ });
+
+ it('renders domain & allows editing', () => {
+ const domainNameInput = wrapper.find('.js-knative-domainname');
+
+ expect(domainNameInput.element.value).toEqual(knative.hostname);
+ expect(domainNameInput.attributes('readonly')).toBeFalsy();
+ });
+
+ it('renders an update/save Knative domain button', () => {
+ expect(wrapper.find('.js-knative-save-domain-button').exists()).toBe(true);
+ });
+ });
+
+ describe('knative without ip address', () => {
+ beforeEach(() => {
+ knative.externalIp = null;
+ createComponent({ knative });
+ });
+
+ it('renders an input text with a loading icon', () => {
+ expect(wrapper.find('.js-knative-ip-loading-icon').exists()).toBe(true);
+ });
+
+ it('renders message indicating there is not IP address assigned', () => {
+ expect(wrapper.find('.js-no-knative-endpoint-message').exists()).toBe(true);
+ });
+ });
+
+ describe('clicking save changes button', () => {
+ beforeEach(() => {
+ createComponent({ knative });
+ });
+
+ it('triggers save event and pass current knative hostname', () => {
+ wrapper.find(LoadingButton).vm.$emit('click');
+ expect(wrapper.emitted('save')[0]).toEqual([knative.hostname]);
+ });
+ });
+
+ describe('when knative domain name was saved successfully', () => {
+ beforeEach(() => {
+ createComponent({ knative });
+ });
+
+ it('displays toast indicating a successful update', () => {
+ wrapper.vm.$toast = { show: jest.fn() };
+ wrapper.setProps({ knative: Object.assign({ updateSuccessful: true }, knative) });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(
+ 'Knative domain name was updated successfully.',
+ );
+ });
+ });
+ });
+
+ describe('when knative domain name input changes', () => {
+ it('emits "set" event with updated domain name', () => {
+ const newHostname = 'newhostname.com';
+
+ wrapper.setData({ knativeHostname: newHostname });
+
+ expect(wrapper.emitted('set')[0]).toEqual([newHostname]);
+ });
+ });
+
+ describe('when updating knative domain name failed', () => {
+ beforeEach(() => {
+ createComponent({ knative });
+ });
+
+ it('displays an error banner indicating the operation failure', () => {
+ wrapper.setProps({ knative: { updateFailed: true, ...knative } });
+
+ expect(wrapper.find('.js-cluster-knative-domain-name-failure-message').exists()).toBe(true);
+ });
+ });
+
+ describe(`when knative status is ${UPDATING}`, () => {
+ beforeEach(() => {
+ createComponent({ knative: { status: UPDATING, ...knative } });
+ });
+
+ it('renders loading spinner in save button', () => {
+ expect(wrapper.find(LoadingButton).props('loading')).toBe(true);
+ });
+
+ it('renders disabled save button', () => {
+ expect(wrapper.find(LoadingButton).props('disabled')).toBe(true);
+ });
+
+ it('renders save button with "Saving" label', () => {
+ expect(wrapper.find(LoadingButton).props('label')).toBe('Saving');
+ });
+ });
+});
diff --git a/spec/frontend/clusters/services/application_state_machine_spec.js b/spec/frontend/clusters/services/application_state_machine_spec.js
index e057e2ac955..c146ef79be7 100644
--- a/spec/frontend/clusters/services/application_state_machine_spec.js
+++ b/spec/frontend/clusters/services/application_state_machine_spec.js
@@ -127,7 +127,7 @@ describe('applicationStateMachine', () => {
describe(`current state is ${UPDATING}`, () => {
it.each`
expectedState | event | effects
- ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true, updateAcknowledged: false }}
+ ${INSTALLED} | ${UPDATED} | ${{ updateSuccessful: true }}
${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
`(`transitions to $expectedState on $event event and applies $effects`, data => {
const { expectedState, event, effects } = data;
diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js
index aa926bb36d7..f2cc413512d 100644
--- a/spec/frontend/clusters/stores/clusters_store_spec.js
+++ b/spec/frontend/clusters/stores/clusters_store_spec.js
@@ -85,11 +85,10 @@ describe('Clusters Store', () => {
statusReason: mockResponseData.applications[2].status_reason,
requestReason: null,
version: mockResponseData.applications[2].version,
- upgradeAvailable: mockResponseData.applications[2].update_available,
+ updateAvailable: mockResponseData.applications[2].update_available,
chartRepo: 'https://gitlab.com/charts/gitlab-runner',
installed: false,
installFailed: false,
- updateAcknowledged: true,
updateFailed: false,
updateSuccessful: false,
uninstallable: false,
@@ -133,6 +132,8 @@ describe('Clusters Store', () => {
uninstallable: false,
uninstallSuccessful: false,
uninstallFailed: false,
+ updateSuccessful: false,
+ updateFailed: false,
},
cert_manager: {
title: 'Cert-Manager',
diff --git a/spec/frontend/helpers/jest_helpers.js b/spec/frontend/helpers/jest_helpers.js
new file mode 100644
index 00000000000..4a150be9935
--- /dev/null
+++ b/spec/frontend/helpers/jest_helpers.js
@@ -0,0 +1,24 @@
+/* eslint-disable import/prefer-default-export */
+
+/*
+@module
+
+This method provides convenience functions to help migrating from Karma/Jasmine to Jest.
+
+Try not to use these in new tests - this module is provided primarily for convenience of migrating tests.
+ */
+
+/**
+ * Creates a plain JS object pre-populated with Jest spy functions. Useful for making simple mocks classes.
+ *
+ * @see https://jasmine.github.io/2.0/introduction.html#section-Spies:_%3Ccode%3EcreateSpyObj%3C/code%3E
+ * @param {string} baseName Human-readable name of the object. This is used for reporting purposes.
+ * @param methods {string[]} List of method names that will be added to the spy object.
+ */
+export function createSpyObj(baseName, methods) {
+ const obj = {};
+ methods.forEach(method => {
+ obj[method] = jest.fn().mockName(`${baseName}#${method}`);
+ });
+ return obj;
+}
diff --git a/spec/frontend/helpers/jquery.js b/spec/frontend/helpers/jquery.js
new file mode 100644
index 00000000000..6421a592c0c
--- /dev/null
+++ b/spec/frontend/helpers/jquery.js
@@ -0,0 +1,6 @@
+import $ from 'jquery';
+
+global.$ = $;
+global.jQuery = $;
+
+export default $;
diff --git a/spec/frontend/helpers/local_storage_helper.js b/spec/frontend/helpers/local_storage_helper.js
new file mode 100644
index 00000000000..48e66b11767
--- /dev/null
+++ b/spec/frontend/helpers/local_storage_helper.js
@@ -0,0 +1,41 @@
+/**
+ * Manage the instance of a custom `window.localStorage`
+ *
+ * This only encapsulates the setup / teardown logic so that it can easily be
+ * reused with different implementations (i.e. a spy or a [fake][1])
+ *
+ * [1]: https://stackoverflow.com/a/41434763/1708147
+ *
+ * @param {() => any} fn Function that returns the object to use for localStorage
+ */
+const useLocalStorage = fn => {
+ const origLocalStorage = window.localStorage;
+ let currentLocalStorage;
+
+ Object.defineProperty(window, 'localStorage', {
+ get: () => currentLocalStorage,
+ });
+
+ beforeEach(() => {
+ currentLocalStorage = fn();
+ });
+
+ afterEach(() => {
+ currentLocalStorage = origLocalStorage;
+ });
+};
+
+/**
+ * Create an object with the localStorage interface but `jest.fn()` implementations.
+ */
+export const createLocalStorageSpy = () => ({
+ clear: jest.fn(),
+ getItem: jest.fn(),
+ setItem: jest.fn(),
+ removeItem: jest.fn(),
+});
+
+/**
+ * Before each test, overwrite `window.localStorage` with a spy implementation.
+ */
+export const useLocalStorageSpy = () => useLocalStorage(createLocalStorageSpy);
diff --git a/spec/frontend/helpers/timeout.js b/spec/frontend/helpers/timeout.js
index b30b7f1ce1e..702ef0be5aa 100644
--- a/spec/frontend/helpers/timeout.js
+++ b/spec/frontend/helpers/timeout.js
@@ -1,13 +1,33 @@
const NS_PER_SEC = 1e9;
const NS_PER_MS = 1e6;
+const IS_DEBUGGING = process.execArgv.join(' ').includes('--inspect-brk');
let testTimeoutNS;
export const setTestTimeout = newTimeoutMS => {
- testTimeoutNS = newTimeoutMS * NS_PER_MS;
+ const newTimeoutNS = newTimeoutMS * NS_PER_MS;
+ // never accept a smaller timeout than the default
+ if (newTimeoutNS < testTimeoutNS) {
+ return;
+ }
+
+ testTimeoutNS = newTimeoutNS;
jest.setTimeout(newTimeoutMS);
};
+// Allows slow tests to set their own timeout.
+// Useful for tests with jQuery, which is very slow in big DOMs.
+let temporaryTimeoutNS = null;
+export const setTestTimeoutOnce = newTimeoutMS => {
+ const newTimeoutNS = newTimeoutMS * NS_PER_MS;
+ // never accept a smaller timeout than the default
+ if (newTimeoutNS < testTimeoutNS) {
+ return;
+ }
+
+ temporaryTimeoutNS = newTimeoutNS;
+};
+
export const initializeTestTimeout = defaultTimeoutMS => {
setTestTimeout(defaultTimeoutMS);
@@ -19,12 +39,20 @@ export const initializeTestTimeout = defaultTimeoutMS => {
});
afterEach(() => {
+ let timeoutNS = testTimeoutNS;
+ if (Number.isFinite(temporaryTimeoutNS)) {
+ timeoutNS = temporaryTimeoutNS;
+ temporaryTimeoutNS = null;
+ }
+
const [seconds, remainingNs] = process.hrtime(testStartTime);
const elapsedNS = seconds * NS_PER_SEC + remainingNs;
- if (elapsedNS > testTimeoutNS) {
+ // Disable the timeout error when debugging. It is meaningless because
+ // debugging always takes longer than the test timeout.
+ if (elapsedNS > timeoutNS && !IS_DEBUGGING) {
throw new Error(
- `Test took too long (${elapsedNS / NS_PER_MS}ms > ${testTimeoutNS / NS_PER_MS}ms)!`,
+ `Test took too long (${elapsedNS / NS_PER_MS}ms > ${timeoutNS / NS_PER_MS}ms)!`,
);
}
});
diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js
index 19e27388eeb..121e99c9783 100644
--- a/spec/frontend/helpers/vue_test_utils_helper.js
+++ b/spec/frontend/helpers/vue_test_utils_helper.js
@@ -16,4 +16,6 @@ const vNodeContainsText = (vnode, text) =>
* @param {String} text
*/
export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =>
- !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length;
+ Boolean(
+ shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
+ );
diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
index 40d47aaad03..246500a2f34 100644
--- a/spec/frontend/ide/stores/modules/commit/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
@@ -54,5 +54,20 @@ describe('IDE commit module mutations', () => {
expect(state.shouldCreateMR).toBe(false);
});
+
+ it('sets shouldCreateMR to given value when passed in', () => {
+ state.shouldCreateMR = false;
+ mutations.TOGGLE_SHOULD_CREATE_MR(state, false);
+
+ expect(state.shouldCreateMR).toBe(false);
+ });
+ });
+
+ describe('INTERACT_WITH_NEW_MR', () => {
+ it('sets interactedWithNewMR to true', () => {
+ mutations.INTERACT_WITH_NEW_MR(state);
+
+ expect(state.interactedWithNewMR).toBe(true);
+ });
});
});
diff --git a/spec/frontend/ide/stores/mutations/branch_spec.js b/spec/frontend/ide/stores/mutations/branch_spec.js
index 29eb859ddaf..0900b25d5d3 100644
--- a/spec/frontend/ide/stores/mutations/branch_spec.js
+++ b/spec/frontend/ide/stores/mutations/branch_spec.js
@@ -37,4 +37,39 @@ describe('Multi-file store branch mutations', () => {
expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit');
});
});
+
+ describe('SET_BRANCH_WORKING_REFERENCE', () => {
+ beforeEach(() => {
+ localState.projects = {
+ Foo: {
+ branches: {
+ bar: {},
+ },
+ },
+ };
+ });
+
+ it('sets workingReference for existing branch', () => {
+ mutations.SET_BRANCH_WORKING_REFERENCE(localState, {
+ projectId: 'Foo',
+ branchId: 'bar',
+ reference: 'foo-bar-ref',
+ });
+
+ expect(localState.projects.Foo.branches.bar.workingReference).toBe('foo-bar-ref');
+ });
+
+ it('does not fail on non-existent just yet branch', () => {
+ expect(localState.projects.Foo.branches.unknown).toBeUndefined();
+
+ mutations.SET_BRANCH_WORKING_REFERENCE(localState, {
+ projectId: 'Foo',
+ branchId: 'unknown',
+ reference: 'fun-fun-ref',
+ });
+
+ expect(localState.projects.Foo.branches.unknown).not.toBeUndefined();
+ expect(localState.projects.Foo.branches.unknown.workingReference).toBe('fun-fun-ref');
+ });
+ });
});
diff --git a/spec/frontend/ide/stores/mutations/project_spec.js b/spec/frontend/ide/stores/mutations/project_spec.js
new file mode 100644
index 00000000000..b3ce39c33d2
--- /dev/null
+++ b/spec/frontend/ide/stores/mutations/project_spec.js
@@ -0,0 +1,23 @@
+import mutations from '~/ide/stores/mutations/project';
+import state from '~/ide/stores/state';
+
+describe('Multi-file store branch mutations', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ localState.projects = { abcproject: { empty_repo: true } };
+ });
+
+ describe('TOGGLE_EMPTY_STATE', () => {
+ it('sets empty_repo for project to passed value', () => {
+ mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: 'abcproject', value: false });
+
+ expect(localState.projects.abcproject.empty_repo).toBe(false);
+
+ mutations.TOGGLE_EMPTY_STATE(localState, { projectPath: 'abcproject', value: true });
+
+ expect(localState.projects.abcproject.empty_repo).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js
index d7908efcf13..343301b8716 100644
--- a/spec/frontend/jobs/store/mutations_spec.js
+++ b/spec/frontend/jobs/store/mutations_spec.js
@@ -150,44 +150,8 @@ describe('Jobs Store Mutations', () => {
});
});
- describe('REQUEST_STAGES', () => {
- it('sets isLoadingStages to true', () => {
- mutations[types.REQUEST_STAGES](stateCopy);
-
- expect(stateCopy.isLoadingStages).toEqual(true);
- });
- });
-
- describe('RECEIVE_STAGES_SUCCESS', () => {
- beforeEach(() => {
- mutations[types.RECEIVE_STAGES_SUCCESS](stateCopy, [{ name: 'build' }]);
- });
-
- it('sets isLoadingStages to false', () => {
- expect(stateCopy.isLoadingStages).toEqual(false);
- });
-
- it('sets stages', () => {
- expect(stateCopy.stages).toEqual([{ name: 'build' }]);
- });
- });
-
- describe('RECEIVE_STAGES_ERROR', () => {
- beforeEach(() => {
- mutations[types.RECEIVE_STAGES_ERROR](stateCopy);
- });
-
- it('sets isLoadingStages to false', () => {
- expect(stateCopy.isLoadingStages).toEqual(false);
- });
-
- it('resets stages', () => {
- expect(stateCopy.stages).toEqual([]);
- });
- });
-
describe('REQUEST_JOBS_FOR_STAGE', () => {
- it('sets isLoadingStages to true', () => {
+ it('sets isLoadingJobs to true', () => {
mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
expect(stateCopy.isLoadingJobs).toEqual(true);
diff --git a/spec/javascripts/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index 5327ec9d2a0..9f49e68cfe8 100644
--- a/spec/javascripts/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -65,6 +65,26 @@ describe('Date time utils', () => {
});
});
+ describe('formatDate', () => {
+ it('should format date properly', () => {
+ const formattedDate = datetimeUtility.formatDate(new Date('07/23/2016'));
+
+ expect(formattedDate).toBe('Jul 23, 2016 12:00am GMT+0000');
+ });
+
+ it('should format ISO date properly', () => {
+ const formattedDate = datetimeUtility.formatDate('2016-07-23T00:00:00.559Z');
+
+ expect(formattedDate).toBe('Jul 23, 2016 12:00am GMT+0000');
+ });
+
+ it('should throw an error if date is invalid', () => {
+ expect(() => {
+ datetimeUtility.formatDate('2016-07-23 00:00:00 UTC');
+ }).toThrow(new Error('Invalid date'));
+ });
+ });
+
describe('get day difference', () => {
it('should return 7', () => {
const firstDay = new Date('07/01/2016');
@@ -380,7 +400,7 @@ describe('prettyTime methods', () => {
describe('calculateRemainingMilliseconds', () => {
beforeEach(() => {
- spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime());
+ jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
});
it('calculates the remaining time for a given end date', () => {
diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js
index 818404bad81..77d7478d317 100644
--- a/spec/frontend/lib/utils/number_utility_spec.js
+++ b/spec/frontend/lib/utils/number_utility_spec.js
@@ -5,6 +5,7 @@ import {
bytesToGiB,
numberToHumanSize,
sum,
+ isOdd,
} from '~/lib/utils/number_utils';
describe('Number Utils', () => {
@@ -98,4 +99,14 @@ describe('Number Utils', () => {
expect([1, 2, 3, 4, 5].reduce(sum)).toEqual(15);
});
});
+
+ describe('isOdd', () => {
+ it('should return 0 with a even number', () => {
+ expect(isOdd(2)).toEqual(0);
+ });
+
+ it('should return 1 with a odd number', () => {
+ expect(isOdd(1)).toEqual(1);
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 381c7b2d0a6..c771984a137 100644
--- a/spec/javascripts/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -1,5 +1,12 @@
import * as urlUtils from '~/lib/utils/url_utility';
+const setWindowLocation = value => {
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value,
+ });
+};
+
describe('URL utility', () => {
describe('webIDEUrl', () => {
afterEach(() => {
@@ -107,4 +114,113 @@ describe('URL utility', () => {
expect(url).toBe('/home/feature#install');
});
});
+
+ describe('getBaseURL', () => {
+ beforeEach(() => {
+ setWindowLocation({
+ protocol: 'https:',
+ host: 'gitlab.com',
+ });
+ });
+
+ it('returns correct base URL', () => {
+ expect(urlUtils.getBaseURL()).toBe('https://gitlab.com');
+ });
+ });
+
+ describe('isAbsoluteOrRootRelative', () => {
+ const validUrls = ['https://gitlab.com/', 'http://gitlab.com/', '/users/sign_in'];
+
+ const invalidUrls = [' https://gitlab.com/', './file/path', 'notanurl', '<a></a>'];
+
+ it.each(validUrls)(`returns true for %s`, url => {
+ expect(urlUtils.isAbsoluteOrRootRelative(url)).toBe(true);
+ });
+
+ it.each(invalidUrls)(`returns false for %s`, url => {
+ expect(urlUtils.isAbsoluteOrRootRelative(url)).toBe(false);
+ });
+ });
+
+ describe('isSafeUrl', () => {
+ const absoluteUrls = [
+ 'http://example.org',
+ 'http://example.org:8080',
+ 'https://example.org',
+ 'https://example.org:8080',
+ 'https://192.168.1.1',
+ ];
+
+ const rootRelativeUrls = ['/relative/link'];
+
+ const relativeUrls = ['./relative/link', '../relative/link'];
+
+ const urlsWithoutHost = ['http://', 'https://', 'https:https:https:'];
+
+ /* eslint-disable no-script-url */
+ const nonHttpUrls = [
+ 'javascript:',
+ 'javascript:alert("XSS")',
+ 'jav\tascript:alert("XSS");',
+ ' &#14; javascript:alert("XSS");',
+ 'ftp://192.168.1.1',
+ 'file:///',
+ 'file:///etc/hosts',
+ ];
+ /* eslint-enable no-script-url */
+
+ // javascript:alert('XSS')
+ const encodedJavaScriptUrls = [
+ '&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041',
+ '&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;',
+ '&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29',
+ '\\u006A\\u0061\\u0076\\u0061\\u0073\\u0063\\u0072\\u0069\\u0070\\u0074\\u003A\\u0061\\u006C\\u0065\\u0072\\u0074\\u0028\\u0027\\u0058\\u0053\\u0053\\u0027\\u0029',
+ ];
+
+ const safeUrls = [...absoluteUrls, ...rootRelativeUrls];
+ const unsafeUrls = [
+ ...relativeUrls,
+ ...urlsWithoutHost,
+ ...nonHttpUrls,
+ ...encodedJavaScriptUrls,
+ ];
+
+ describe('with URL constructor support', () => {
+ it.each(safeUrls)('returns true for %s', url => {
+ expect(urlUtils.isSafeURL(url)).toBe(true);
+ });
+
+ it.each(unsafeUrls)('returns false for %s', url => {
+ expect(urlUtils.isSafeURL(url)).toBe(false);
+ });
+ });
+ });
+
+ describe('getWebSocketProtocol', () => {
+ it.each`
+ protocol | expectation
+ ${'http:'} | ${'ws:'}
+ ${'https:'} | ${'wss:'}
+ `('returns "$expectation" with "$protocol" protocol', ({ protocol, expectation }) => {
+ setWindowLocation({
+ protocol,
+ host: 'example.com',
+ });
+
+ expect(urlUtils.getWebSocketProtocol()).toEqual(expectation);
+ });
+ });
+
+ describe('getWebSocketUrl', () => {
+ it('joins location host to path', () => {
+ setWindowLocation({
+ protocol: 'http:',
+ host: 'example.com',
+ });
+
+ const path = '/lorem/ipsum?a=bc';
+
+ expect(urlUtils.getWebSocketUrl(path)).toEqual('ws://example.com/lorem/ipsum?a=bc');
+ });
+ });
});
diff --git a/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap
index 5f9f13d591d..a2a7d0ee91e 100644
--- a/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap
+++ b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap
@@ -3,6 +3,7 @@
exports[`MR Popover loaded state matches the snapshot 1`] = `
<glpopover-stub
boundary="viewport"
+ cssclasses=""
placement="top"
show=""
target=""
@@ -61,6 +62,7 @@ exports[`MR Popover loaded state matches the snapshot 1`] = `
exports[`MR Popover shows skeleton-loader while apollo is loading 1`] = `
<glpopover-stub
boundary="viewport"
+ cssclasses=""
placement="top"
show=""
target=""
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/frontend/notes/components/note_app_spec.js
index ef876dc2941..ff833d2c899 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/frontend/notes/components/note_app_spec.js
@@ -1,18 +1,47 @@
-import $ from 'jquery';
-import _ from 'underscore';
+import $ from 'helpers/jquery';
import Vue from 'vue';
import { mount, createLocalVue } from '@vue/test-utils';
import NotesApp from '~/notes/components/notes_app.vue';
import service from '~/notes/services/notes_service';
import createStore from '~/notes/stores';
import '~/behaviors/markdown/render_gfm';
-import * as mockData from '../mock_data';
+import { setTestTimeout } from 'helpers/timeout';
+// TODO: use generated fixture (https://gitlab.com/gitlab-org/gitlab-ce/issues/62491)
+import * as mockData from '../../../javascripts/notes/mock_data';
+
+const originalInterceptors = [...Vue.http.interceptors];
+
+const emptyResponseInterceptor = (request, next) => {
+ next(
+ request.respondWith(JSON.stringify([]), {
+ status: 200,
+ }),
+ );
+};
+
+setTestTimeout(1000);
describe('note_app', () => {
let mountComponent;
let wrapper;
let store;
+ /**
+ * waits for fetchNotes() to complete
+ */
+ const waitForDiscussionsRequest = () =>
+ new Promise(resolve => {
+ const { vm } = wrapper.find(NotesApp);
+ const unwatch = vm.$watch('isFetching', isFetching => {
+ if (isFetching) {
+ return;
+ }
+
+ unwatch();
+ resolve();
+ });
+ });
+
beforeEach(() => {
$('body').attr('data-page', 'projects:merge_requests:show');
@@ -33,6 +62,7 @@ describe('note_app', () => {
template: '<div class="js-vue-notes-event"><notes-app v-bind="$attrs" /></div>',
},
{
+ attachToDocument: true,
propsData,
store,
localVue,
@@ -44,24 +74,14 @@ describe('note_app', () => {
afterEach(() => {
wrapper.destroy();
+ Vue.http.interceptors = [...originalInterceptors];
});
describe('set data', () => {
- const responseInterceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify([]), {
- status: 200,
- }),
- );
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(responseInterceptor);
+ Vue.http.interceptors.push(emptyResponseInterceptor);
wrapper = mountComponent();
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, responseInterceptor);
+ return waitForDiscussionsRequest();
});
it('should set notes data', () => {
@@ -87,29 +107,23 @@ describe('note_app', () => {
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
wrapper = mountComponent();
+ return waitForDiscussionsRequest();
});
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, mockData.individualNoteInterceptor);
- });
-
- it('should render list of notes', done => {
+ it('should render list of notes', () => {
const note =
mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[
'/gitlab-org/gitlab-ce/issues/26/discussions.json'
][0].notes[0];
- setTimeout(() => {
- expect(
- wrapper
- .find('.main-notes-list .note-header-author-name')
- .text()
- .trim(),
- ).toEqual(note.author.name);
+ expect(
+ wrapper
+ .find('.main-notes-list .note-header-author-name')
+ .text()
+ .trim(),
+ ).toEqual(note.author.name);
- expect(wrapper.find('.main-notes-list .note-text').html()).toContain(note.note_html);
- done();
- }, 0);
+ expect(wrapper.find('.main-notes-list .note-text').html()).toContain(note.note_html);
});
it('should render form', () => {
@@ -120,37 +134,42 @@ describe('note_app', () => {
});
it('should not render form when commenting is disabled', () => {
+ wrapper.destroy();
+
store.state.commentsDisabled = true;
wrapper = mountComponent();
-
- expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
+ return waitForDiscussionsRequest().then(() => {
+ expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
+ });
});
it('should render discussion filter note `commentsDisabled` is true', () => {
+ wrapper.destroy();
+
store.state.commentsDisabled = true;
wrapper = mountComponent();
-
- expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true);
+ return waitForDiscussionsRequest().then(() => {
+ expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true);
+ });
});
it('should render form comment button as disabled', () => {
expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled');
});
- it('updates discussions badge', done => {
- setTimeout(() => {
- expect(document.querySelector('.js-discussions-count').textContent).toEqual('2');
-
- done();
- });
+ it('updates discussions badge', () => {
+ expect(document.querySelector('.js-discussions-count').textContent).toEqual('2');
});
});
describe('while fetching data', () => {
beforeEach(() => {
+ Vue.http.interceptors.push(emptyResponseInterceptor);
wrapper = mountComponent();
});
+ afterEach(() => waitForDiscussionsRequest());
+
it('renders skeleton notes', () => {
expect(wrapper.find('.animation-container').exists()).toBe(true);
});
@@ -165,78 +184,55 @@ describe('note_app', () => {
describe('update note', () => {
describe('individual note', () => {
- beforeEach(done => {
+ beforeEach(() => {
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
- spyOn(service, 'updateNote').and.callThrough();
+ jest.spyOn(service, 'updateNote');
wrapper = mountComponent();
- setTimeout(() => {
+ return waitForDiscussionsRequest().then(() => {
wrapper.find('.js-note-edit').trigger('click');
- Vue.nextTick(done);
- }, 0);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors,
- mockData.individualNoteInterceptor,
- );
+ });
});
it('renders edit form', () => {
expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true);
});
- it('calls the service to update the note', done => {
+ it('calls the service to update the note', () => {
wrapper.find('.js-vue-issue-note-form').value = 'this is a note';
wrapper.find('.js-vue-issue-save').trigger('click');
expect(service.updateNote).toHaveBeenCalled();
- // Wait for the requests to finish before destroying
- setTimeout(() => {
- done();
- });
});
});
describe('discussion note', () => {
- beforeEach(done => {
+ beforeEach(() => {
Vue.http.interceptors.push(mockData.discussionNoteInterceptor);
- spyOn(service, 'updateNote').and.callThrough();
+ jest.spyOn(service, 'updateNote');
wrapper = mountComponent();
-
- setTimeout(() => {
+ return waitForDiscussionsRequest().then(() => {
wrapper.find('.js-note-edit').trigger('click');
- Vue.nextTick(done);
- }, 0);
- });
-
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors,
- mockData.discussionNoteInterceptor,
- );
+ });
});
it('renders edit form', () => {
expect(wrapper.find('.js-vue-issue-note-form').exists()).toBe(true);
});
- it('updates the note and resets the edit form', done => {
+ it('updates the note and resets the edit form', () => {
wrapper.find('.js-vue-issue-note-form').value = 'this is a note';
wrapper.find('.js-vue-issue-save').trigger('click');
expect(service.updateNote).toHaveBeenCalled();
- // Wait for the requests to finish before destroying
- setTimeout(() => {
- done();
- });
});
});
});
describe('new note form', () => {
beforeEach(() => {
+ Vue.http.interceptors.push(mockData.individualNoteInterceptor);
wrapper = mountComponent();
+ return waitForDiscussionsRequest();
});
it('should render markdown docs url', () => {
@@ -266,43 +262,37 @@ describe('note_app', () => {
beforeEach(() => {
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
wrapper = mountComponent();
+ return waitForDiscussionsRequest();
});
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, mockData.individualNoteInterceptor);
- });
+ it('should render markdown docs url', () => {
+ wrapper.find('.js-note-edit').trigger('click');
+ const { markdownDocsPath } = mockData.notesDataMock;
- it('should render markdown docs url', done => {
- setTimeout(() => {
- wrapper.find('.js-note-edit').trigger('click');
- const { markdownDocsPath } = mockData.notesDataMock;
-
- Vue.nextTick(() => {
- expect(
- wrapper
- .find(`.edit-note a[href="${markdownDocsPath}"]`)
- .text()
- .trim(),
- ).toEqual('Markdown is supported');
- done();
- });
- }, 0);
+ return Vue.nextTick().then(() => {
+ expect(
+ wrapper
+ .find(`.edit-note a[href="${markdownDocsPath}"]`)
+ .text()
+ .trim(),
+ ).toEqual('Markdown is supported');
+ });
});
- it('should not render quick actions docs url', done => {
- setTimeout(() => {
- wrapper.find('.js-note-edit').trigger('click');
- const { quickActionsDocsPath } = mockData.notesDataMock;
-
- Vue.nextTick(() => {
- expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false);
- done();
- });
- }, 0);
+ it('should not render quick actions docs url', () => {
+ wrapper.find('.js-note-edit').trigger('click');
+ const { quickActionsDocsPath } = mockData.notesDataMock;
+ expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false);
});
});
describe('emoji awards', () => {
+ beforeEach(() => {
+ Vue.http.interceptors.push(emptyResponseInterceptor);
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest();
+ });
+
it('dispatches toggleAward after toggleAward event', () => {
const toggleAwardEvent = new CustomEvent('toggleAward', {
detail: {
@@ -310,17 +300,18 @@ describe('note_app', () => {
noteId: 1,
},
});
- const toggleAwardAction = jasmine.createSpy('toggleAward');
+ const toggleAwardAction = jest.fn().mockName('toggleAward');
wrapper.vm.$store.hotUpdate({
actions: {
toggleAward: toggleAwardAction,
+ stopPolling() {},
},
});
wrapper.vm.$parent.$el.dispatchEvent(toggleAwardEvent);
expect(toggleAwardAction).toHaveBeenCalledTimes(1);
- const [, payload] = toggleAwardAction.calls.argsFor(0);
+ const [, payload] = toggleAwardAction.mock.calls[0];
expect(payload).toEqual({
awardName: 'test',
diff --git a/spec/javascripts/notes_spec.js b/spec/frontend/notes/old_notes_spec.js
index 394e3343be6..b57041cf4d1 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/frontend/notes/old_notes_spec.js
@@ -1,84 +1,89 @@
-/* eslint-disable no-unused-expressions, no-var, object-shorthand */
+/* eslint-disable import/no-commonjs, no-new */
+
import $ from 'jquery';
import _ from 'underscore';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
-import 'autosize';
-import '~/gl_form';
-import '~/lib/utils/text_utility';
+import * as urlUtility from '~/lib/utils/url_utility';
import '~/behaviors/markdown/render_gfm';
-import Notes from '~/notes';
-import timeoutPromise from './helpers/set_timeout_promise_helper';
-
-window.gon || (window.gon = {});
+import { createSpyObj } from 'helpers/jest_helpers';
+import { setTestTimeoutOnce } from 'helpers/timeout';
+import { TEST_HOST } from 'helpers/test_constants';
+
+// These must be imported synchronously because they pull dependencies
+// from the DOM.
+window.jQuery = $;
+require('autosize');
+require('~/commons');
+require('~/notes');
+
+const { Notes } = window;
+const FLASH_TYPE_ALERT = 'alert';
+const NOTES_POST_PATH = /(.*)\/notes\?html=true$/;
+const fixture = 'snippets/show.html';
+let mockAxios;
+
+window.project_uploads_path = `${TEST_HOST}/uploads`;
+window.gon = window.gon || {};
window.gl = window.gl || {};
gl.utils = gl.utils || {};
+gl.utils.disableButtonIfEmptyField = () => {};
-const htmlEscape = comment => {
- const escapedString = comment.replace(/["&'<>]/g, a => {
- const escapedToken = {
- '&': '&amp;',
- '<': '&lt;',
- '>': '&gt;',
- '"': '&quot;',
- "'": '&#x27;',
- '`': '&#x60;',
- }[a];
-
- return escapedToken;
- });
+describe('Old Notes (~/notes.js)', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ loadFixtures(fixture);
+
+ // Re-declare this here so that test_setup.js#beforeEach() doesn't
+ // overwrite it.
+ mockAxios = new MockAdapter(axios);
- return escapedString;
-};
+ $.ajax = () => {
+ throw new Error('$.ajax should not be called through!');
+ };
-describe('Notes', function() {
- const FLASH_TYPE_ALERT = 'alert';
- const NOTES_POST_PATH = /(.*)\/notes\?html=true$/;
- var fixture = 'snippets/show.html';
- preloadFixtures(fixture);
+ // These jQuery+DOM tests are super flaky so increase the timeout to avoid
+ // random failures.
+ // It seems that running tests in parallel increases failure rate.
+ jest.setTimeout(4000);
+ setTestTimeoutOnce(4000);
+ });
- beforeEach(function() {
- loadFixtures(fixture);
- gl.utils.disableButtonIfEmptyField = _.noop;
- window.project_uploads_path = 'http://test.host/uploads';
- $('body').attr('data-page', 'projects:merge_requets:show');
+ afterEach(done => {
+ // The Notes component sets a polling interval. Clear it after every run.
+ // Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers().
+ jest.clearAllTimers();
+
+ setImmediate(() => {
+ // Wait for any requests to resolve, otherwise we get failures about
+ // unmocked requests.
+ mockAxios.restore();
+ done();
+ });
});
- afterEach(() => {
- // Undo what we did to the shared <body>
- $('body').removeAttr('data-page');
+ it('loads the Notes class into the DOM', () => {
+ expect(Notes).toBeDefined();
+ expect(Notes.name).toBe('Notes');
});
describe('addBinding', () => {
it('calls postComment when comment button is clicked', () => {
- spyOn(Notes.prototype, 'postComment');
- this.notes = new Notes('', []);
+ jest.spyOn(Notes.prototype, 'postComment');
+ new window.Notes('', []);
$('.js-comment-button').click();
-
expect(Notes.prototype.postComment).toHaveBeenCalled();
});
});
- describe('task lists', function() {
- let mock;
-
- beforeEach(function() {
- spyOn(axios, 'patch').and.callFake(() => new Promise(() => {}));
- mock = new MockAdapter(axios);
- mock.onAny().reply(200, {});
-
- $('.js-comment-button').on('click', function(e) {
- e.preventDefault();
- });
- this.notes = new Notes('', []);
- });
-
- afterEach(() => {
- mock.restore();
+ describe('task lists', () => {
+ beforeEach(() => {
+ mockAxios.onAny().reply(200, {});
+ new Notes('', []);
});
- it('modifies the Markdown field', function() {
+ it('modifies the Markdown field', () => {
const changeEvent = document.createEvent('HTMLEvents');
changeEvent.initEvent('change', true, true);
$('input[type=checkbox]')
@@ -88,7 +93,9 @@ describe('Notes', function() {
expect($('.js-task-list-field.original-task-list').val()).toBe('- [x] Task List Item');
});
- it('submits an ajax request on tasklist:changed', function(done) {
+ it('submits an ajax request on tasklist:changed', () => {
+ jest.spyOn(axios, 'patch');
+
const lineNumber = 8;
const lineSource = '- [ ] item 8';
const index = 3;
@@ -99,76 +106,74 @@ describe('Notes', function() {
detail: { lineNumber, lineSource, index, checked },
});
- setTimeout(() => {
- expect(axios.patch).toHaveBeenCalledWith(undefined, {
- note: {
- note: '',
- lock_version: undefined,
- update_task: { index, checked, line_number: lineNumber, line_source: lineSource },
- },
- });
-
- done();
+ expect(axios.patch).toHaveBeenCalledWith(undefined, {
+ note: {
+ note: '',
+ lock_version: undefined,
+ update_task: { index, checked, line_number: lineNumber, line_source: lineSource },
+ },
});
});
});
- describe('comments', function() {
- var textarea = '.js-note-text';
-
- beforeEach(function() {
- this.notes = new Notes('', []);
+ describe('comments', () => {
+ let notes;
+ let autosizeSpy;
+ let textarea;
- this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update');
- spyOn(this.notes, 'renderNote').and.stub();
+ beforeEach(() => {
+ notes = new Notes('', []);
- $(textarea).data('autosave', {
- reset: function() {},
+ textarea = $('.js-note-text');
+ textarea.data('autosave', {
+ reset: () => {},
});
+ autosizeSpy = jest.fn();
+ $(textarea).on('autosize:update', autosizeSpy);
+
+ jest.spyOn(notes, 'renderNote');
$('.js-comment-button').on('click', e => {
const $form = $(this);
e.preventDefault();
- this.notes.addNote($form);
- this.notes.reenableTargetFormSubmitButton(e);
- this.notes.resetMainTargetForm(e);
+ notes.addNote($form, {});
+ notes.reenableTargetFormSubmitButton(e);
+ notes.resetMainTargetForm(e);
});
});
- it('autosizes after comment submission', function() {
- $(textarea).text('This is an example comment note');
-
- expect(this.autoSizeSpy).not.toHaveBeenTriggered();
-
+ it('autosizes after comment submission', () => {
+ textarea.text('This is an example comment note');
+ expect(autosizeSpy).not.toHaveBeenCalled();
$('.js-comment-button').click();
-
- expect(this.autoSizeSpy).toHaveBeenTriggered();
+ expect(autosizeSpy).toHaveBeenCalled();
});
- it('should not place escaped text in the comment box in case of error', function() {
+ it('should not place escaped text in the comment box in case of error', () => {
const deferred = $.Deferred();
- spyOn($, 'ajax').and.returnValue(deferred.promise());
+ jest.spyOn($, 'ajax').mockReturnValueOnce(deferred);
$(textarea).text('A comment with `markup`.');
deferred.reject();
$('.js-comment-button').click();
- expect($(textarea).val()).toEqual('A comment with `markup`.');
+ expect($(textarea).val()).toBe('A comment with `markup`.');
+
+ $.ajax.mockRestore();
+ expect($.ajax.mock).toBeUndefined();
});
});
describe('updateNote', () => {
- let sampleComment;
+ let notes;
let noteEntity;
- let $form;
let $notesContainer;
- let mock;
beforeEach(() => {
- this.notes = new Notes('', []);
+ notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
- sampleComment = 'foo';
+ const sampleComment = 'foo';
noteEntity = {
id: 1234,
html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
@@ -177,35 +182,27 @@ describe('Notes', function() {
note: sampleComment,
valid: true,
};
- $form = $('form.js-main-target-form');
+
$notesContainer = $('ul.main-notes-list');
+ const $form = $('form.js-main-target-form');
$form.find('textarea.js-note-text').val(sampleComment);
- mock = new MockAdapter(axios);
- mock.onPost(NOTES_POST_PATH).reply(200, noteEntity);
- });
-
- afterEach(() => {
- mock.restore();
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, noteEntity);
});
- it('updates note and resets edit form', done => {
- spyOn(this.notes, 'revertNoteEditForm');
- spyOn(this.notes, 'setupNewNote');
+ it('updates note and resets edit form', () => {
+ jest.spyOn(notes, 'revertNoteEditForm');
+ jest.spyOn(notes, 'setupNewNote');
$('.js-comment-button').click();
- setTimeout(() => {
- const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
- const updatedNote = Object.assign({}, noteEntity);
- updatedNote.note = 'bar';
- this.notes.updateNote(updatedNote, $targetNote);
-
- expect(this.notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
- expect(this.notes.setupNewNote).toHaveBeenCalled();
+ const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`);
+ const updatedNote = Object.assign({}, noteEntity);
+ updatedNote.note = 'bar';
+ notes.updateNote(updatedNote, $targetNote);
- done();
- });
+ expect(notes.revertNoteEditForm).toHaveBeenCalledWith($targetNote);
+ expect(notes.setupNewNote).toHaveBeenCalled();
});
});
@@ -215,32 +212,44 @@ describe('Notes', function() {
beforeEach(() => {
$note = $(`<div id="${hash}"></div>`);
- spyOn($note, 'filter').and.callThrough();
- spyOn($note, 'toggleClass').and.callThrough();
+ jest.spyOn($note, 'filter');
+ jest.spyOn($note, 'toggleClass');
+ });
+
+ afterEach(() => {
+ expect(typeof urlUtility.getLocationHash.mock).toBe('object');
+ urlUtility.getLocationHash.mockRestore();
+ expect(urlUtility.getLocationHash.mock).toBeUndefined();
+ expect(urlUtility.getLocationHash()).toBeNull();
});
+ // urlUtility is a dependency of the notes module. Its getLocatinHash() method should be called internally.
+
it('sets target when hash matches', () => {
- spyOnDependency(Notes, 'getLocationHash').and.returnValue(hash);
+ jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(hash);
Notes.updateNoteTargetSelector($note);
+ expect(urlUtility.getLocationHash).toHaveBeenCalled();
expect($note.filter).toHaveBeenCalledWith(`#${hash}`);
expect($note.toggleClass).toHaveBeenCalledWith('target', true);
});
it('unsets target when hash does not match', () => {
- spyOnDependency(Notes, 'getLocationHash').and.returnValue('note_doesnotexist');
+ jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce('note_doesnotexist');
Notes.updateNoteTargetSelector($note);
+ expect(urlUtility.getLocationHash).toHaveBeenCalled();
expect($note.toggleClass).toHaveBeenCalledWith('target', false);
});
it('unsets target when there is not a hash fragment anymore', () => {
- spyOnDependency(Notes, 'getLocationHash').and.returnValue(null);
+ jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(null);
Notes.updateNoteTargetSelector($note);
+ expect(urlUtility.getLocationHash).toHaveBeenCalled();
expect($note.toggleClass).toHaveBeenCalledWith('target', false);
});
});
@@ -257,28 +266,28 @@ describe('Notes', function() {
note: 'heya',
html: '<div>heya</div>',
};
- $notesList = jasmine.createSpyObj('$notesList', ['find', 'append']);
+ $notesList = createSpyObj('$notesList', ['find', 'append']);
- notes = jasmine.createSpyObj('notes', [
+ notes = createSpyObj('notes', [
'setupNewNote',
'refresh',
'collapseLongCommitList',
'updateNotesCount',
'putConflictEditWarningInPlace',
]);
- notes.taskList = jasmine.createSpyObj('tasklist', ['init']);
+ notes.taskList = createSpyObj('tasklist', ['init']);
notes.note_ids = [];
notes.updatedNotesTrackingMap = {};
- spyOn(Notes, 'isNewNote').and.callThrough();
- spyOn(Notes, 'isUpdatedNote').and.callThrough();
- spyOn(Notes, 'animateAppendNote').and.callThrough();
- spyOn(Notes, 'animateUpdateNote').and.callThrough();
+ jest.spyOn(Notes, 'isNewNote');
+ jest.spyOn(Notes, 'isUpdatedNote');
+ jest.spyOn(Notes, 'animateAppendNote');
+ jest.spyOn(Notes, 'animateUpdateNote');
});
describe('when adding note', () => {
it('should call .animateAppendNote', () => {
- Notes.isNewNote.and.returnValue(true);
+ Notes.isNewNote.mockReturnValueOnce(true);
Notes.prototype.renderNote.call(notes, note, null, $notesList);
expect(Notes.animateAppendNote).toHaveBeenCalledWith(note.html, $notesList);
@@ -287,12 +296,12 @@ describe('Notes', function() {
describe('when note was edited', () => {
it('should call .animateUpdateNote', () => {
- Notes.isNewNote.and.returnValue(false);
- Notes.isUpdatedNote.and.returnValue(true);
+ Notes.isNewNote.mockReturnValueOnce(false);
+ Notes.isUpdatedNote.mockReturnValueOnce(true);
const $note = $('<div>');
- $notesList.find.and.returnValue($note);
+ $notesList.find.mockReturnValueOnce($note);
const $newNote = $(note.html);
- Notes.animateUpdateNote.and.returnValue($newNote);
+ Notes.animateUpdateNote.mockReturnValueOnce($newNote);
Notes.prototype.renderNote.call(notes, note, null, $notesList);
@@ -302,26 +311,26 @@ describe('Notes', function() {
describe('while editing', () => {
it('should update textarea if nothing has been touched', () => {
- Notes.isNewNote.and.returnValue(false);
- Notes.isUpdatedNote.and.returnValue(true);
+ Notes.isNewNote.mockReturnValueOnce(false);
+ Notes.isUpdatedNote.mockReturnValueOnce(true);
const $note = $(`<div class="is-editing">
<div class="original-note-content">initial</div>
<textarea class="js-note-text">initial</textarea>
</div>`);
- $notesList.find.and.returnValue($note);
+ $notesList.find.mockReturnValueOnce($note);
Notes.prototype.renderNote.call(notes, note, null, $notesList);
expect($note.find('.js-note-text').val()).toEqual(note.note);
});
it('should call .putConflictEditWarningInPlace', () => {
- Notes.isNewNote.and.returnValue(false);
- Notes.isUpdatedNote.and.returnValue(true);
+ Notes.isNewNote.mockReturnValueOnce(false);
+ Notes.isUpdatedNote.mockReturnValueOnce(true);
const $note = $(`<div class="is-editing">
<div class="original-note-content">initial</div>
<textarea class="js-note-text">different</textarea>
</div>`);
- $notesList.find.and.returnValue($note);
+ $notesList.find.mockReturnValueOnce($note);
Notes.prototype.renderNote.call(notes, note, null, $notesList);
expect(notes.putConflictEditWarningInPlace).toHaveBeenCalledWith(note, $note);
@@ -386,32 +395,32 @@ describe('Notes', function() {
discussion_resolvable: false,
diff_discussion_html: false,
};
- $form = jasmine.createSpyObj('$form', ['closest', 'find']);
+ $form = createSpyObj('$form', ['closest', 'find']);
$form.length = 1;
- row = jasmine.createSpyObj('row', ['prevAll', 'first', 'find']);
+ row = createSpyObj('row', ['prevAll', 'first', 'find']);
- notes = jasmine.createSpyObj('notes', ['isParallelView', 'updateNotesCount']);
+ notes = createSpyObj('notes', ['isParallelView', 'updateNotesCount']);
notes.note_ids = [];
- spyOn(Notes, 'isNewNote');
- spyOn(Notes, 'animateAppendNote');
- Notes.isNewNote.and.returnValue(true);
- notes.isParallelView.and.returnValue(false);
- row.prevAll.and.returnValue(row);
- row.first.and.returnValue(row);
- row.find.and.returnValue(row);
+ jest.spyOn(Notes, 'isNewNote');
+ jest.spyOn(Notes, 'animateAppendNote').mockImplementation();
+ Notes.isNewNote.mockReturnValue(true);
+ notes.isParallelView.mockReturnValue(false);
+ row.prevAll.mockReturnValue(row);
+ row.first.mockReturnValue(row);
+ row.find.mockReturnValue(row);
});
describe('Discussion root note', () => {
let body;
beforeEach(() => {
- body = jasmine.createSpyObj('body', ['attr']);
+ body = createSpyObj('body', ['attr']);
discussionContainer = { length: 0 };
- $form.closest.and.returnValues(row, $form);
- $form.find.and.returnValues(discussionContainer);
- body.attr.and.returnValue('');
+ $form.closest.mockReturnValueOnce(row).mockReturnValue($form);
+ $form.find.mockReturnValue(discussionContainer);
+ body.attr.mockReturnValue('');
});
it('should call Notes.animateAppendNote', () => {
@@ -432,7 +441,9 @@ describe('Notes', function() {
line.id = note.discussion_line_code;
document.body.appendChild(line);
- $form.closest.and.returnValues($form);
+ // Override mocks for this single test
+ $form.closest.mockReset();
+ $form.closest.mockReturnValue($form);
Notes.prototype.renderDiscussionNote.call(notes, note, $form);
@@ -444,8 +455,8 @@ describe('Notes', function() {
beforeEach(() => {
discussionContainer = { length: 1 };
- $form.closest.and.returnValues(row, $form);
- $form.find.and.returnValues(discussionContainer);
+ $form.closest.mockReturnValueOnce(row).mockReturnValueOnce($form);
+ $form.find.mockReturnValue(discussionContainer);
Notes.prototype.renderDiscussionNote.call(notes, note, $form);
});
@@ -463,7 +474,7 @@ describe('Notes', function() {
beforeEach(() => {
noteHTML = '<div></div>';
- $notesList = jasmine.createSpyObj('$notesList', ['append']);
+ $notesList = createSpyObj('$notesList', ['append']);
$resultantNote = Notes.animateAppendNote(noteHTML, $notesList);
});
@@ -484,7 +495,7 @@ describe('Notes', function() {
beforeEach(() => {
noteHTML = '<div></div>';
- $note = jasmine.createSpyObj('$note', ['replaceWith']);
+ $note = createSpyObj('$note', ['replaceWith']);
$updatedNote = Notes.animateUpdateNote(noteHTML, $note);
});
@@ -515,7 +526,6 @@ describe('Notes', function() {
describe('postComment & updateComment', () => {
const sampleComment = 'foo';
- const updatedComment = 'bar';
const note = {
id: 1234,
html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
@@ -524,22 +534,20 @@ describe('Notes', function() {
note: sampleComment,
valid: true,
};
+ let notes;
let $form;
let $notesContainer;
- let mock;
function mockNotesPost() {
- mock.onPost(NOTES_POST_PATH).reply(200, note);
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
}
function mockNotesPostError() {
- mock.onPost(NOTES_POST_PATH).networkError();
+ mockAxios.onPost(NOTES_POST_PATH).networkError();
}
beforeEach(() => {
- mock = new MockAdapter(axios);
-
- this.notes = new Notes('', []);
+ notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
$form = $('form.js-main-target-form');
@@ -547,10 +555,6 @@ describe('Notes', function() {
$form.find('textarea.js-note-text').val(sampleComment);
});
- afterEach(() => {
- mock.restore();
- });
-
it('should show placeholder note while new comment is being posted', () => {
mockNotesPost();
@@ -564,9 +568,8 @@ describe('Notes', function() {
$('.js-comment-button').click();
- setTimeout(() => {
+ setImmediate(() => {
expect($notesContainer.find('.note.being-posted').length).toEqual(0);
-
done();
});
});
@@ -580,12 +583,12 @@ describe('Notes', function() {
preventDefault() {},
target: $submitButton,
};
- mock.onPost(NOTES_POST_PATH).replyOnce(() => {
+ mockAxios.onPost(NOTES_POST_PATH).replyOnce(() => {
expect($submitButton).toBeDisabled();
return [200, note];
});
- this.notes
+ notes
.postComment(dummyEvent)
.then(() => {
expect($submitButton).not.toBeDisabled();
@@ -600,9 +603,8 @@ describe('Notes', function() {
$('.js-comment-button').click();
- setTimeout(() => {
+ setImmediate(() => {
expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
-
done();
});
});
@@ -612,48 +614,49 @@ describe('Notes', function() {
$('.js-comment-button').click();
- setTimeout(() => {
+ setImmediate(() => {
expect($form.find('textarea.js-note-text').val()).toEqual('');
-
done();
});
});
it('should show flash error message when new comment failed to be posted', done => {
mockNotesPostError();
+ jest.spyOn(notes, 'addFlash');
$('.js-comment-button').click();
- setTimeout(() => {
- expect(
- $notesContainer
- .parent()
- .find('.flash-container .flash-text')
- .is(':visible'),
- ).toEqual(true);
-
+ setImmediate(() => {
+ expect(notes.addFlash).toHaveBeenCalled();
+ // JSDom doesn't support the :visible selector yet
+ expect(notes.flashContainer.style.display).not.toBe('none');
done();
});
});
+ // This is a bad test carried over from the Karma -> Jest migration.
+ // The corresponding test in the Karma suite tests for
+ // elements and methods that don't actually exist, and gives a false
+ // positive pass.
+ /*
it('should show flash error message when comment failed to be updated', done => {
mockNotesPost();
+ jest.spyOn(notes, 'addFlash').mockName('addFlash');
$('.js-comment-button').click();
- timeoutPromise()
+ deferredPromise()
.then(() => {
const $noteEl = $notesContainer.find(`#note_${note.id}`);
$noteEl.find('.js-note-edit').click();
$noteEl.find('textarea.js-note-text').val(updatedComment);
- mock.restore();
-
mockNotesPostError();
$noteEl.find('.js-comment-save-button').click();
+ notes.updateComment({preventDefault: () => {}});
})
- .then(timeoutPromise)
+ .then(() => deferredPromise())
.then(() => {
const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
@@ -665,12 +668,13 @@ describe('Notes', function() {
.trim(),
).toEqual(sampleComment); // See if comment reverted back to original
- expect($('.flash-container').is(':visible')).toEqual(true); // Flash error message shown
-
+ expect(notes.addFlash).toHaveBeenCalled();
+ expect(notes.flashContainer.style.display).not.toBe('none');
done();
})
.catch(done.fail);
- }, 2000);
+ }, 5000);
+ */
});
describe('postComment with Slash commands', () => {
@@ -687,13 +691,11 @@ describe('Notes', function() {
};
let $form;
let $notesContainer;
- let mock;
beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onPost(NOTES_POST_PATH).reply(200, note);
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
- this.notes = new Notes('', []);
+ new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
gl.awardsHandler = {
@@ -710,17 +712,13 @@ describe('Notes', function() {
$form.find('textarea.js-note-text').val(sampleComment);
});
- afterEach(() => {
- mock.restore();
- });
-
it('should remove slash command placeholder when comment with slash commands is done posting', done => {
- spyOn(gl.awardsHandler, 'addAwardToEmojiBar').and.callThrough();
+ jest.spyOn(gl.awardsHandler, 'addAwardToEmojiBar');
$('.js-comment-button').click();
expect($notesContainer.find('.system-note.being-posted').length).toEqual(1); // Placeholder shown
- setTimeout(() => {
+ setImmediate(() => {
expect($notesContainer.find('.system-note.being-posted').length).toEqual(0); // Placeholder removed
done();
});
@@ -740,13 +738,11 @@ describe('Notes', function() {
};
let $form;
let $notesContainer;
- let mock;
beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onPost(NOTES_POST_PATH).reply(200, note);
+ mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
- this.notes = new Notes('', []);
+ new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
$form = $('form.js-main-target-form');
@@ -754,14 +750,10 @@ describe('Notes', function() {
$form.find('textarea.js-note-text').html(sampleComment);
});
- afterEach(() => {
- mock.restore();
- });
-
it('should not render a script tag', done => {
$('.js-comment-button').click();
- setTimeout(() => {
+ setImmediate(() => {
const $noteEl = $notesContainer.find(`#note_${note.id}`);
$noteEl.find('.js-note-edit').click();
$noteEl.find('textarea.js-note-text').html(updatedComment);
@@ -786,9 +778,10 @@ describe('Notes', function() {
describe('getFormData', () => {
let $form;
let sampleComment;
+ let notes;
beforeEach(() => {
- this.notes = new Notes('', []);
+ notes = new Notes('', []);
$form = $('form');
sampleComment = 'foobar';
@@ -796,7 +789,7 @@ describe('Notes', function() {
it('should return form metadata object from form reference', () => {
$form.find('textarea.js-note-text').val(sampleComment);
- const { formData, formContent, formAction } = this.notes.getFormData($form);
+ const { formData, formContent, formAction } = notes.getFormData($form);
expect(formData.indexOf(sampleComment)).toBeGreaterThan(-1);
expect(formContent).toEqual(sampleComment);
@@ -804,12 +797,12 @@ describe('Notes', function() {
});
it('should return form metadata with sanitized formContent from form reference', () => {
- spyOn(_, 'escape').and.callFake(htmlEscape);
+ jest.spyOn(_, 'escape');
sampleComment = '<script>alert("Boom!");</script>';
$form.find('textarea.js-note-text').val(sampleComment);
- const { formContent } = this.notes.getFormData($form);
+ const { formContent } = notes.getFormData($form);
expect(_.escape).toHaveBeenCalledWith(sampleComment);
expect(formContent).toEqual('&lt;script&gt;alert(&quot;Boom!&quot;);&lt;/script&gt;');
@@ -817,27 +810,29 @@ describe('Notes', function() {
});
describe('hasQuickActions', () => {
+ let notes;
+
beforeEach(() => {
- this.notes = new Notes('', []);
+ notes = new Notes('', []);
});
it('should return true when comment begins with a quick action', () => {
const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
- const hasQuickActions = this.notes.hasQuickActions(sampleComment);
+ const hasQuickActions = notes.hasQuickActions(sampleComment);
expect(hasQuickActions).toBeTruthy();
});
it('should return false when comment does NOT begin with a quick action', () => {
const sampleComment = 'Hey, /unassign Merging this';
- const hasQuickActions = this.notes.hasQuickActions(sampleComment);
+ const hasQuickActions = notes.hasQuickActions(sampleComment);
expect(hasQuickActions).toBeFalsy();
});
it('should return false when comment does NOT have any quick actions', () => {
const sampleComment = 'Looking good, Awesome!';
- const hasQuickActions = this.notes.hasQuickActions(sampleComment);
+ const hasQuickActions = notes.hasQuickActions(sampleComment);
expect(hasQuickActions).toBeFalsy();
});
@@ -845,25 +840,25 @@ describe('Notes', function() {
describe('stripQuickActions', () => {
it('should strip quick actions from the comment which begins with a quick action', () => {
- this.notes = new Notes();
+ const notes = new Notes();
const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign Merging this';
- const stripedComment = this.notes.stripQuickActions(sampleComment);
+ const stripedComment = notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe('');
});
it('should strip quick actions from the comment but leaves plain comment if it is present', () => {
- this.notes = new Notes();
+ const notes = new Notes();
const sampleComment = '/wip\n/milestone %1.0\n/merge\n/unassign\nMerging this';
- const stripedComment = this.notes.stripQuickActions(sampleComment);
+ const stripedComment = notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe('Merging this');
});
it('should NOT strip string that has slashes within', () => {
- this.notes = new Notes();
+ const notes = new Notes();
const sampleComment = 'http://127.0.0.1:3000/root/gitlab-shell/issues/1';
- const stripedComment = this.notes.stripQuickActions(sampleComment);
+ const stripedComment = notes.stripQuickActions(sampleComment);
expect(stripedComment).toBe(sampleComment);
});
@@ -875,15 +870,16 @@ describe('Notes', function() {
{ name: 'title', description: 'Change title', params: [{}] },
{ name: 'estimate', description: 'Set time estimate', params: [{}] },
];
+ let notes;
beforeEach(() => {
- this.notes = new Notes();
+ notes = new Notes();
});
it('should return executing quick action description when note has single quick action', () => {
const sampleComment = '/close';
- expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
+ expect(notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
'Applying command to close this issue',
);
});
@@ -891,7 +887,7 @@ describe('Notes', function() {
it('should return generic multiple quick action description when note has multiple quick actions', () => {
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
- expect(this.notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
+ expect(notes.getQuickActionDescription(sampleComment, availableQuickActions)).toBe(
'Applying multiple commands',
);
});
@@ -899,7 +895,7 @@ describe('Notes', function() {
it('should return generic quick action description when available quick actions list is not populated', () => {
const sampleComment = '/close\n/title [Duplicate] Issue foobar';
- expect(this.notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
+ expect(notes.getQuickActionDescription(sampleComment)).toBe('Applying command');
});
});
@@ -909,13 +905,14 @@ describe('Notes', function() {
const currentUsername = 'root';
const currentUserFullname = 'Administrator';
const currentUserAvatar = 'avatar_url';
+ let notes;
beforeEach(() => {
- this.notes = new Notes('', []);
+ notes = new Notes('', []);
});
it('should return constructed placeholder element for regular note based on form contents', () => {
- const $tempNote = this.notes.createPlaceholderNote({
+ const $tempNote = notes.createPlaceholderNote({
formContent: sampleComment,
uniqueId,
isDiscussionNote: false,
@@ -929,8 +926,8 @@ describe('Notes', function() {
expect($tempNote.attr('id')).toEqual(uniqueId);
expect($tempNote.hasClass('being-posted')).toBeTruthy();
expect($tempNote.hasClass('fade-in-half')).toBeTruthy();
- $tempNote.find('.timeline-icon > a, .note-header-info > a').each(function() {
- expect($(this).attr('href')).toEqual(`/${currentUsername}`);
+ $tempNote.find('.timeline-icon > a, .note-header-info > a').each((i, el) => {
+ expect(el.getAttribute('href')).toEqual(`/${currentUsername}`);
});
expect($tempNote.find('.timeline-icon .avatar').attr('src')).toEqual(currentUserAvatar);
@@ -958,7 +955,7 @@ describe('Notes', function() {
});
it('should return constructed placeholder element for discussion note based on form contents', () => {
- const $tempNote = this.notes.createPlaceholderNote({
+ const $tempNote = notes.createPlaceholderNote({
formContent: sampleComment,
uniqueId,
isDiscussionNote: true,
@@ -972,7 +969,7 @@ describe('Notes', function() {
it('should return a escaped user name', () => {
const currentUserFullnameXSS = 'Foo <script>alert("XSS")</script>';
- const $tempNote = this.notes.createPlaceholderNote({
+ const $tempNote = notes.createPlaceholderNote({
formContent: sampleComment,
uniqueId,
isDiscussionNote: false,
@@ -994,14 +991,15 @@ describe('Notes', function() {
describe('createPlaceholderSystemNote', () => {
const sampleCommandDescription = 'Applying command to close this issue';
const uniqueId = 'b1234-a4567';
+ let notes;
beforeEach(() => {
- this.notes = new Notes('', []);
- spyOn(_, 'escape').and.callFake(htmlEscape);
+ notes = new Notes('', []);
+ jest.spyOn(_, 'escape');
});
it('should return constructed placeholder element for system note based on form contents', () => {
- const $tempNote = this.notes.createPlaceholderSystemNote({
+ const $tempNote = notes.createPlaceholderSystemNote({
formContent: sampleCommandDescription,
uniqueId,
});
@@ -1020,29 +1018,28 @@ describe('Notes', function() {
});
describe('appendFlash', () => {
- beforeEach(() => {
- this.notes = new Notes();
- });
-
it('shows a flash message', () => {
- this.notes.addFlash('Error message', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
+ const notes = new Notes('', []);
+ notes.addFlash('Error message', FLASH_TYPE_ALERT, notes.parentTimeline.get(0));
- expect($('.flash-alert').is(':visible')).toBeTruthy();
+ const flash = $('.flash-alert')[0];
+ expect(document.contains(flash)).toBe(true);
+ expect(flash.parentNode.style.display).toBe('block');
});
});
describe('clearFlash', () => {
beforeEach(() => {
$(document).off('ajax:success');
- this.notes = new Notes();
});
it('hides visible flash message', () => {
- this.notes.addFlash('Error message 1', FLASH_TYPE_ALERT, this.notes.parentTimeline.get(0));
-
- this.notes.clearFlash();
-
- expect($('.flash-alert').is(':visible')).toBeFalsy();
+ const notes = new Notes('', []);
+ notes.addFlash('Error message 1', FLASH_TYPE_ALERT, notes.parentTimeline.get(0));
+ const flash = $('.flash-alert')[0];
+ notes.clearFlash();
+ expect(flash.parentNode.style.display).toBe('none');
+ expect(notes.flashContainer).toBeNull();
});
});
});
diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js
index de1dd219fe0..a881de8fbfe 100644
--- a/spec/frontend/operation_settings/components/external_dashboard_spec.js
+++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js
@@ -1,30 +1,64 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui';
import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue';
+import store from '~/operation_settings/store';
+import axios from '~/lib/utils/axios_utils';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import createFlash from '~/flash';
import { TEST_HOST } from 'helpers/test_constants';
+jest.mock('~/lib/utils/axios_utils');
+jest.mock('~/lib/utils/url_utility');
+jest.mock('~/flash');
+
describe('operation settings external dashboard component', () => {
let wrapper;
- const externalDashboardPath = `http://mock-external-domain.com/external/dashboard/path`;
+ const operationsSettingsEndpoint = `${TEST_HOST}/mock/ops/settings/endpoint`;
+ const externalDashboardUrl = `http://mock-external-domain.com/external/dashboard/url`;
const externalDashboardHelpPagePath = `${TEST_HOST}/help/page/path`;
-
- beforeEach(() => {
- wrapper = shallowMount(ExternalDashboard, {
- propsData: {
- externalDashboardPath,
- externalDashboardHelpPagePath,
+ const localVue = createLocalVue();
+ const mountComponent = (shallow = true) => {
+ const config = [
+ ExternalDashboard,
+ {
+ localVue,
+ store: store({
+ operationsSettingsEndpoint,
+ externalDashboardUrl,
+ externalDashboardHelpPagePath,
+ }),
},
- });
+ ];
+ wrapper = shallow ? shallowMount(...config) : mount(...config);
+ };
+
+ afterEach(() => {
+ if (wrapper.destroy) {
+ wrapper.destroy();
+ }
+ axios.patch.mockReset();
+ refreshCurrentPage.mockReset();
+ createFlash.mockReset();
});
it('renders header text', () => {
+ mountComponent();
expect(wrapper.find('.js-section-header').text()).toBe('External Dashboard');
});
+ describe('expand/collapse button', () => {
+ it('renders as an expand button by default', () => {
+ const button = wrapper.find(GlButton);
+
+ expect(button.text()).toBe('Expand');
+ });
+ });
+
describe('sub-header', () => {
let subHeader;
beforeEach(() => {
+ mountComponent();
subHeader = wrapper.find('.js-section-sub-header');
});
@@ -43,57 +77,89 @@ describe('operation settings external dashboard component', () => {
});
describe('form', () => {
- let form;
+ describe('input label', () => {
+ let formGroup;
- beforeEach(() => {
- form = wrapper.find('form');
- });
+ beforeEach(() => {
+ mountComponent();
+ formGroup = wrapper.find(GlFormGroup);
+ });
- describe('external dashboard url', () => {
- describe('input label', () => {
- let formGroup;
+ it('uses label text', () => {
+ expect(formGroup.attributes().label).toBe('Full dashboard URL');
+ });
- beforeEach(() => {
- formGroup = form.find(GlFormGroup);
- });
+ it('uses description text', () => {
+ expect(formGroup.attributes().description).toBe(
+ 'Enter the URL of the dashboard you want to link to',
+ );
+ });
+ });
- it('uses label text', () => {
- expect(formGroup.attributes().label).toBe('Full dashboard URL');
- });
+ describe('input field', () => {
+ let input;
- it('uses description text', () => {
- expect(formGroup.attributes().description).toBe(
- 'Enter the URL of the dashboard you want to link to',
- );
- });
+ beforeEach(() => {
+ mountComponent();
+ input = wrapper.find(GlFormInput);
});
- describe('input field', () => {
- let input;
-
- beforeEach(() => {
- input = form.find(GlFormInput);
- });
+ it('defaults to externalDashboardUrl', () => {
+ expect(input.attributes().value).toBe(externalDashboardUrl);
+ });
- it('defaults to externalDashboardPath prop', () => {
- expect(input.attributes().value).toBe(externalDashboardPath);
- });
+ it('uses a placeholder', () => {
+ expect(input.attributes().placeholder).toBe('https://my-org.gitlab.io/my-dashboards');
+ });
+ });
- it('uses a placeholder', () => {
- expect(input.attributes().placeholder).toBe('https://my-org.gitlab.io/my-dashboards');
- });
+ describe('submit button', () => {
+ const findSubmitButton = () => wrapper.find('.settings-content form').find(GlButton);
+
+ const endpointRequest = [
+ operationsSettingsEndpoint,
+ {
+ project: {
+ metrics_setting_attributes: {
+ external_dashboard_url: externalDashboardUrl,
+ },
+ },
+ },
+ ];
+
+ it('renders button label', () => {
+ mountComponent();
+ const submit = findSubmitButton();
+ expect(submit.text()).toBe('Save Changes');
});
- describe('submit button', () => {
- let submit;
+ it('submits form on click', () => {
+ mountComponent(false);
+ axios.patch.mockResolvedValue();
+ findSubmitButton().trigger('click');
- beforeEach(() => {
- submit = form.find(GlButton);
- });
+ expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
- it('renders button label', () => {
- expect(submit.text()).toBe('Save Changes');
- });
+ return wrapper.vm.$nextTick().then(() => expect(refreshCurrentPage).toHaveBeenCalled());
+ });
+
+ it('creates flash banner on error', () => {
+ mountComponent(false);
+ const message = 'mockErrorMessage';
+ axios.patch.mockRejectedValue({ response: { data: { message } } });
+ findSubmitButton().trigger('click');
+
+ expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
+
+ return wrapper.vm
+ .$nextTick()
+ .then(jest.runAllTicks)
+ .then(() =>
+ expect(createFlash).toHaveBeenCalledWith(
+ `There was an error saving your changes. ${message}`,
+ 'alert',
+ ),
+ );
});
});
});
diff --git a/spec/frontend/operation_settings/store/mutations_spec.js b/spec/frontend/operation_settings/store/mutations_spec.js
new file mode 100644
index 00000000000..1854142c89a
--- /dev/null
+++ b/spec/frontend/operation_settings/store/mutations_spec.js
@@ -0,0 +1,19 @@
+import mutations from '~/operation_settings/store/mutations';
+import createState from '~/operation_settings/store/state';
+
+describe('operation settings mutations', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = createState();
+ });
+
+ describe('SET_EXTERNAL_DASHBOARD_URL', () => {
+ it('sets externalDashboardUrl', () => {
+ const mockUrl = 'mockUrl';
+ mutations.SET_EXTERNAL_DASHBOARD_URL(localState, mockUrl);
+
+ expect(localState.externalDashboardUrl).toBe(mockUrl);
+ });
+ });
+});
diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js
index 3b609484b9e..d4a3073374a 100644
--- a/spec/frontend/reports/components/report_section_spec.js
+++ b/spec/frontend/reports/components/report_section_spec.js
@@ -197,4 +197,44 @@ describe('Report section', () => {
expect(vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual('Expand');
});
});
+
+ describe('Success and Error slots', () => {
+ const createComponent = status => {
+ vm = mountComponentWithSlots(ReportSection, {
+ props: {
+ status,
+ hasIssues: true,
+ },
+ slots: {
+ success: ['This is a success'],
+ loading: ['This is loading'],
+ error: ['This is an error'],
+ },
+ });
+ };
+
+ it('only renders success slot when status is "SUCCESS"', () => {
+ createComponent('SUCCESS');
+
+ expect(vm.$el.textContent.trim()).toContain('This is a success');
+ expect(vm.$el.textContent.trim()).not.toContain('This is an error');
+ expect(vm.$el.textContent.trim()).not.toContain('This is loading');
+ });
+
+ it('only renders error slot when status is "ERROR"', () => {
+ createComponent('ERROR');
+
+ expect(vm.$el.textContent.trim()).toContain('This is an error');
+ expect(vm.$el.textContent.trim()).not.toContain('This is a success');
+ expect(vm.$el.textContent.trim()).not.toContain('This is loading');
+ });
+
+ it('only renders loading slot when status is "LOADING"', () => {
+ createComponent('LOADING');
+
+ expect(vm.$el.textContent.trim()).toContain('This is loading');
+ expect(vm.$el.textContent.trim()).not.toContain('This is an error');
+ expect(vm.$el.textContent.trim()).not.toContain('This is a success');
+ });
+ });
});
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
new file mode 100644
index 00000000000..068fa317a87
--- /dev/null
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -0,0 +1,44 @@
+import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
+
+let vm;
+
+function factory(currentPath) {
+ vm = shallowMount(Breadcrumbs, {
+ propsData: {
+ currentPath,
+ },
+ stubs: {
+ RouterLink: RouterLinkStub,
+ },
+ });
+}
+
+describe('Repository breadcrumbs component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it.each`
+ path | linkCount
+ ${'/'} | ${1}
+ ${'app'} | ${2}
+ ${'app/assets'} | ${3}
+ ${'app/assets/javascripts'} | ${4}
+ `('renders $linkCount links for path $path', ({ path, linkCount }) => {
+ factory(path);
+
+ expect(vm.findAll(RouterLinkStub).length).toEqual(linkCount);
+ });
+
+ it('renders last link as active', () => {
+ factory('app/assets');
+
+ expect(
+ vm
+ .findAll(RouterLinkStub)
+ .at(2)
+ .attributes('aria-current'),
+ ).toEqual('page');
+ });
+});
diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
index f0b72343b6e..86bfde1a28c 100644
--- a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
+++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap
@@ -16,10 +16,14 @@ exports[`Repository table row component renders table row 1`] = `
<a
class="str-truncated"
>
+
test
+
</a>
<!---->
+
+ <!---->
</td>
<td
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index 6f52cffe077..827927e6d9a 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -3,18 +3,19 @@ import { GlLoadingIcon } from '@gitlab/ui';
import Table from '~/repository/components/table/index.vue';
let vm;
+let $apollo;
+
+function factory(path, data = () => ({})) {
+ $apollo = {
+ query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })),
+ };
-function factory(path, loading = false) {
vm = shallowMount(Table, {
propsData: {
path,
},
mocks: {
- $apollo: {
- queries: {
- files: { loading },
- },
- },
+ $apollo,
},
});
}
@@ -39,9 +40,41 @@ describe('Repository table component', () => {
);
});
- it('renders loading icon', () => {
- factory('/', true);
+ it('shows loading icon', () => {
+ factory('/');
+
+ vm.setData({ isLoadingFiles: true });
+
+ expect(vm.find(GlLoadingIcon).isVisible()).toBe(true);
+ });
+
+ describe('normalizeData', () => {
+ it('normalizes edge nodes', () => {
+ const output = vm.vm.normalizeData('blobs', [{ node: '1' }, { node: '2' }]);
+
+ expect(output).toEqual(['1', '2']);
+ });
+ });
+
+ describe('hasNextPage', () => {
+ it('returns undefined when hasNextPage is false', () => {
+ const output = vm.vm.hasNextPage({
+ trees: { pageInfo: { hasNextPage: false } },
+ submodules: { pageInfo: { hasNextPage: false } },
+ blobs: { pageInfo: { hasNextPage: false } },
+ });
+
+ expect(output).toBe(undefined);
+ });
+
+ it('returns pageInfo object when hasNextPage is true', () => {
+ const output = vm.vm.hasNextPage({
+ trees: { pageInfo: { hasNextPage: false } },
+ submodules: { pageInfo: { hasNextPage: false } },
+ blobs: { pageInfo: { hasNextPage: true, nextCursor: 'test' } },
+ });
- expect(vm.find(GlLoadingIcon).exists()).toBe(true);
+ expect(output).toEqual({ hasNextPage: true, nextCursor: 'test' });
+ });
});
});
diff --git a/spec/frontend/repository/components/table/parent_row_spec.js b/spec/frontend/repository/components/table/parent_row_spec.js
new file mode 100644
index 00000000000..7020055271f
--- /dev/null
+++ b/spec/frontend/repository/components/table/parent_row_spec.js
@@ -0,0 +1,64 @@
+import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import ParentRow from '~/repository/components/table/parent_row.vue';
+
+let vm;
+let $router;
+
+function factory(path) {
+ $router = {
+ push: jest.fn(),
+ };
+
+ vm = shallowMount(ParentRow, {
+ propsData: {
+ commitRef: 'master',
+ path,
+ },
+ stubs: {
+ RouterLink: RouterLinkStub,
+ },
+ mocks: {
+ $router,
+ },
+ });
+}
+
+describe('Repository parent row component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it.each`
+ path | to
+ ${'app'} | ${'/tree/master/'}
+ ${'app/assets'} | ${'/tree/master/app'}
+ `('renders link in $path to $to', ({ path, to }) => {
+ factory(path);
+
+ expect(vm.find(RouterLinkStub).props().to).toEqual({
+ path: to,
+ });
+ });
+
+ it('pushes new router when clicking row', () => {
+ factory('app/assets');
+
+ vm.find('td').trigger('click');
+
+ expect($router.push).toHaveBeenCalledWith({
+ path: '/tree/master/app',
+ });
+ });
+
+ // We test that it does not get called when clicking any internal
+ // links as this was causing multipe routes to get pushed
+ it('does not trigger router.push when clicking link', () => {
+ factory('app/assets');
+
+ vm.find('a').trigger('click');
+
+ expect($router.push).not.toHaveBeenCalledWith({
+ path: '/tree/master/app',
+ });
+ });
+});
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index 216128dce25..90a502966ad 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -1,4 +1,5 @@
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import { GlBadge } from '@gitlab/ui';
import TableRow from '~/repository/components/table/row.vue';
let vm;
@@ -29,9 +30,10 @@ describe('Repository table row component', () => {
it('renders table row', () => {
factory({
- id: 1,
+ id: '1',
path: 'test',
type: 'file',
+ currentPath: '/',
});
expect(vm.element).toMatchSnapshot();
@@ -39,14 +41,15 @@ describe('Repository table row component', () => {
it.each`
type | component | componentName
- ${'folder'} | ${RouterLinkStub} | ${'RouterLink'}
+ ${'tree'} | ${RouterLinkStub} | ${'RouterLink'}
${'file'} | ${'a'} | ${'hyperlink'}
${'commit'} | ${'a'} | ${'hyperlink'}
`('renders a $componentName for type $type', ({ type, component }) => {
factory({
- id: 1,
+ id: '1',
path: 'test',
type,
+ currentPath: '/',
});
expect(vm.find(component).exists()).toBe(true);
@@ -54,14 +57,15 @@ describe('Repository table row component', () => {
it.each`
type | pushes
- ${'folder'} | ${true}
+ ${'tree'} | ${true}
${'file'} | ${false}
${'commit'} | ${false}
- `('pushes new router if type $type is folder', ({ type, pushes }) => {
+ `('pushes new router if type $type is tree', ({ type, pushes }) => {
factory({
- id: 1,
+ id: '1',
path: 'test',
type,
+ currentPath: '/',
});
vm.trigger('click');
@@ -75,11 +79,36 @@ describe('Repository table row component', () => {
it('renders commit ID for submodule', () => {
factory({
- id: 1,
+ id: '1',
path: 'test',
type: 'commit',
+ currentPath: '/',
});
expect(vm.find('.commit-sha').text()).toContain('1');
});
+
+ it('renders link with href', () => {
+ factory({
+ id: '1',
+ path: 'test',
+ type: 'blob',
+ url: 'https://test.com',
+ currentPath: '/',
+ });
+
+ expect(vm.find('a').attributes('href')).toEqual('https://test.com');
+ });
+
+ it('renders LFS badge', () => {
+ factory({
+ id: '1',
+ path: 'test',
+ type: 'commit',
+ currentPath: '/',
+ lfsOid: '1',
+ });
+
+ expect(vm.find(GlBadge).exists()).toBe(true);
+ });
});
diff --git a/spec/frontend/repository/utils/icon_spec.js b/spec/frontend/repository/utils/icon_spec.js
index 52787327bef..3d84705f7ea 100644
--- a/spec/frontend/repository/utils/icon_spec.js
+++ b/spec/frontend/repository/utils/icon_spec.js
@@ -6,7 +6,7 @@ describe('getIconName', () => {
// file types
it.each`
type | path | icon
- ${'folder'} | ${''} | ${'folder'}
+ ${'tree'} | ${''} | ${'folder'}
${'commit'} | ${''} | ${'archive'}
${'file'} | ${'test.pdf'} | ${'file-pdf-o'}
${'file'} | ${'test.jpg'} | ${'file-image-o'}
diff --git a/spec/frontend/repository/utils/title_spec.js b/spec/frontend/repository/utils/title_spec.js
new file mode 100644
index 00000000000..c4879716fd7
--- /dev/null
+++ b/spec/frontend/repository/utils/title_spec.js
@@ -0,0 +1,15 @@
+import { setTitle } from '~/repository/utils/title';
+
+describe('setTitle', () => {
+ it.each`
+ path | title
+ ${'/'} | ${'Files'}
+ ${'app'} | ${'app'}
+ ${'app/assets'} | ${'app/assets'}
+ ${'app/assets/javascripts'} | ${'app/assets/javascripts'}
+ `('sets document title as $title for $path', ({ path, title }) => {
+ setTitle(path, 'master', 'GitLab');
+
+ expect(document.title).toEqual(`${title} · master · GitLab`);
+ });
+});
diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js
index 161a637dd75..0ad85e218dc 100644
--- a/spec/frontend/serverless/components/environment_row_spec.js
+++ b/spec/frontend/serverless/components/environment_row_spec.js
@@ -14,7 +14,7 @@ describe('environment row component', () => {
beforeEach(() => {
localVue = createLocalVue();
- vm = createComponent(localVue, translate(mockServerlessFunctions)['*'], '*');
+ vm = createComponent(localVue, translate(mockServerlessFunctions.functions)['*'], '*');
});
afterEach(() => vm.$destroy());
@@ -48,7 +48,11 @@ describe('environment row component', () => {
beforeEach(() => {
localVue = createLocalVue();
- vm = createComponent(localVue, translate(mockServerlessFunctionsDiffEnv).test, 'test');
+ vm = createComponent(
+ localVue,
+ translate(mockServerlessFunctionsDiffEnv.functions).test,
+ 'test',
+ );
});
afterEach(() => vm.$destroy());
diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js
index 6924fb9e91f..d8a80f8031e 100644
--- a/spec/frontend/serverless/components/functions_spec.js
+++ b/spec/frontend/serverless/components/functions_spec.js
@@ -34,11 +34,11 @@ describe('functionsComponent', () => {
});
it('should render empty state when Knative is not installed', () => {
+ store.dispatch('receiveFunctionsSuccess', { knative_installed: false });
component = shallowMount(functionsComponent, {
localVue,
store,
propsData: {
- installed: false,
clustersPath: '',
helpPath: '',
statusPath: '',
@@ -55,7 +55,6 @@ describe('functionsComponent', () => {
localVue,
store,
propsData: {
- installed: true,
clustersPath: '',
helpPath: '',
statusPath: '',
@@ -67,12 +66,11 @@ describe('functionsComponent', () => {
});
it('should render empty state when there is no function data', () => {
- store.dispatch('receiveFunctionsNoDataSuccess');
+ store.dispatch('receiveFunctionsNoDataSuccess', { knative_installed: true });
component = shallowMount(functionsComponent, {
localVue,
store,
propsData: {
- installed: true,
clustersPath: '',
helpPath: '',
statusPath: '',
@@ -91,12 +89,31 @@ describe('functionsComponent', () => {
);
});
+ it('should render functions and a loader when functions are partially fetched', () => {
+ store.dispatch('receiveFunctionsPartial', {
+ ...mockServerlessFunctions,
+ knative_installed: 'checking',
+ });
+ component = shallowMount(functionsComponent, {
+ localVue,
+ store,
+ propsData: {
+ clustersPath: '',
+ helpPath: '',
+ statusPath: '',
+ },
+ sync: false,
+ });
+
+ expect(component.find('.js-functions-wrapper').exists()).toBe(true);
+ expect(component.find('.js-functions-loader').exists()).toBe(true);
+ });
+
it('should render the functions list', () => {
component = shallowMount(functionsComponent, {
localVue,
store,
propsData: {
- installed: true,
clustersPath: 'clustersPath',
helpPath: 'helpPath',
statusPath,
diff --git a/spec/frontend/serverless/mock_data.js b/spec/frontend/serverless/mock_data.js
index a2c18616324..ef616ceb37f 100644
--- a/spec/frontend/serverless/mock_data.js
+++ b/spec/frontend/serverless/mock_data.js
@@ -1,56 +1,62 @@
-export const mockServerlessFunctions = [
- {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'A test service',
- image: 'knative-test-container-buildtemplate',
- },
- {
- name: 'testfunc2',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc2.tm-example.apps.example.com',
- description: 'A second test service\nThis one with additional descriptions',
- image: 'knative-test-echo-buildtemplate',
- },
-];
+export const mockServerlessFunctions = {
+ knative_installed: true,
+ functions: [
+ {
+ name: 'testfunc1',
+ namespace: 'tm-example',
+ environment_scope: '*',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
+ podcount: null,
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc1.tm-example.apps.example.com',
+ description: 'A test service',
+ image: 'knative-test-container-buildtemplate',
+ },
+ {
+ name: 'testfunc2',
+ namespace: 'tm-example',
+ environment_scope: '*',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
+ podcount: null,
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc2.tm-example.apps.example.com',
+ description: 'A second test service\nThis one with additional descriptions',
+ image: 'knative-test-echo-buildtemplate',
+ },
+ ],
+};
-export const mockServerlessFunctionsDiffEnv = [
- {
- name: 'testfunc1',
- namespace: 'tm-example',
- environment_scope: '*',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc1.tm-example.apps.example.com',
- description: 'A test service',
- image: 'knative-test-container-buildtemplate',
- },
- {
- name: 'testfunc2',
- namespace: 'tm-example',
- environment_scope: 'test',
- cluster_id: 46,
- detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
- podcount: null,
- created_at: '2019-02-05T01:01:23Z',
- url: 'http://testfunc2.tm-example.apps.example.com',
- description: 'A second test service\nThis one with additional descriptions',
- image: 'knative-test-echo-buildtemplate',
- },
-];
+export const mockServerlessFunctionsDiffEnv = {
+ knative_installed: true,
+ functions: [
+ {
+ name: 'testfunc1',
+ namespace: 'tm-example',
+ environment_scope: '*',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc1',
+ podcount: null,
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc1.tm-example.apps.example.com',
+ description: 'A test service',
+ image: 'knative-test-container-buildtemplate',
+ },
+ {
+ name: 'testfunc2',
+ namespace: 'tm-example',
+ environment_scope: 'test',
+ cluster_id: 46,
+ detail_url: '/testuser/testproj/serverless/functions/*/testfunc2',
+ podcount: null,
+ created_at: '2019-02-05T01:01:23Z',
+ url: 'http://testfunc2.tm-example.apps.example.com',
+ description: 'A second test service\nThis one with additional descriptions',
+ image: 'knative-test-echo-buildtemplate',
+ },
+ ],
+};
export const mockServerlessFunction = {
name: 'testfunc1',
diff --git a/spec/frontend/serverless/store/getters_spec.js b/spec/frontend/serverless/store/getters_spec.js
index fb549c8f153..92853fda37c 100644
--- a/spec/frontend/serverless/store/getters_spec.js
+++ b/spec/frontend/serverless/store/getters_spec.js
@@ -32,7 +32,7 @@ describe('Serverless Store Getters', () => {
describe('getFunctions', () => {
it('should translate the raw function array to group the functions per environment scope', () => {
- state.functions = mockServerlessFunctions;
+ state.functions = mockServerlessFunctions.functions;
const funcs = getters.getFunctions(state);
diff --git a/spec/frontend/serverless/store/mutations_spec.js b/spec/frontend/serverless/store/mutations_spec.js
index ca3053e5c38..e2771c7e5fd 100644
--- a/spec/frontend/serverless/store/mutations_spec.js
+++ b/spec/frontend/serverless/store/mutations_spec.js
@@ -19,13 +19,13 @@ describe('ServerlessMutations', () => {
expect(state.isLoading).toEqual(false);
expect(state.hasFunctionData).toEqual(true);
- expect(state.functions).toEqual(mockServerlessFunctions);
+ expect(state.functions).toEqual(mockServerlessFunctions.functions);
});
it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => {
const state = {};
- mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state);
+ mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state, { knative_installed: true });
expect(state.isLoading).toEqual(false);
expect(state.hasFunctionData).toEqual(false);
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index c24f0bc4776..7e7cc1488b8 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -15,7 +15,7 @@ afterEach(() =>
}),
);
-initializeTestTimeout(500);
+initializeTestTimeout(process.env.CI ? 5000 : 500);
// fail tests for unmocked requests
beforeEach(done => {
diff --git a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
index b356ea85cad..0f5d47b3bfe 100644
--- a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
+++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
@@ -4,7 +4,7 @@ describe('getStateKey', () => {
it('should return proper state name', () => {
const context = {
mergeStatus: 'checked',
- mergeWhenPipelineSucceeds: false,
+ autoMergeEnabled: false,
canMerge: true,
onlyAllowMergeIfPipelineSucceeds: false,
isPipelineFailed: false,
@@ -31,9 +31,9 @@ describe('getStateKey', () => {
expect(bound()).toEqual('notAllowedToMerge');
- context.mergeWhenPipelineSucceeds = true;
+ context.autoMergeEnabled = true;
- expect(bound()).toEqual('mergeWhenPipelineSucceeds');
+ expect(bound()).toEqual('autoMergeEnabled');
context.isSHAMismatch = true;
@@ -80,7 +80,7 @@ describe('getStateKey', () => {
it('returns rebased state key', () => {
const context = {
mergeStatus: 'checked',
- mergeWhenPipelineSucceeds: false,
+ autoMergeEnabled: false,
canMerge: true,
onlyAllowMergeIfPipelineSucceeds: true,
isPipelineFailed: true,
diff --git a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
index 4a8de5fc4f1..63880b85625 100644
--- a/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
+++ b/spec/frontend/vue_shared/components/issue/issue_warning_spec.js
@@ -15,31 +15,37 @@ function formatWarning(string) {
describe('Issue Warning Component', () => {
describe('isLocked', () => {
it('should render locked issue warning information', () => {
- const vm = mountComponent(IssueWarning, {
+ const props = {
isLocked: true,
- });
+ lockedIssueDocsPath: 'docs/issues/locked',
+ };
+ const vm = mountComponent(IssueWarning, props);
expect(
vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
).toMatch(/lock$/);
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
- 'This issue is locked. Only project members can comment.',
+ 'This issue is locked. Only project members can comment. Learn more',
);
+ expect(vm.$el.querySelector('a').href).toContain(props.lockedIssueDocsPath);
});
});
describe('isConfidential', () => {
it('should render confidential issue warning information', () => {
- const vm = mountComponent(IssueWarning, {
+ const props = {
isConfidential: true,
- });
+ confidentialIssueDocsPath: '/docs/issues/confidential',
+ };
+ const vm = mountComponent(IssueWarning, props);
expect(
vm.$el.querySelector('.icon use').getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
).toMatch(/eye-slash$/);
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual(
- 'This is a confidential issue. Your comment will not be visible to the public.',
+ 'This is a confidential issue. People without permission will never get a notification. Learn more',
);
+ expect(vm.$el.querySelector('a').href).toContain(props.confidentialIssueDocsPath);
});
});
diff --git a/spec/frontend/vue_shared/components/modal_copy_button_spec.js b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
new file mode 100644
index 00000000000..f1943861523
--- /dev/null
+++ b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
@@ -0,0 +1,40 @@
+import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import modalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+
+describe('modal copy button', () => {
+ const Component = Vue.extend(modalCopyButton);
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ beforeEach(() => {
+ wrapper = shallowMount(Component, {
+ propsData: {
+ text: 'copy me',
+ title: 'Copy this value into Clipboard!',
+ },
+ });
+ });
+
+ describe('clipboard', () => {
+ it('should fire a `success` event on click', () => {
+ document.execCommand = jest.fn(() => true);
+ window.getSelection = jest.fn(() => ({
+ toString: jest.fn(() => 'test'),
+ removeAllRanges: jest.fn(),
+ }));
+ wrapper.trigger('click');
+ expect(wrapper.emitted().success).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
+ });
+ it("should propagate the clipboard error event if execCommand doesn't work", () => {
+ document.execCommand = jest.fn(() => false);
+ wrapper.trigger('click');
+ expect(wrapper.emitted().error).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/droplab_dropdown_button_spec.js b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js
new file mode 100644
index 00000000000..22295721328
--- /dev/null
+++ b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js
@@ -0,0 +1,136 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+
+import DroplabDropdownButton from '~/vue_shared/components/droplab_dropdown_button.vue';
+
+const mockActions = [
+ {
+ title: 'Foo',
+ description: 'Some foo action',
+ },
+ {
+ title: 'Bar',
+ description: 'Some bar action',
+ },
+];
+
+const createComponent = ({
+ size = '',
+ dropdownClass = '',
+ actions = mockActions,
+ defaultAction = 0,
+}) => {
+ const localVue = createLocalVue();
+
+ return mount(DroplabDropdownButton, {
+ localVue,
+ propsData: {
+ size,
+ dropdownClass,
+ actions,
+ defaultAction,
+ },
+ });
+};
+
+describe('DroplabDropdownButton', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = createComponent({});
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('data', () => {
+ it('contains `selectedAction` representing value of `defaultAction` prop', () => {
+ expect(wrapper.vm.selectedAction).toBe(0);
+ });
+ });
+
+ describe('computed', () => {
+ describe('selectedActionTitle', () => {
+ it('returns string containing title of selected action', () => {
+ wrapper.setData({ selectedAction: 0 });
+
+ expect(wrapper.vm.selectedActionTitle).toBe(mockActions[0].title);
+
+ wrapper.setData({ selectedAction: 1 });
+
+ expect(wrapper.vm.selectedActionTitle).toBe(mockActions[1].title);
+ });
+ });
+
+ describe('buttonSizeClass', () => {
+ it('returns string containing button sizing class based on `size` prop', done => {
+ const wrapperWithSize = createComponent({
+ size: 'sm',
+ });
+
+ wrapperWithSize.vm.$nextTick(() => {
+ expect(wrapperWithSize.vm.buttonSizeClass).toBe('btn-sm');
+
+ done();
+ wrapperWithSize.destroy();
+ });
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('handlePrimaryActionClick', () => {
+ it('emits `onActionClick` event on component with selectedAction object as param', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+
+ wrapper.setData({ selectedAction: 0 });
+ wrapper.vm.handlePrimaryActionClick();
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionClick', mockActions[0]);
+ });
+ });
+
+ describe('handleActionClick', () => {
+ it('emits `onActionSelect` event on component with selectedAction index as param', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+
+ wrapper.vm.handleActionClick(1);
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionSelect', 1);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders default action button', () => {
+ const defaultButton = wrapper.findAll('.btn').at(0);
+
+ expect(defaultButton.text()).toBe(mockActions[0].title);
+ });
+
+ it('renders dropdown button', () => {
+ const dropdownButton = wrapper.findAll('.dropdown-toggle').at(0);
+
+ expect(dropdownButton.isVisible()).toBe(true);
+ });
+
+ it('renders dropdown actions', () => {
+ const dropdownActions = wrapper.findAll('.dropdown-menu li button');
+
+ Array(dropdownActions.length)
+ .fill()
+ .forEach((_, index) => {
+ const actionContent = dropdownActions.at(index).find('.description');
+
+ expect(actionContent.find('strong').text()).toBe(mockActions[index].title);
+ expect(actionContent.find('p').text()).toBe(mockActions[index].description);
+ });
+ });
+
+ it('renders divider between dropdown actions', () => {
+ const dropdownDivider = wrapper.find('.dropdown-menu .divider');
+
+ expect(dropdownDivider.isVisible()).toBe(true);
+ });
+ });
+});
diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb
index f5eb628a982..c427893f9cc 100644
--- a/spec/graphql/features/authorization_spec.rb
+++ b/spec/graphql/features/authorization_spec.rb
@@ -282,7 +282,7 @@ describe 'Gitlab::Graphql::Authorization' do
issue_ids = issue_edges.map { |issue_edge| issue_edge['node']&.fetch('id') }
expect(issue_edges.size).to eq(visible_issues.size)
- expect(issue_ids).to eq(visible_issues.map { |i| i.id.to_s })
+ expect(issue_ids).to eq(visible_issues.map { |i| i.to_global_id.to_s })
end
it 'does not check access on fields that will not be rendered' do
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index c138c87c4ac..4076c1f824b 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -56,10 +56,10 @@ describe GitlabSchema do
described_class.execute('query', context: {})
end
- it 'returns ANONYMOUS_MAX_DEPTH' do
+ it 'returns DEFAULT_MAX_DEPTH' do
expect(GraphQL::Schema)
.to receive(:execute)
- .with('query', hash_including(max_depth: GitlabSchema::ANONYMOUS_MAX_DEPTH))
+ .with('query', hash_including(max_depth: GitlabSchema::DEFAULT_MAX_DEPTH))
described_class.execute('query', context: {})
end
@@ -107,6 +107,64 @@ describe GitlabSchema do
end
end
+ describe '.id_from_object' do
+ it 'returns a global id' do
+ expect(described_class.id_from_object(build(:project, id: 1))).to be_a(GlobalID)
+ end
+
+ it "raises a meaningful error if a global id couldn't be generated" do
+ expect { described_class.id_from_object(build(:commit)) }
+ .to raise_error(RuntimeError, /include `GlobalID::Identification` into/i)
+ end
+ end
+
+ describe '.object_from_id' do
+ context 'for subclasses of `ApplicationRecord`' do
+ it 'returns the correct record' do
+ user = create(:user)
+
+ result = described_class.object_from_id(user.to_global_id.to_s)
+
+ expect(result.__sync).to eq(user)
+ end
+
+ it 'batchloads the queries' do
+ user1 = create(:user)
+ user2 = create(:user)
+
+ expect do
+ [described_class.object_from_id(user1.to_global_id),
+ described_class.object_from_id(user2.to_global_id)].map(&:__sync)
+ end.not_to exceed_query_limit(1)
+ end
+ end
+
+ context 'for other classes' do
+ # We cannot use an anonymous class here as `GlobalID` expects `.name` not
+ # to return `nil`
+ class TestGlobalId
+ include GlobalID::Identification
+ attr_accessor :id
+
+ def initialize(id)
+ @id = id
+ end
+ end
+
+ it 'falls back to a regular find' do
+ result = TestGlobalId.new(123)
+
+ expect(TestGlobalId).to receive(:find).with("123").and_return(result)
+
+ expect(described_class.object_from_id(result.to_global_id)).to eq(result)
+ end
+ end
+
+ it 'raises the correct error on invalid input' do
+ expect { described_class.object_from_id("bogus id") }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
+ end
+
def field_instrumenters
described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins]
end
diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
index 9982288e206..c162fdbbb47 100644
--- a/spec/graphql/resolvers/base_resolver_spec.rb
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -29,18 +29,20 @@ describe Resolvers::BaseResolver do
end
end
- it 'increases complexity based on arguments' do
- field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 1)
+ context 'when field is a connection' do
+ it 'increases complexity based on arguments' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 1)
- expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3
- expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7
- end
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3
+ expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7
+ end
- it 'does not increase complexity when filtering by iids' do
- field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100)
+ it 'does not increase complexity when filtering by iids' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
- expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6
- expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3
- expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3
+ expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3
+ end
end
end
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index bffcdbfe915..798fe00de97 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -121,7 +121,7 @@ describe Resolvers::IssuesResolver do
end
it 'increases field complexity based on arguments' do
- field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: described_class, null: false, max_page_size: 100)
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4
expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8
diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
new file mode 100644
index 00000000000..20e197e9f73
--- /dev/null
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::NamespaceProjectsResolver, :nested_groups do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+
+ context "with a group" do
+ let(:group) { create(:group) }
+ let(:namespace) { group }
+ let(:project1) { create(:project, namespace: namespace) }
+ let(:project2) { create(:project, namespace: namespace) }
+ let(:nested_group) { create(:group, parent: group) }
+ let(:nested_project) { create(:project, group: nested_group) }
+
+ before do
+ project1.add_developer(current_user)
+ project2.add_developer(current_user)
+ nested_project.add_developer(current_user)
+ end
+
+ describe '#resolve' do
+ it 'finds all projects' do
+ expect(resolve_projects).to contain_exactly(project1, project2)
+ end
+
+ it 'finds all projects including the subgroups' do
+ expect(resolve_projects(include_subgroups: true)).to contain_exactly(project1, project2, nested_project)
+ end
+
+ context 'with an user namespace' do
+ let(:namespace) { current_user.namespace }
+
+ it 'finds all projects' do
+ expect(resolve_projects).to contain_exactly(project1, project2)
+ end
+
+ it 'finds all projects including the subgroups' do
+ expect(resolve_projects(include_subgroups: true)).to contain_exactly(project1, project2)
+ end
+ end
+ end
+ end
+
+ context "when passing a non existent, batch loaded namespace" do
+ let(:namespace) do
+ BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _|
+ loader.call("non-existent-path", nil)
+ end
+ end
+
+ it "returns nil without breaking" do
+ expect(resolve_projects).to be_empty
+ end
+ end
+
+ it 'has an high complexity regardless of arguments' do
+ field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
+
+ expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24
+ expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24
+ end
+
+ def resolve_projects(args = { include_subgroups: false }, context = { current_user: current_user })
+ resolve(described_class, obj: namespace, args: args, ctx: context)
+ end
+end
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index 4fe426e2447..0d3c3e37daf 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -6,7 +6,7 @@ describe Types::BaseField do
context 'when considering complexity' do
let(:resolver) do
Class.new(described_class) do
- def self.resolver_complexity(args)
+ def self.resolver_complexity(args, child_complexity:)
2 if args[:foo]
end
@@ -28,18 +28,29 @@ describe Types::BaseField do
expect(field.to_graphql.complexity).to eq 12
end
- it 'sets complexity depending on arguments for resolvers' do
- field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
+ context 'when field has a resolver proc' do
+ context 'and is a connection' do
+ let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: resolver, max_page_size: 100, null: true) }
- expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4
- expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3
- end
+ it 'sets complexity depending on arguments for resolvers' do
+ expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4
+ expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3
+ end
- it 'sets complexity depending on number load limits for resolvers' do
- field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
+ it 'sets complexity depending on number load limits for resolvers' do
+ expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2
+ expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
+ end
+ end
- expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2
- expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
+ context 'and is not a connection' do
+ it 'sets complexity as normal' do
+ field = described_class.new(name: 'test', type: GraphQL::STRING_TYPE, resolver_class: resolver, max_page_size: 100, null: true)
+
+ expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 2
+ expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 2
+ end
+ end
end
end
end
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
index dc37b15001f..bae560829cc 100644
--- a/spec/graphql/types/issue_type_spec.rb
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -6,4 +6,10 @@ describe GitlabSchema.types['Issue'] do
it { expect(described_class.graphql_name).to eq('Issue') }
it { expect(described_class).to require_graphql_authorizations(:read_issue) }
+
+ it 'has specific fields' do
+ %i[relative_position web_path web_url reference].each do |field_name|
+ expect(described_class).to have_graphql_field(field_name)
+ end
+ end
end
diff --git a/spec/graphql/types/namespace_type.rb b/spec/graphql/types/namespace_type_spec.rb
index 7cd6a79ae5d..b4144cc4121 100644
--- a/spec/graphql/types/namespace_type.rb
+++ b/spec/graphql/types/namespace_type_spec.rb
@@ -4,4 +4,6 @@ require 'spec_helper'
describe GitlabSchema.types['Namespace'] do
it { expect(described_class.graphql_name).to eq('Namespace') }
+
+ it { expect(described_class).to have_graphql_field(:projects) }
end
diff --git a/spec/graphql/types/project_statistics_type_spec.rb b/spec/graphql/types/project_statistics_type_spec.rb
new file mode 100644
index 00000000000..e9feac57a36
--- /dev/null
+++ b/spec/graphql/types/project_statistics_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['ProjectStatistics'] do
+ it "has all the required fields" do
+ is_expected.to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
+ :build_artifacts_size, :packages_size, :commit_count,
+ :wiki_size)
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 075fa7c7e43..cb5ac2e3cb1 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -19,4 +19,6 @@ describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_field(:pipelines) }
it { is_expected.to have_graphql_field(:repository) }
+
+ it { is_expected.to have_graphql_field(:statistics) }
end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index b4626955816..af1972a2513 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -5,7 +5,17 @@ describe GitlabSchema.types['Query'] do
expect(described_class.graphql_name).to eq('Query')
end
- it { is_expected.to have_graphql_fields(:project, :group, :echo, :metadata) }
+ it { is_expected.to have_graphql_fields(:project, :namespace, :group, :echo, :metadata) }
+
+ describe 'namespace field' do
+ subject { described_class.fields['namespace'] }
+
+ it 'finds namespaces by full path' do
+ is_expected.to have_graphql_arguments(:full_path)
+ is_expected.to have_graphql_type(Types::NamespaceType)
+ is_expected.to have_graphql_resolver(Resolvers::NamespaceResolver)
+ end
+ end
describe 'project field' do
subject { described_class.fields['project'] }
diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb
index fa29bb5fff7..22c11aff90a 100644
--- a/spec/graphql/types/tree/blob_type_spec.rb
+++ b/spec/graphql/types/tree/blob_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::BlobType do
it { expect(described_class.graphql_name).to eq('Blob') }
- it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) }
+ it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url, :lfs_oid) }
end
diff --git a/spec/graphql/types/tree/tree_entry_type_spec.rb b/spec/graphql/types/tree/tree_entry_type_spec.rb
index 397cabde8e5..ea1b6426034 100644
--- a/spec/graphql/types/tree/tree_entry_type_spec.rb
+++ b/spec/graphql/types/tree/tree_entry_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::TreeEntryType do
it { expect(described_class.graphql_name).to eq('TreeEntry') }
- it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) }
+ it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) }
end
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 0434af25866..e6aacb5b92b 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -8,19 +8,19 @@ describe EmailsHelper do
context "and format is text" do
it "returns plain text" do
- expect(closure_reason_text(merge_request, format: :text)).to eq(" via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
end
end
context "and format is HTML" do
it "returns HTML" do
- expect(closure_reason_text(merge_request, format: :html)).to eq(" via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
+ expect(closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
end
end
context "and format is unknown" do
it "returns plain text" do
- expect(closure_reason_text(merge_request, format: :text)).to eq(" via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
end
end
end
@@ -29,7 +29,7 @@ describe EmailsHelper do
let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" }
it "returns plain text" do
- expect(closure_reason_text(closed_via)).to eq(" via #{closed_via}")
+ expect(closure_reason_text(closed_via)).to eq("via #{closed_via}")
end
end
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
new file mode 100644
index 00000000000..0c8a8d2f032
--- /dev/null
+++ b/spec/helpers/environments_helper_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe EnvironmentsHelper do
+ set(:environment) { create(:environment) }
+ set(:project) { environment.project }
+ set(:user) { create(:user) }
+
+ describe '#metrics_data' do
+ before do
+ # This is so that this spec also passes in EE.
+ allow(helper).to receive(:current_user).and_return(user)
+ allow(helper).to receive(:can?).and_return(true)
+ end
+
+ let(:metrics_data) { helper.metrics_data(project, environment) }
+
+ it 'returns data' do
+ expect(metrics_data).to include(
+ 'settings-path' => edit_project_service_path(project, 'prometheus'),
+ 'clusters-path' => project_clusters_path(project),
+ 'current-environment-name': environment.name,
+ 'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'),
+ 'empty-getting-started-svg-path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
+ 'empty-loading-svg-path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'),
+ 'empty-no-data-svg-path' => match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
+ 'empty-unable-to-connect-svg-path' => match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'),
+ 'metrics-endpoint' => additional_metrics_project_environment_path(project, environment, format: :json),
+ 'deployment-endpoint' => project_environment_deployments_path(project, environment, format: :json),
+ 'environments-endpoint': project_environments_path(project, format: :json),
+ 'project-path' => project_path(project),
+ 'tags-path' => project_tags_path(project),
+ 'has-metrics' => "#{environment.has_metrics?}",
+ 'external-dashboard-url' => nil
+ )
+ end
+
+ context 'with metrics_setting' do
+ before do
+ create(:project_metrics_setting, project: project, external_dashboard_url: 'http://gitlab.com')
+ end
+
+ it 'adds external_dashboard_url' do
+ expect(metrics_data['external-dashboard-url']).to eq('http://gitlab.com')
+ end
+ end
+ end
+end
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 143b28728a3..027480143bd 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -101,7 +101,7 @@ describe GitlabRoutingHelper do
it 'returns project milestone edit path when given entity parent is not a Group' do
milestone = create(:milestone, group: nil)
- expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/milestones/#{milestone.iid}/edit")
+ expect(edit_milestone_path(milestone)).to eq("/#{milestone.project.full_path}/-/milestones/#{milestone.iid}/edit")
end
end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 58eaf991d6e..314305d7a8e 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -6,7 +6,7 @@ describe LabelsHelper do
let(:context_project) { project }
context "when asking for a #{issuables_type} link" do
- subject { show_label_issuables_link?(label, issuables_type, project: context_project) }
+ subject { show_label_issuables_link?(label.present(issuable_subject: nil), issuables_type, project: context_project) }
context "when #{issuables_type} are enabled for the project" do
let(:project) { create(:project, "#{issuables_type}_access_level": ProjectFeature::ENABLED) }
@@ -279,4 +279,21 @@ describe LabelsHelper do
expect(label.color).to eq('bar')
end
end
+
+ describe '#label_status_tooltip' do
+ let(:status) { 'unsubscribed'.inquiry }
+ subject { label_status_tooltip(label.present(issuable_subject: nil), status) }
+
+ context 'with a project label' do
+ let(:label) { create(:label, title: 'bug') }
+
+ it { is_expected.to eq('Subscribe at project level') }
+ end
+
+ context 'with a group label' do
+ let(:label) { create(:group_label, title: 'bug') }
+
+ it { is_expected.to eq('Subscribe at group level') }
+ end
+ end
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index c3956ba08fd..597c8f836a9 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -78,7 +78,8 @@ describe MarkupHelper do
let(:link) { '/commits/0a1b2c3d' }
let(:issues) { create_list(:issue, 2, project: project) }
- it 'handles references nested in links with all the text' do
+ # Clean the cache to make sure the title is re-rendered from the stubbed one
+ it 'handles references nested in links with all the text', :clean_gitlab_redis_cache do
allow(commit).to receive(:title).and_return("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real")
actual = helper.link_to_markdown_field(commit, :title, link)
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 83271aa24a3..3716879c458 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -819,4 +819,26 @@ describe ProjectsHelper do
expect(helper.can_import_members?).to eq true
end
end
+
+ describe '#metrics_external_dashboard_url' do
+ let(:project) { create(:project) }
+
+ before do
+ helper.instance_variable_set(:@project, project)
+ end
+
+ context 'metrics_setting exists' do
+ it 'returns external_dashboard_url' do
+ metrics_setting = create(:project_metrics_setting, project: project)
+
+ expect(helper.metrics_external_dashboard_url).to eq(metrics_setting.external_dashboard_url)
+ end
+ end
+
+ context 'metrics_setting does not exist' do
+ it 'returns nil' do
+ expect(helper.metrics_external_dashboard_url).to eq(nil)
+ end
+ end
+ end
end
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index 50c74a7c2f9..62c00964524 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -26,11 +26,12 @@ describe StorageHelper do
namespace: namespace,
statistics: build(:project_statistics,
repository_size: 10.kilobytes,
+ wiki_size: 10.bytes,
lfs_objects_size: 20.gigabytes,
build_artifacts_size: 30.megabytes))
end
- let(:message) { '10 KB repositories, 30 MB build artifacts, 20 GB LFS' }
+ let(:message) { '10 KB repositories, 10 Bytes wikis, 30 MB build artifacts, 20 GB LFS' }
it 'works on ProjectStatistics' do
expect(helper.storage_counters_details(project.statistics)).to eq(message)
diff --git a/spec/helpers/tracking_helper_spec.rb b/spec/helpers/tracking_helper_spec.rb
new file mode 100644
index 00000000000..71505e8ea69
--- /dev/null
+++ b/spec/helpers/tracking_helper_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe TrackingHelper do
+ describe '#tracking_attrs' do
+ it 'returns an empty hash' do
+ expect(helper.tracking_attrs('a', 'b', 'c')).to eq({})
+ end
+ end
+end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index e565ac8c530..25a2fcf5a81 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -162,4 +162,49 @@ describe VisibilityLevelHelper do
end
end
end
+
+ describe "selected_visibility_level" do
+ let(:group) { create(:group, :public) }
+ let!(:project) { create(:project, :internal, group: group) }
+ let!(:forked_project) { fork_project(project) }
+
+ using RSpec::Parameterized::TableSyntax
+
+ PUBLIC = Gitlab::VisibilityLevel::PUBLIC
+ INTERNAL = Gitlab::VisibilityLevel::INTERNAL
+ PRIVATE = Gitlab::VisibilityLevel::PRIVATE
+
+ # This is a subset of all the permutations
+ where(:requested_level, :max_allowed, :global_default_level, :restricted_levels, :expected) do
+ PUBLIC | PUBLIC | PUBLIC | [] | PUBLIC
+ PUBLIC | PUBLIC | PUBLIC | [PUBLIC] | INTERNAL
+ INTERNAL | PUBLIC | PUBLIC | [] | INTERNAL
+ INTERNAL | PRIVATE | PRIVATE | [] | PRIVATE
+ PRIVATE | PUBLIC | PUBLIC | [] | PRIVATE
+ PUBLIC | PRIVATE | INTERNAL | [] | PRIVATE
+ PUBLIC | INTERNAL | PUBLIC | [] | INTERNAL
+ PUBLIC | PRIVATE | PUBLIC | [] | PRIVATE
+ PUBLIC | INTERNAL | INTERNAL | [] | INTERNAL
+ PUBLIC | PUBLIC | INTERNAL | [] | PUBLIC
+ end
+
+ before do
+ stub_application_setting(restricted_visibility_levels: restricted_levels,
+ default_project_visibility: global_default_level)
+ end
+
+ with_them do
+ it "provides correct visibility level for forked project" do
+ project.update(visibility_level: max_allowed)
+
+ expect(selected_visibility_level(forked_project, requested_level)).to eq(expected)
+ end
+
+ it "provides correct visibiility level for project in group" do
+ project.group.update(visibility_level: max_allowed)
+
+ expect(selected_visibility_level(project, requested_level)).to eq(expected)
+ end
+ end
+ end
end
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index 77bc28a6b07..726ce07a2d1 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -45,21 +45,11 @@ describe 'create_tokens' do
expect(keys).to all(match(RSA_KEY))
end
- it "generates private key for Let's Encrypt" do
- create_tokens
-
- keys = secrets.values_at(:lets_encrypt_private_key)
-
- expect(keys.uniq).to eq(keys)
- expect(keys).to all(match(RSA_KEY))
- end
-
it 'warns about the secrets to add to secrets.yml' do
expect(self).to receive(:warn_missing_secret).with('secret_key_base')
expect(self).to receive(:warn_missing_secret).with('otp_key_base')
expect(self).to receive(:warn_missing_secret).with('db_key_base')
expect(self).to receive(:warn_missing_secret).with('openid_connect_signing_key')
- expect(self).to receive(:warn_missing_secret).with('lets_encrypt_private_key')
create_tokens
end
@@ -88,7 +78,6 @@ describe 'create_tokens' do
before do
secrets.db_key_base = 'db_key_base'
secrets.openid_connect_signing_key = 'openid_connect_signing_key'
- secrets.lets_encrypt_private_key = 'lets_encrypt_private_key'
allow(File).to receive(:exist?).with('.secret').and_return(true)
allow(File).to receive(:read).with('.secret').and_return('file_key')
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index e1017130bed..13b708a03d5 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -7,8 +7,8 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import eventHub from '~/boards/eventhub';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/list';
import boardsStore from '~/boards/stores/boards_store';
import boardCard from '~/boards/components/board_card.vue';
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 22f192bc7f3..e81115e10c9 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -6,12 +6,13 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
import boardsStore from '~/boards/stores/boards_store';
+import eventHub from '~/boards/eventhub';
import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data';
describe('Store', () => {
@@ -44,6 +45,48 @@ describe('Store', () => {
expect(boardsStore.state.lists.length).toBe(0);
});
+ describe('addList', () => {
+ it('sorts by position', () => {
+ boardsStore.addList({ position: 2 });
+ boardsStore.addList({ position: 1 });
+
+ expect(boardsStore.state.lists[0].position).toBe(1);
+ });
+ });
+
+ describe('toggleFilter', () => {
+ const dummyFilter = 'x=42';
+ let updateTokensSpy;
+
+ beforeEach(() => {
+ updateTokensSpy = jasmine.createSpy('updateTokens');
+ eventHub.$once('updateTokens', updateTokensSpy);
+
+ // prevent using window.history
+ spyOn(boardsStore, 'updateFiltersUrl').and.callFake(() => {});
+ });
+
+ it('adds the filter if it is not present', () => {
+ boardsStore.filter.path = 'something';
+
+ boardsStore.toggleFilter(dummyFilter);
+
+ expect(boardsStore.filter.path).toEqual(`something&${dummyFilter}`);
+ expect(updateTokensSpy).toHaveBeenCalled();
+ expect(boardsStore.updateFiltersUrl).toHaveBeenCalled();
+ });
+
+ it('removes the filter if it is present', () => {
+ boardsStore.filter.path = `something&${dummyFilter}`;
+
+ boardsStore.toggleFilter(dummyFilter);
+
+ expect(boardsStore.filter.path).toEqual('something');
+ expect(updateTokensSpy).toHaveBeenCalled();
+ expect(boardsStore.updateFiltersUrl).toHaveBeenCalled();
+ });
+ });
+
describe('lists', () => {
it('creates new list without persisting to DB', () => {
boardsStore.addList(listObj);
@@ -268,4 +311,48 @@ describe('Store', () => {
});
});
});
+
+ describe('setListDetail', () => {
+ it('sets the list detail', () => {
+ boardsStore.detail.list = 'not a list';
+
+ const dummyValue = 'new list';
+ boardsStore.setListDetail(dummyValue);
+
+ expect(boardsStore.detail.list).toEqual(dummyValue);
+ });
+ });
+
+ describe('clearDetailIssue', () => {
+ it('resets issue details', () => {
+ boardsStore.detail.issue = 'something';
+
+ boardsStore.clearDetailIssue();
+
+ expect(boardsStore.detail.issue).toEqual({});
+ });
+ });
+
+ describe('setIssueDetail', () => {
+ it('sets issue details', () => {
+ boardsStore.detail.issue = 'some details';
+
+ const dummyValue = 'new details';
+ boardsStore.setIssueDetail(dummyValue);
+
+ expect(boardsStore.detail.issue).toEqual(dummyValue);
+ });
+ });
+
+ describe('startMoving', () => {
+ it('stores list and issue', () => {
+ const dummyIssue = 'some issue';
+ const dummyList = 'some list';
+
+ boardsStore.startMoving(dummyList, dummyIssue);
+
+ expect(boardsStore.moving.issue).toEqual(dummyIssue);
+ expect(boardsStore.moving.list).toEqual(dummyList);
+ });
+ });
});
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index a5bf97bdcc2..8a20911cc66 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -4,8 +4,8 @@
import Vue from 'vue';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index e4ff3eb381f..bb7abe52eae 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -1,8 +1,8 @@
/* global ListIssue */
import Vue from 'vue';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index bb6fc6c693d..15c9ff6dfb4 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -4,8 +4,8 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
-import '~/vue_shared/models/label';
-import '~/vue_shared/models/assignee';
+import '~/boards/models/label';
+import '~/boards/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index 93a0f29af0a..9854cf49e97 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -47,7 +47,7 @@ export const BoardsMockData = {
},
],
},
- '/test/issue-boards/milestones.json': [
+ '/test/issue-boards/-/milestones.json': [
{
id: 1,
title: 'test',
@@ -58,10 +58,10 @@ export const BoardsMockData = {
'/test/-/boards/1/lists': listObj,
},
PUT: {
- '/test/issue-boards/board/1/lists{/id}': {},
+ '/test/issue-boards/-/board/1/lists{/id}': {},
},
DELETE: {
- '/test/issue-boards/board/1/lists{/id}': {},
+ '/test/issue-boards/-/board/1/lists{/id}': {},
},
};
@@ -71,7 +71,7 @@ export const boardsMockInterceptor = config => {
};
export const mockBoardService = (opts = {}) => {
- const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/boards.json';
+ const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/-/boards.json';
const listsEndpoint = opts.listsEndpoint || '/test/-/boards/1/lists';
const bulkUpdatePath = opts.bulkUpdatePath || '';
const boardId = opts.boardId || '1';
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index 2839922fbd3..e6a969bd855 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -32,6 +32,7 @@ describe('AjaxFormVariableList', () => {
saveButton,
errorBox,
saveEndpoint: container.dataset.saveEndpoint,
+ maskableRegex: container.dataset.maskableRegex,
});
spyOn(ajaxVariableList, 'updateRowsWithPersistedVariables').and.callThrough();
@@ -220,4 +221,11 @@ describe('AjaxFormVariableList', () => {
expect(row.dataset.isPersisted).toEqual('true');
});
});
+
+ describe('maskableRegex', () => {
+ it('takes in the regex provided by the data attribute', () => {
+ expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex);
+ });
+ });
});
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 394e60fc22c..064113e879a 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -150,6 +150,65 @@ describe('VariableList', () => {
.then(done)
.catch(done.fail);
});
+
+ describe('validateMaskability', () => {
+ let $row;
+
+ const maskingErrorElement = '.js-row:last-child .masking-validation-error';
+
+ beforeEach(() => {
+ $row = $wrapper.find('.js-row:last-child');
+ $row.find('.ci-variable-masked-item .js-project-feature-toggle').click();
+ });
+
+ it('has a regex provided via a data attribute', () => {
+ expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ });
+
+ it('allows values that are 8 characters long', done => {
+ $row.find('.js-ci-variable-input-value').val('looooong');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toHaveClass('hide');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('rejects values that are shorter than 8 characters', done => {
+ $row.find('.js-ci-variable-input-value').val('short');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toBeVisible();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('allows values with base 64 characters', done => {
+ $row.find('.js-ci-variable-input-value').val('abcABC123_+=/-');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toHaveClass('hide');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('rejects values with other special characters', done => {
+ $row.find('.js-ci-variable-input-value').val('1234567$');
+
+ getSetTimeoutPromise()
+ .then(() => {
+ expect($wrapper.find(maskingErrorElement)).toBeVisible();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
describe('toggleEnableRow method', () => {
diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js
index 8fc9b10dd0b..dc3fb16eb40 100644
--- a/spec/javascripts/diffs/components/commit_item_spec.js
+++ b/spec/javascripts/diffs/components/commit_item_spec.js
@@ -8,7 +8,7 @@ import getDiffWithCommit from '../mock_data/diff_with_commit';
const TEST_AUTHOR_NAME = 'test';
const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com';
-const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=36`;
+const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=40`;
const TEST_SIGNATURE_HTML = '<a>Legit commit</a>';
const TEST_PIPELINE_STATUS_PATH = `${TEST_HOST}/pipeline/status`;
@@ -18,7 +18,7 @@ const getDescExpandElement = vm =>
vm.$el.querySelector('.commit-content .text-expander.js-toggle-button');
const getShaElement = vm => vm.$el.querySelector('.commit-sha-group');
const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link');
-const getCommitterElement = vm => vm.$el.querySelector('.commiter');
+const getCommitterElement = vm => vm.$el.querySelector('.committer');
const getCommitActionsElement = vm => vm.$el.querySelector('.commit-actions');
describe('diffs/components/commit_item', () => {
@@ -65,7 +65,7 @@ describe('diffs/components/commit_item', () => {
const imgElement = avatarElement.querySelector('img');
expect(avatarElement).toHaveAttr('href', commit.author.web_url);
- expect(imgElement).toHaveClass('s36');
+ expect(imgElement).toHaveClass('s40');
expect(imgElement).toHaveAttr('alt', commit.author.name);
expect(imgElement).toHaveAttr('src', commit.author.avatar_url);
});
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index 6309a8823d7..f129fbb57a3 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -396,6 +396,7 @@ describe('DiffsStoreActions', () => {
});
describe('loadCollapsedDiff', () => {
+ const state = { showWhitespace: true };
it('should fetch data and call mutation with response and the give parameter', done => {
const file = { hash: 123, load_collapsed_diff_url: '/load/collapsed/diff/url' };
const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] };
@@ -403,7 +404,7 @@ describe('DiffsStoreActions', () => {
const commit = jasmine.createSpy('commit');
mock.onGet(file.loadCollapsedDiffUrl).reply(200, data);
- loadCollapsedDiff({ commit, getters: { commitId: null } }, file)
+ loadCollapsedDiff({ commit, getters: { commitId: null }, state }, file)
.then(() => {
expect(commit).toHaveBeenCalledWith(types.ADD_COLLAPSED_DIFFS, { file, data });
@@ -421,10 +422,10 @@ describe('DiffsStoreActions', () => {
spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
- loadCollapsedDiff({ commit() {}, getters }, file);
+ loadCollapsedDiff({ commit() {}, getters, state }, file);
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
- params: { commit_id: null },
+ params: { commit_id: null, w: '0' },
});
});
@@ -436,10 +437,10 @@ describe('DiffsStoreActions', () => {
spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
- loadCollapsedDiff({ commit() {}, getters }, file);
+ loadCollapsedDiff({ commit() {}, getters, state }, file);
expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
- params: { commit_id: '123' },
+ params: { commit_id: '123', w: '0' },
});
});
});
diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js
index 19e27388eeb..121e99c9783 100644
--- a/spec/javascripts/helpers/vue_test_utils_helper.js
+++ b/spec/javascripts/helpers/vue_test_utils_helper.js
@@ -16,4 +16,6 @@ const vNodeContainsText = (vnode, text) =>
* @param {String} text
*/
export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =>
- !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length;
+ Boolean(
+ shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
+ );
diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
index 23e6a055518..b903abe63fc 100644
--- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
@@ -73,47 +73,4 @@ describe('IDE commit sidebar actions', () => {
expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc);
});
});
-
- describe('create new MR checkbox', () => {
- it('disables `createMR` button when an MR already exists and committing to current branch', () => {
- createComponent({ hasMR: true, commitAction: consts.COMMIT_TO_CURRENT_BRANCH });
-
- expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(true);
- });
-
- it('does not disable checkbox if MR does not exist', () => {
- createComponent({ hasMR: false });
-
- expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false);
- });
-
- it('does not disable checkbox when creating a new branch', () => {
- createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH });
-
- expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false);
- });
-
- it('toggles off new MR when switching back to commit to current branch and MR exists', () => {
- createComponent({
- commitAction: consts.COMMIT_TO_NEW_BRANCH,
- shouldCreateMR: true,
- });
- const currentBranchRadio = vm.$el.querySelector(
- `input[value="${consts.COMMIT_TO_CURRENT_BRANCH}"`,
- );
- currentBranchRadio.click();
-
- vm.$nextTick(() => {
- expect(vm.$store.state.commit.shouldCreateMR).toBe(false);
- });
- });
-
- it('toggles `shouldCreateMR` when clicking checkbox', () => {
- createComponent();
- const el = vm.$el.querySelector('input[type="checkbox"]');
- el.dispatchEvent(new Event('change'));
-
- expect(vm.$store.state.commit.shouldCreateMR).toBe(true);
- });
- });
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
new file mode 100644
index 00000000000..7017bfcd6a6
--- /dev/null
+++ b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
@@ -0,0 +1,73 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import consts from '~/ide/stores/modules/commit/constants';
+import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { projectData } from 'spec/ide/mock_data';
+import { resetStore } from 'spec/ide/helpers';
+
+describe('create new MR checkbox', () => {
+ let vm;
+ const createComponent = ({
+ hasMR = false,
+ commitAction = consts.COMMIT_TO_NEW_BRANCH,
+ currentBranchId = 'master',
+ } = {}) => {
+ const Component = Vue.extend(NewMergeRequestOption);
+
+ vm = createComponentWithStore(Component, store);
+
+ vm.$store.state.currentBranchId = currentBranchId;
+ vm.$store.state.currentProjectId = 'abcproject';
+ vm.$store.state.commit.commitAction = commitAction;
+ Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData });
+
+ if (hasMR) {
+ vm.$store.state.currentMergeRequestId = '1';
+ vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
+ store.state.currentMergeRequestId
+ ] = { foo: 'bar' };
+ }
+
+ return vm.$mount();
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+
+ resetStore(vm.$store);
+ });
+
+ it('is hidden when an MR already exists and committing to current branch', () => {
+ createComponent({
+ hasMR: true,
+ commitAction: consts.COMMIT_TO_CURRENT_BRANCH,
+ currentBranchId: 'feature',
+ });
+
+ expect(vm.$el.textContent).toBe('');
+ });
+
+ it('does not hide checkbox if MR does not exist', () => {
+ createComponent({ hasMR: false });
+
+ expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ });
+
+ it('does not hide checkbox when creating a new branch', () => {
+ createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH });
+
+ expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ });
+
+ it('dispatches toggleShouldCreateMR when clicking checkbox', () => {
+ createComponent();
+ const el = vm.$el.querySelector('input[type="checkbox"]');
+ spyOn(vm.$store, 'dispatch');
+ el.dispatchEvent(new Event('change'));
+
+ expect(vm.$store.dispatch.calls.allArgs()).toEqual(
+ jasmine.arrayContaining([['commit/toggleShouldCreateMR', jasmine.any(Object)]]),
+ );
+ });
+});
diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js
index dc5790f6562..de4becec1cd 100644
--- a/spec/javascripts/ide/components/ide_spec.js
+++ b/spec/javascripts/ide/components/ide_spec.js
@@ -5,21 +5,53 @@ import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helpe
import { file, resetStore } from '../helpers';
import { projectData } from '../mock_data';
-describe('ide component', () => {
+function bootstrap(projData) {
+ const Component = Vue.extend(ide);
+
+ store.state.currentProjectId = 'abcproject';
+ store.state.currentBranchId = 'master';
+ store.state.projects.abcproject = Object.assign({}, projData);
+ Vue.set(store.state.trees, 'abcproject/master', {
+ tree: [],
+ loading: false,
+ });
+
+ return createComponentWithStore(Component, store, {
+ emptyStateSvgPath: 'svg',
+ noChangesStateSvgPath: 'svg',
+ committedStateSvgPath: 'svg',
+ });
+}
+
+describe('ide component, empty repo', () => {
let vm;
beforeEach(() => {
- const Component = Vue.extend(ide);
+ const emptyProjData = Object.assign({}, projectData, { empty_repo: true, branches: {} });
+ vm = bootstrap(emptyProjData);
+ vm.$mount();
+ });
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
- store.state.projects.abcproject = Object.assign({}, projectData);
+ afterEach(() => {
+ vm.$destroy();
+
+ resetStore(vm.$store);
+ });
- vm = createComponentWithStore(Component, store, {
- emptyStateSvgPath: 'svg',
- noChangesStateSvgPath: 'svg',
- committedStateSvgPath: 'svg',
- }).$mount();
+ it('renders "New file" button in empty repo', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).not.toBeNull();
+ done();
+ });
+ });
+});
+
+describe('ide component, non-empty repo', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = bootstrap(projectData);
+ vm.$mount();
});
afterEach(() => {
@@ -28,17 +60,15 @@ describe('ide component', () => {
resetStore(vm.$store);
});
- it('does not render right when no files open', () => {
- expect(vm.$el.querySelector('.panel-right')).toBeNull();
- });
+ it('shows error message when set', done => {
+ expect(vm.$el.querySelector('.flash-container')).toBe(null);
- it('renders right panel when files are open', done => {
- vm.$store.state.trees['abcproject/mybranch'] = {
- tree: [file()],
+ vm.$store.state.errorMessage = {
+ text: 'error',
};
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.panel-right')).toBeNull();
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.flash-container')).not.toBe(null);
done();
});
@@ -71,17 +101,25 @@ describe('ide component', () => {
});
});
- it('shows error message when set', done => {
- expect(vm.$el.querySelector('.flash-container')).toBe(null);
-
- vm.$store.state.errorMessage = {
- text: 'error',
- };
+ describe('non-existent branch', () => {
+ it('does not render "New file" button for non-existent branch when repo is not empty', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull();
+ done();
+ });
+ });
+ });
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.flash-container')).not.toBe(null);
+ describe('branch with files', () => {
+ beforeEach(() => {
+ store.state.trees['abcproject/master'].tree = [file()];
+ });
- done();
+ it('does not render "New file" button', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull();
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/ide/components/ide_tree_list_spec.js b/spec/javascripts/ide/components/ide_tree_list_spec.js
index 4ecbdb8a55e..f63007c7dd2 100644
--- a/spec/javascripts/ide/components/ide_tree_list_spec.js
+++ b/spec/javascripts/ide/components/ide_tree_list_spec.js
@@ -7,25 +7,23 @@ import { projectData } from '../mock_data';
describe('IDE tree list', () => {
const Component = Vue.extend(IdeTreeList);
+ const normalBranchTree = [file('fileName')];
+ const emptyBranchTree = [];
let vm;
- beforeEach(() => {
+ const bootstrapWithTree = (tree = normalBranchTree) => {
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = Object.assign({}, projectData);
Vue.set(store.state.trees, 'abcproject/master', {
- tree: [file('fileName')],
+ tree,
loading: false,
});
vm = createComponentWithStore(Component, store, {
viewerType: 'edit',
});
-
- spyOn(vm, 'updateViewer').and.callThrough();
-
- vm.$mount();
- });
+ };
afterEach(() => {
vm.$destroy();
@@ -33,22 +31,47 @@ describe('IDE tree list', () => {
resetStore(vm.$store);
});
- it('updates viewer on mount', () => {
- expect(vm.updateViewer).toHaveBeenCalledWith('edit');
- });
+ describe('normal branch', () => {
+ beforeEach(() => {
+ bootstrapWithTree();
+
+ spyOn(vm, 'updateViewer').and.callThrough();
+
+ vm.$mount();
+ });
+
+ it('updates viewer on mount', () => {
+ expect(vm.updateViewer).toHaveBeenCalledWith('edit');
+ });
+
+ it('renders loading indicator', done => {
+ store.state.trees['abcproject/master'].loading = true;
- it('renders loading indicator', done => {
- store.state.trees['abcproject/master'].loading = true;
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull();
+ expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3);
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull();
- expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3);
+ done();
+ });
+ });
- done();
+ it('renders list of files', () => {
+ expect(vm.$el.textContent).toContain('fileName');
});
});
- it('renders list of files', () => {
- expect(vm.$el.textContent).toContain('fileName');
+ describe('empty-branch state', () => {
+ beforeEach(() => {
+ bootstrapWithTree(emptyBranchTree);
+
+ spyOn(vm, 'updateViewer').and.callThrough();
+
+ vm.$mount();
+ });
+
+ it('does not load files if the branch is empty', () => {
+ expect(vm.$el.textContent).not.toContain('fileName');
+ expect(vm.$el.textContent).toContain('No files');
+ });
});
});
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index 4fe826943b2..570a396c5e3 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -16,6 +16,7 @@ export const projectData = {
},
mergeRequests: {},
merge_requests_enabled: true,
+ default_branch: 'master',
};
export const pipelines = [
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index e6fb08bcc49..dd2313dc800 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -719,4 +719,20 @@ describe('IDE store file actions', () => {
.catch(done.fail);
});
});
+
+ describe('triggerFilesChange', () => {
+ beforeEach(() => {
+ spyOn(eventHub, '$emit');
+ });
+
+ it('emits event that files have changed', done => {
+ store
+ .dispatch('triggerFilesChange')
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('ide.files.change');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js
index cd519eaed7c..8ecb6129c63 100644
--- a/spec/javascripts/ide/stores/actions/project_spec.js
+++ b/spec/javascripts/ide/stores/actions/project_spec.js
@@ -4,7 +4,7 @@ import {
refreshLastCommitData,
showBranchNotFoundError,
createNewBranchFromDefault,
- getBranchData,
+ showEmptyState,
openBranch,
} from '~/ide/stores/actions';
import store from '~/ide/stores';
@@ -196,39 +196,44 @@ describe('IDE store project actions', () => {
});
});
- describe('getBranchData', () => {
- describe('error', () => {
- it('dispatches branch not found action when response is 404', done => {
- const dispatch = jasmine.createSpy('dispatchSpy');
-
- mock.onGet(/(.*)/).replyOnce(404);
-
- getBranchData(
+ describe('showEmptyState', () => {
+ it('commits proper mutations when supplied error is 404', done => {
+ testAction(
+ showEmptyState,
+ {
+ err: {
+ response: {
+ status: 404,
+ },
+ },
+ projectId: 'abc/def',
+ branchId: 'master',
+ },
+ store.state,
+ [
{
- commit() {},
- dispatch,
- state: store.state,
+ type: 'CREATE_TREE',
+ payload: {
+ treePath: 'abc/def/master',
+ },
},
{
- projectId: 'abc/def',
- branchId: 'master-testing',
+ type: 'TOGGLE_LOADING',
+ payload: {
+ entry: store.state.trees['abc/def/master'],
+ forceValue: false,
+ },
},
- )
- .then(done.fail)
- .catch(() => {
- expect(dispatch.calls.argsFor(0)).toEqual([
- 'showBranchNotFoundError',
- 'master-testing',
- ]);
- done();
- });
- });
+ ],
+ [],
+ done,
+ );
});
});
describe('openBranch', () => {
const branch = {
- projectId: 'feature/lorem-ipsum',
+ projectId: 'abc/def',
branchId: '123-lorem',
};
@@ -238,63 +243,113 @@ describe('IDE store project actions', () => {
'foo/bar-pending': { pending: true },
'foo/bar': { pending: false },
};
-
- spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
});
- it('dispatches branch actions', done => {
- openBranch(store, branch)
- .then(() => {
- expect(store.dispatch.calls.allArgs()).toEqual([
- ['setCurrentBranchId', branch.branchId],
- ['getBranchData', branch],
- ['getFiles', branch],
- ['getMergeRequestsForBranch', branch],
- ]);
- })
- .then(done)
- .catch(done.fail);
- });
+ describe('empty repo', () => {
+ beforeEach(() => {
+ spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
- it('handles tree entry action, if basePath is given', done => {
- openBranch(store, { ...branch, basePath: 'foo/bar/' })
- .then(() => {
- expect(store.dispatch).toHaveBeenCalledWith(
- 'handleTreeEntryAction',
- store.state.entries['foo/bar'],
- );
- })
- .then(done)
- .catch(done.fail);
+ store.state.currentProjectId = 'abc/def';
+ store.state.projects['abc/def'] = {
+ empty_repo: true,
+ };
+ });
+
+ afterEach(() => {
+ resetStore(store);
+ });
+
+ it('dispatches showEmptyState action right away', done => {
+ openBranch(store, branch)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['setCurrentBranchId', branch.branchId],
+ ['showEmptyState', branch],
+ ]);
+ done();
+ })
+ .catch(done.fail);
+ });
});
- it('does not handle tree entry action, if entry is pending', done => {
- openBranch(store, { ...branch, basePath: 'foo/bar-pending' })
- .then(() => {
- expect(store.dispatch).not.toHaveBeenCalledWith(
- 'handleTreeEntryAction',
- jasmine.anything(),
- );
- })
- .then(done)
- .catch(done.fail);
+ describe('existing branch', () => {
+ beforeEach(() => {
+ spyOn(store, 'dispatch').and.returnValue(Promise.resolve());
+ });
+
+ it('dispatches branch actions', done => {
+ openBranch(store, branch)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['setCurrentBranchId', branch.branchId],
+ ['getBranchData', branch],
+ ['getMergeRequestsForBranch', branch],
+ ['getFiles', branch],
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('handles tree entry action, if basePath is given', done => {
+ openBranch(store, { ...branch, basePath: 'foo/bar/' })
+ .then(() => {
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ store.state.entries['foo/bar'],
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not handle tree entry action, if entry is pending', done => {
+ openBranch(store, { ...branch, basePath: 'foo/bar-pending' })
+ .then(() => {
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ jasmine.anything(),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('creates a new file supplied via URL if the file does not exist yet', done => {
+ openBranch(store, { ...branch, basePath: 'not-existent.md' })
+ .then(() => {
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ 'handleTreeEntryAction',
+ jasmine.anything(),
+ );
+
+ expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
+ name: 'not-existent.md',
+ type: 'blob',
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
- it('creates a new file supplied via URL if the file does not exist yet', done => {
- openBranch(store, { ...branch, basePath: 'not-existent.md' })
- .then(() => {
- expect(store.dispatch).not.toHaveBeenCalledWith(
- 'handleTreeEntryAction',
- jasmine.anything(),
- );
+ describe('non-existent branch', () => {
+ beforeEach(() => {
+ spyOn(store, 'dispatch').and.returnValue(Promise.reject());
+ });
- expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
- name: 'not-existent.md',
- type: 'blob',
- });
- })
- .then(done)
- .catch(done.fail);
+ it('dispatches correct branch actions', done => {
+ openBranch(store, branch)
+ .then(() => {
+ expect(store.dispatch.calls.allArgs()).toEqual([
+ ['setCurrentBranchId', branch.branchId],
+ ['getBranchData', branch],
+ ['showBranchNotFoundError', branch.branchId],
+ ]);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
});
});
diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js
index 5ed9b9003a7..674ecdc6764 100644
--- a/spec/javascripts/ide/stores/actions/tree_spec.js
+++ b/spec/javascripts/ide/stores/actions/tree_spec.js
@@ -93,38 +93,6 @@ describe('Multi-file store tree actions', () => {
});
describe('error', () => {
- it('dispatches branch not found actions when response is 404', done => {
- const dispatch = jasmine.createSpy('dispatchSpy');
-
- store.state.projects = {
- 'abc/def': {
- web_url: `${gl.TEST_HOST}/files`,
- },
- };
-
- mock.onGet(/(.*)/).replyOnce(404);
-
- getFiles(
- {
- commit() {},
- dispatch,
- state: store.state,
- },
- {
- projectId: 'abc/def',
- branchId: 'master-testing',
- },
- )
- .then(done.fail)
- .catch(() => {
- expect(dispatch.calls.argsFor(0)).toEqual([
- 'showBranchNotFoundError',
- 'master-testing',
- ]);
- done();
- });
- });
-
it('dispatches error action', done => {
const dispatch = jasmine.createSpy('dispatchSpy');
diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js
index 0b5587d02ae..37354283cab 100644
--- a/spec/javascripts/ide/stores/actions_spec.js
+++ b/spec/javascripts/ide/stores/actions_spec.js
@@ -9,12 +9,15 @@ import actions, {
setErrorMessage,
deleteEntry,
renameEntry,
+ getBranchData,
} from '~/ide/stores/actions';
+import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
import * as types from '~/ide/stores/mutation_types';
import router from '~/ide/ide_router';
import { resetStore, file } from '../helpers';
import testAction from '../../helpers/vuex_action_helper';
+import MockAdapter from 'axios-mock-adapter';
describe('Multi-file store actions', () => {
beforeEach(() => {
@@ -485,7 +488,7 @@ describe('Multi-file store actions', () => {
'path',
store.state,
[{ type: types.DELETE_ENTRY, payload: 'path' }],
- [{ type: 'burstUnusedSeal' }],
+ [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }],
done,
);
});
@@ -507,7 +510,7 @@ describe('Multi-file store actions', () => {
payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' },
},
],
- [{ type: 'deleteEntry', payload: 'test' }],
+ [{ type: 'deleteEntry', payload: 'test' }, { type: 'triggerFilesChange' }],
done,
);
});
@@ -555,9 +558,71 @@ describe('Multi-file store actions', () => {
},
},
{ type: 'deleteEntry', payload: 'test' },
+ { type: 'triggerFilesChange' },
],
done,
);
});
});
+
+ describe('getBranchData', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('error', () => {
+ let dispatch;
+ const callParams = [
+ {
+ commit() {},
+ state: store.state,
+ },
+ {
+ projectId: 'abc/def',
+ branchId: 'master-testing',
+ },
+ ];
+
+ beforeEach(() => {
+ dispatch = jasmine.createSpy('dispatchSpy');
+ document.body.innerHTML += '<div class="flash-container"></div>';
+ });
+
+ afterEach(() => {
+ document.querySelector('.flash-container').remove();
+ });
+
+ it('passes the error further unchanged without dispatching any action when response is 404', done => {
+ mock.onGet(/(.*)/).replyOnce(404);
+
+ getBranchData(...callParams)
+ .then(done.fail)
+ .catch(e => {
+ expect(dispatch.calls.count()).toEqual(0);
+ expect(e.response.status).toEqual(404);
+ expect(document.querySelector('.flash-alert')).toBeNull();
+ done();
+ });
+ });
+
+ it('does not pass the error further and flashes an alert if error is not 404', done => {
+ mock.onGet(/(.*)/).replyOnce(418);
+
+ getBranchData(...callParams)
+ .then(done.fail)
+ .catch(e => {
+ expect(dispatch.calls.count()).toEqual(0);
+ expect(e.response).toBeUndefined();
+ expect(document.querySelector('.flash-alert')).not.toBeNull();
+ done();
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/getters_spec.js b/spec/javascripts/ide/stores/getters_spec.js
index 9c135661997..735bbd47f55 100644
--- a/spec/javascripts/ide/stores/getters_spec.js
+++ b/spec/javascripts/ide/stores/getters_spec.js
@@ -180,6 +180,38 @@ describe('IDE store getters', () => {
});
});
+ describe('isOnDefaultBranch', () => {
+ it('returns false when no project exists', () => {
+ const localGetters = {
+ currentProject: undefined,
+ };
+
+ expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy();
+ });
+
+ it("returns true when project's default branch matches current branch", () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ branchName: 'master',
+ };
+
+ expect(getters.isOnDefaultBranch({}, localGetters)).toBeTruthy();
+ });
+
+ it("returns false when project's default branch doesn't match current branch", () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ branchName: 'feature',
+ };
+
+ expect(getters.isOnDefaultBranch({}, localGetters)).toBeFalsy();
+ });
+ });
+
describe('packageJson', () => {
it('returns package.json entry', () => {
localState.entries['package.json'] = { name: 'package.json' };
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index cdeb9b4b896..5f7272311c8 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -1,9 +1,12 @@
-import actions from '~/ide/stores/actions';
+import rootActions from '~/ide/stores/actions';
import store from '~/ide/stores';
import service from '~/ide/services';
import router from '~/ide/ide_router';
import eventHub from '~/ide/eventhub';
import consts from '~/ide/stores/modules/commit/constants';
+import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types';
+import * as actions from '~/ide/stores/modules/commit/actions';
+import testAction from '../../../../helpers/vuex_action_helper';
import { commitActionTypes } from '~/ide/constants';
import { resetStore, file } from 'spec/ide/helpers';
@@ -225,7 +228,7 @@ describe('IDE commit module actions', () => {
let visitUrl;
beforeEach(() => {
- visitUrl = spyOnDependency(actions, 'visitUrl');
+ visitUrl = spyOnDependency(rootActions, 'visitUrl');
document.body.innerHTML += '<div class="flash-container"></div>';
@@ -272,6 +275,7 @@ describe('IDE commit module actions', () => {
short_id: '123',
message: 'test message',
committed_date: 'date',
+ parent_ids: '321',
stats: {
additions: '1',
deletions: '2',
@@ -463,5 +467,213 @@ describe('IDE commit module actions', () => {
.catch(done.fail);
});
});
+
+ describe('first commit of a branch', () => {
+ const COMMIT_RESPONSE = {
+ id: '123456',
+ short_id: '123',
+ message: 'test message',
+ committed_date: 'date',
+ parent_ids: [],
+ stats: {
+ additions: '1',
+ deletions: '2',
+ },
+ };
+
+ it('commits TOGGLE_EMPTY_STATE mutation on empty repo', done => {
+ spyOn(service, 'commit').and.returnValue(
+ Promise.resolve({
+ data: COMMIT_RESPONSE,
+ }),
+ );
+
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/commitChanges')
+ .then(() => {
+ expect(store.commit.calls.allArgs()).toEqual(
+ jasmine.arrayContaining([
+ ['TOGGLE_EMPTY_STATE', jasmine.any(Object), jasmine.any(Object)],
+ ]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not commmit TOGGLE_EMPTY_STATE mutation on existing project', done => {
+ COMMIT_RESPONSE.parent_ids.push('1234');
+ spyOn(service, 'commit').and.returnValue(
+ Promise.resolve({
+ data: COMMIT_RESPONSE,
+ }),
+ );
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/commitChanges')
+ .then(() => {
+ expect(store.commit.calls.allArgs()).not.toEqual(
+ jasmine.arrayContaining([
+ ['TOGGLE_EMPTY_STATE', jasmine.any(Object), jasmine.any(Object)],
+ ]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('toggleShouldCreateMR', () => {
+ it('commits both toggle and interacting with MR checkbox actions', done => {
+ testAction(
+ actions.toggleShouldCreateMR,
+ {},
+ store.state,
+ [
+ { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR },
+ { type: mutationTypes.INTERACT_WITH_NEW_MR },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setShouldCreateMR', () => {
+ beforeEach(() => {
+ store.state.projects = {
+ project: {
+ default_branch: 'master',
+ branches: {
+ master: {
+ name: 'master',
+ },
+ feature: {
+ name: 'feature',
+ },
+ },
+ },
+ };
+
+ store.state.currentProjectId = 'project';
+ });
+
+ it('sets to false when the current branch already has an MR', done => {
+ store.state.commit.currentMergeRequestId = 1;
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.currentMergeRequestId = '1';
+ store.state.currentBranchId = 'feature';
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('changes to false when current branch is the default branch and user has not interacted', done => {
+ store.state.commit.interactedWithNewMR = false;
+ store.state.currentBranchId = 'master';
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('changes to true when "create new branch" is selected and user has not interacted', done => {
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.interactedWithNewMR = false;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, true]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not change anything if user has interacted and comitting to new branch', done => {
+ store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not change anything if user has interacted and comitting to branch without MR', done => {
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.commit.currentMergeRequestId = null;
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('still changes to false if hiding the checkbox', done => {
+ store.state.currentBranchId = 'feature';
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.currentMergeRequestId = '1';
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit.calls.allArgs()[0]).toEqual(
+ jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
+ );
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('does not change to false when on master and user has interacted even if MR exists', done => {
+ store.state.currentBranchId = 'master';
+ store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+ store.state.currentMergeRequestId = '1';
+ store.state.commit.interactedWithNewMR = true;
+ spyOn(store, 'commit').and.callThrough();
+
+ store
+ .dispatch('commit/setShouldCreateMR')
+ .then(() => {
+ expect(store.commit).not.toHaveBeenCalled();
+ done();
+ })
+ .catch(done.fail);
+ });
});
});
diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
index e00fd7199d7..6e71a790deb 100644
--- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
@@ -144,33 +144,4 @@ describe('IDE commit module getters', () => {
});
});
});
-
- describe('shouldDisableNewMrOption', () => {
- it('returns false if commitAction `COMMIT_TO_NEW_BRANCH`', () => {
- state.commitAction = consts.COMMIT_TO_NEW_BRANCH;
- const rootState = {
- currentMergeRequest: { foo: 'bar' },
- };
-
- expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy();
- });
-
- it('returns false if there is no current merge request', () => {
- state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- const rootState = {
- currentMergeRequest: null,
- };
-
- expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy();
- });
-
- it('returns true an MR exists and commit action is `COMMIT_TO_CURRENT_BRANCH`', () => {
- state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- const rootState = {
- currentMergeRequest: { foo: 'bar' },
- };
-
- expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeTruthy();
- });
- });
});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index cef40117304..f28d2c2a882 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -90,9 +90,12 @@ describe('Job App ', () => {
describe('triggered job', () => {
beforeEach(() => {
+ const aYearAgo = new Date();
+ aYearAgo.setFullYear(aYearAgo.getFullYear() - 1);
+
mock
.onGet(props.endpoint)
- .replyOnce(200, Object.assign({}, job, { started: '2017-05-24T10:59:52.000+01:00' }));
+ .replyOnce(200, Object.assign({}, job, { started: aYearAgo.toISOString() }));
vm = mountComponentWithStore(Component, { props, store });
});
diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js
index 26d9effcac5..740bc3d0491 100644
--- a/spec/javascripts/jobs/components/sidebar_spec.js
+++ b/spec/javascripts/jobs/components/sidebar_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import sidebarDetailsBlock from '~/jobs/components/sidebar.vue';
import createStore from '~/jobs/store';
-import job, { stages, jobsInStage } from '../mock_data';
+import job, { jobsInStage } from '../mock_data';
import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { trimText } from '../../helpers/text_helper';
@@ -131,18 +131,8 @@ describe('Sidebar details block', () => {
store.dispatch('receiveJobSuccess', job);
});
- describe('while fetching stages', () => {
- it('it does not render dropdown', () => {
- store.dispatch('requestStages');
- vm = mountComponentWithStore(SidebarComponent, { store });
-
- expect(vm.$el.querySelector('.js-selected-stage')).toBeNull();
- });
- });
-
describe('with stages', () => {
beforeEach(() => {
- store.dispatch('receiveStagesSuccess', stages);
vm = mountComponentWithStore(SidebarComponent, { store });
});
@@ -156,7 +146,6 @@ describe('Sidebar details block', () => {
describe('without jobs for stages', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
- store.dispatch('receiveStagesSuccess', stages);
vm = mountComponentWithStore(SidebarComponent, { store });
});
@@ -168,7 +157,6 @@ describe('Sidebar details block', () => {
describe('with jobs for stages', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
- store.dispatch('receiveStagesSuccess', stages);
store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses);
vm = mountComponentWithStore(SidebarComponent, { store });
});
diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js
index 52bb5161123..e98639bf21e 100644
--- a/spec/javascripts/jobs/components/stages_dropdown_spec.js
+++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js
@@ -9,6 +9,7 @@ describe('Stages Dropdown', () => {
const mockPipelineData = {
id: 28029444,
+ iid: 123,
details: {
status: {
details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
@@ -77,8 +78,8 @@ describe('Stages Dropdown', () => {
expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
});
- it(`renders the pipeline info text like "Pipeline #123 for source_branch"`, () => {
- const expected = `Pipeline #${pipeline.id} for ${pipeline.ref.name}`;
+ it(`renders the pipeline info text like "Pipeline #123 (#12) for source_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for ${pipeline.ref.name}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
expect(actual).toBe(expected);
@@ -100,10 +101,10 @@ describe('Stages Dropdown', () => {
});
});
- it(`renders the pipeline info text like "Pipeline #123 for !456 with source_branch into target_branch"`, () => {
- const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${
- pipeline.merge_request.source_branch
- } into ${pipeline.merge_request.target_branch}`;
+ it(`renders the pipeline info text like "Pipeline #123 (#12) for !456 with source_branch into target_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${
+ pipeline.merge_request.iid
+ } with ${pipeline.merge_request.source_branch} into ${pipeline.merge_request.target_branch}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
expect(actual).toBe(expected);
@@ -143,10 +144,10 @@ describe('Stages Dropdown', () => {
});
});
- it(`renders the pipeline info like "Pipeline #123 for !456 with source_branch"`, () => {
- const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${
- pipeline.merge_request.source_branch
- }`;
+ it(`renders the pipeline info like "Pipeline #123 (#12) for !456 with source_branch"`, () => {
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${
+ pipeline.merge_request.iid
+ } with ${pipeline.merge_request.source_branch}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText);
expect(actual).toBe(expected);
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 1a7f338c5fa..88b0bb206ee 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -3,140 +3,6 @@ import { TEST_HOST } from 'spec/test_constants';
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
-export default {
- id: 4757,
- name: 'test',
- build_path: '/root/ci-mock/-/jobs/4757',
- retry_path: '/root/ci-mock/-/jobs/4757/retry',
- cancel_path: '/root/ci-mock/-/jobs/4757/cancel',
- new_issue_path: '/root/ci-mock/issues/new',
- playable: false,
- created_at: threeWeeksAgo.toISOString(),
- updated_at: threeWeeksAgo.toISOString(),
- finished_at: threeWeeksAgo.toISOString(),
- queued: 9.54,
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`,
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/-/jobs/4757/retry',
- method: 'post',
- },
- },
- coverage: 20,
- erased_at: threeWeeksAgo.toISOString(),
- erased: false,
- duration: 6.785563,
- tags: ['tag'],
- user: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- erase_path: '/root/ci-mock/-/jobs/4757/erase',
- artifacts: [null],
- runner: {
- id: 1,
- description: 'local ci runner',
- edit_path: '/root/ci-mock/runners/1/edit',
- },
- pipeline: {
- id: 140,
- user: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- active: false,
- coverage: null,
- source: 'unknown',
- created_at: '2017-05-24T09:59:58.634Z',
- updated_at: '2017-06-01T17:32:00.062Z',
- path: '/root/ci-mock/pipelines/140',
- flags: {
- latest: true,
- stuck: false,
- yaml_errors: false,
- retryable: false,
- cancelable: false,
- },
- details: {
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/root/ci-mock/pipelines/140',
- favicon:
- '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
- },
- duration: 6,
- finished_at: '2017-06-01T17:32:00.042Z',
- },
- ref: {
- name: 'abc',
- path: '/root/ci-mock/commits/abc',
- tag: false,
- branch: true,
- },
- commit: {
- id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
- short_id: 'c5864777',
- title: 'Add new file',
- created_at: '2017-05-24T10:59:52.000+01:00',
- parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'],
- message: 'Add new file',
- author_name: 'Root',
- author_email: 'admin@example.com',
- authored_date: '2017-05-24T10:59:52.000+01:00',
- committer_name: 'Root',
- committer_email: 'admin@example.com',
- committed_date: '2017-05-24T10:59:52.000+01:00',
- author: {
- name: 'Root',
- username: 'root',
- id: 1,
- state: 'active',
- avatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- web_url: 'http://localhost:3000/root',
- },
- author_gravatar_url:
- 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
- commit_url:
- 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
- commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
- },
- },
- metadata: {
- timeout_human_readable: '1m 40s',
- timeout_source: 'runner',
- },
- merge_request: {
- iid: 2,
- path: '/root/ci-mock/merge_requests/2',
- },
- raw_path: '/root/ci-mock/builds/4757/raw',
- has_trace: true,
-};
-
export const stages = [
{
name: 'build',
@@ -1043,6 +909,168 @@ export const stages = [
},
];
+export default {
+ id: 4757,
+ name: 'test',
+ build_path: '/root/ci-mock/-/jobs/4757',
+ retry_path: '/root/ci-mock/-/jobs/4757/retry',
+ cancel_path: '/root/ci-mock/-/jobs/4757/cancel',
+ new_issue_path: '/root/ci-mock/issues/new',
+ playable: false,
+ created_at: threeWeeksAgo.toISOString(),
+ updated_at: threeWeeksAgo.toISOString(),
+ finished_at: threeWeeksAgo.toISOString(),
+ queued: 9.54,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/-/jobs/4757/retry',
+ method: 'post',
+ },
+ },
+ coverage: 20,
+ erased_at: threeWeeksAgo.toISOString(),
+ erased: false,
+ duration: 6.785563,
+ tags: ['tag'],
+ user: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ erase_path: '/root/ci-mock/-/jobs/4757/erase',
+ artifacts: [null],
+ runner: {
+ id: 1,
+ description: 'local ci runner',
+ edit_path: '/root/ci-mock/runners/1/edit',
+ },
+ pipeline: {
+ id: 140,
+ iid: 13,
+ user: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ active: false,
+ coverage: null,
+ source: 'unknown',
+ created_at: '2017-05-24T09:59:58.634Z',
+ updated_at: '2017-06-01T17:32:00.062Z',
+ path: '/root/ci-mock/pipelines/140',
+ flags: {
+ latest: true,
+ stuck: false,
+ yaml_errors: false,
+ retryable: false,
+ cancelable: false,
+ },
+ details: {
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/ci-mock/pipelines/140',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
+ },
+ duration: 6,
+ finished_at: '2017-06-01T17:32:00.042Z',
+ stages: [
+ {
+ dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=build',
+ name: 'build',
+ path: '/jashkenas/underscore/pipelines/16#build',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ },
+ title: 'build: passed',
+ },
+ {
+ dropdown_path: '/jashkenas/underscore/pipelines/16/stage.json?stage=test',
+ name: 'test',
+ path: '/jashkenas/underscore/pipelines/16#test',
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success-with-warnings',
+ },
+ title: 'test: passed with warnings',
+ },
+ ],
+ },
+ ref: {
+ name: 'abc',
+ path: '/root/ci-mock/commits/abc',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ short_id: 'c5864777',
+ title: 'Add new file',
+ created_at: '2017-05-24T10:59:52.000+01:00',
+ parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'],
+ message: 'Add new file',
+ author_name: 'Root',
+ author_email: 'admin@example.com',
+ authored_date: '2017-05-24T10:59:52.000+01:00',
+ committer_name: 'Root',
+ committer_email: 'admin@example.com',
+ committed_date: '2017-05-24T10:59:52.000+01:00',
+ author: {
+ name: 'Root',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ author_gravatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ commit_url:
+ 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
+ },
+ },
+ metadata: {
+ timeout_human_readable: '1m 40s',
+ timeout_source: 'runner',
+ },
+ merge_request: {
+ iid: 2,
+ path: '/root/ci-mock/merge_requests/2',
+ },
+ raw_path: '/root/ci-mock/builds/4757/raw',
+ has_trace: true,
+};
+
export const jobsInStage = {
name: 'build',
title: 'build: running',
diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js
index 77b44995b12..7b96df85b82 100644
--- a/spec/javascripts/jobs/store/actions_spec.js
+++ b/spec/javascripts/jobs/store/actions_spec.js
@@ -16,10 +16,6 @@ import {
stopPollingTrace,
receiveTraceSuccess,
receiveTraceError,
- requestStages,
- fetchStages,
- receiveStagesSuccess,
- receiveStagesError,
requestJobsForStage,
fetchJobsForStage,
receiveJobsForStageSuccess,
@@ -307,107 +303,6 @@ describe('Job State actions', () => {
});
});
- describe('requestStages', () => {
- it('should commit REQUEST_STAGES mutation ', done => {
- testAction(requestStages, null, mockedState, [{ type: types.REQUEST_STAGES }], [], done);
- });
- });
-
- describe('fetchStages', () => {
- let mock;
-
- beforeEach(() => {
- mockedState.job.pipeline = {
- path: `${TEST_HOST}/endpoint`,
- };
- mockedState.selectedStage = 'deploy';
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('success', () => {
- it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => {
- mock
- .onGet(`${TEST_HOST}/endpoint.json`)
- .replyOnce(200, { details: { stages: [{ name: 'build' }, { name: 'deploy' }] } });
-
- testAction(
- fetchStages,
- null,
- mockedState,
- [],
- [
- {
- type: 'requestStages',
- },
- {
- payload: [{ name: 'build' }, { name: 'deploy' }],
- type: 'receiveStagesSuccess',
- },
- {
- payload: { name: 'deploy' },
- type: 'fetchJobsForStage',
- },
- ],
- done,
- );
- });
- });
-
- describe('error', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
- });
-
- it('dispatches requestStages and receiveStagesError ', done => {
- testAction(
- fetchStages,
- null,
- mockedState,
- [],
- [
- {
- type: 'requestStages',
- },
- {
- type: 'receiveStagesError',
- },
- ],
- done,
- );
- });
- });
- });
-
- describe('receiveStagesSuccess', () => {
- it('should commit RECEIVE_STAGES_SUCCESS mutation ', done => {
- testAction(
- receiveStagesSuccess,
- {},
- mockedState,
- [{ type: types.RECEIVE_STAGES_SUCCESS, payload: {} }],
- [],
- done,
- );
- });
- });
-
- describe('receiveStagesError', () => {
- it('should commit RECEIVE_STAGES_ERROR mutation ', done => {
- testAction(
- receiveStagesError,
- null,
- mockedState,
- [{ type: types.RECEIVE_STAGES_ERROR }],
- [],
- done,
- );
- });
- });
-
describe('requestJobsForStage', () => {
it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => {
testAction(
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 0cd077a6099..296ee85089f 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -852,20 +852,20 @@ describe('common_utils', () => {
describe('roundOffFloat', () => {
it('Rounds off decimal places of a float number with provided precision', () => {
- expect(commonUtils.roundOffFloat(3.141592, 3)).toBe(3.142);
+ expect(commonUtils.roundOffFloat(3.141592, 3)).toBeCloseTo(3.142);
});
it('Rounds off a float number to a whole number when provided precision is zero', () => {
- expect(commonUtils.roundOffFloat(3.141592, 0)).toBe(3);
- expect(commonUtils.roundOffFloat(3.5, 0)).toBe(4);
+ expect(commonUtils.roundOffFloat(3.141592, 0)).toBeCloseTo(3);
+ expect(commonUtils.roundOffFloat(3.5, 0)).toBeCloseTo(4);
});
it('Rounds off float number to nearest 0, 10, 100, 1000 and so on when provided precision is below 0', () => {
- expect(commonUtils.roundOffFloat(34567.14159, -1)).toBe(34570);
- expect(commonUtils.roundOffFloat(34567.14159, -2)).toBe(34600);
- expect(commonUtils.roundOffFloat(34567.14159, -3)).toBe(35000);
- expect(commonUtils.roundOffFloat(34567.14159, -4)).toBe(30000);
- expect(commonUtils.roundOffFloat(34567.14159, -5)).toBe(0);
+ expect(commonUtils.roundOffFloat(34567.14159, -1)).toBeCloseTo(34570);
+ expect(commonUtils.roundOffFloat(34567.14159, -2)).toBeCloseTo(34600);
+ expect(commonUtils.roundOffFloat(34567.14159, -3)).toBeCloseTo(35000);
+ expect(commonUtils.roundOffFloat(34567.14159, -4)).toBeCloseTo(30000);
+ expect(commonUtils.roundOffFloat(34567.14159, -5)).toBeCloseTo(0);
});
});
diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js
index 406527b08a3..7d1921cabcf 100644
--- a/spec/javascripts/matchers.js
+++ b/spec/javascripts/matchers.js
@@ -28,7 +28,7 @@ export default {
reference.getAttribute('xlink:href').endsWith(`#${iconName}`),
);
const result = {
- pass: !!matchingIcon,
+ pass: Boolean(matchingIcon),
};
if (result.pass) {
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index 41a6c04efb9..56609665b88 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -2,7 +2,8 @@ import { shallowMount } from '@vue/test-utils';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue';
-import MonitoringStore from '~/monitoring/stores/monitoring_store';
+import { createStore } from '~/monitoring/stores';
+import * as types from '~/monitoring/stores/mutation_types';
import MonitoringMock, { deploymentData } from '../mock_data';
describe('Area component', () => {
@@ -13,17 +14,18 @@ describe('Area component', () => {
let spriteSpy;
beforeEach(() => {
- const store = new MonitoringStore();
- store.storeMetrics(MonitoringMock.data);
- store.storeDeploymentData(deploymentData);
+ const store = createStore();
- [mockGraphData] = store.groups[0].metrics;
+ store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
+ store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
+
+ [mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
areaChart = shallowMount(Area, {
propsData: {
graphData: mockGraphData,
containerWidth: 0,
- deploymentData: store.deploymentData,
+ deploymentData: store.state.monitoringDashboard.deploymentData,
},
slots: {
default: mockWidgets,
diff --git a/spec/javascripts/monitoring/charts/single_stat_spec.js b/spec/javascripts/monitoring/charts/single_stat_spec.js
new file mode 100644
index 00000000000..12b73002f97
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/single_stat_spec.js
@@ -0,0 +1,28 @@
+import { shallowMount } from '@vue/test-utils';
+import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
+
+describe('Single Stat Chart component', () => {
+ let singleStatChart;
+
+ beforeEach(() => {
+ singleStatChart = shallowMount(SingleStatChart, {
+ propsData: {
+ title: 'Time to render',
+ value: 1,
+ unit: 'sec',
+ },
+ });
+ });
+
+ afterEach(() => {
+ singleStatChart.destroy();
+ });
+
+ describe('computed', () => {
+ describe('valueWithUnit', () => {
+ it('should interpolate the value and unit props', () => {
+ expect(singleStatChart.vm.valueWithUnit).toBe('1sec');
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index e9bd6050d68..1a371c3adaf 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -2,8 +2,15 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
+import * as types from '~/monitoring/stores/mutation_types';
+import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
-import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data';
+import {
+ metricsGroupsAPIResponse,
+ mockApiEndpoint,
+ environmentData,
+ singleGroupResponse,
+} from './mock_data';
const propsData = {
hasMetrics: false,
@@ -30,6 +37,8 @@ export default propsData;
describe('Dashboard', () => {
let DashboardComponent;
let mock;
+ let store;
+ let component;
beforeEach(() => {
setFixtures(`
@@ -45,23 +54,26 @@ describe('Dashboard', () => {
},
};
+ store = createStore();
mock = new MockAdapter(axios);
DashboardComponent = Vue.extend(Dashboard);
});
afterEach(() => {
+ component.$destroy();
mock.restore();
});
describe('no metrics are available yet', () => {
it('shows a getting started empty state when no metrics are present', () => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, showTimeWindowDropdown: false },
+ propsData: { ...propsData },
+ store,
});
expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
- expect(component.state).toEqual('gettingStarted');
+ expect(component.emptyState).toEqual('gettingStarted');
});
});
@@ -71,26 +83,27 @@ describe('Dashboard', () => {
});
it('shows up a loading state', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false },
+ propsData: { ...propsData, hasMetrics: true },
+ store,
});
Vue.nextTick(() => {
- expect(component.state).toEqual('loading');
+ expect(component.emptyState).toEqual('loading');
done();
});
});
it('hides the legend when showLegend is false', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showLegend: false,
- showTimeWindowDropdown: false,
},
+ store,
});
setTimeout(() => {
@@ -102,14 +115,14 @@ describe('Dashboard', () => {
});
it('hides the group panels when showPanels is false', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
+ store,
});
setTimeout(() => {
@@ -121,85 +134,106 @@ describe('Dashboard', () => {
});
it('renders the environments dropdown with a number of environments', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
+ store,
});
- component.store.storeEnvironmentsData(environmentData);
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
setTimeout(() => {
const dropdownMenuEnvironments = component.$el.querySelectorAll(
'.js-environments-dropdown .dropdown-item',
);
- expect(dropdownMenuEnvironments.length).toEqual(component.store.environmentsData.length);
+ expect(dropdownMenuEnvironments.length).toEqual(component.environments.length);
done();
});
});
it('hides the environments dropdown list when there is no environments', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
+ store,
});
- component.store.storeEnvironmentsData([]);
+ component.$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, []);
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
- setTimeout(() => {
- const dropdownMenuEnvironments = component.$el.querySelectorAll(
- '.js-environments-dropdown .dropdown-item',
- );
+ Vue.nextTick()
+ .then(() => {
+ const dropdownMenuEnvironments = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item',
+ );
- expect(dropdownMenuEnvironments.length).toEqual(0);
- done();
- });
+ expect(dropdownMenuEnvironments.length).toEqual(0);
+ done();
+ })
+ .catch(done.fail);
});
it('renders the environments dropdown with a single active element', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
+ store,
});
- component.store.storeEnvironmentsData(environmentData);
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
- setTimeout(() => {
- const dropdownItems = component.$el.querySelectorAll(
- '.js-environments-dropdown .dropdown-item[active="true"]',
- );
+ Vue.nextTick()
+ .then(() => {
+ const dropdownItems = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item[active="true"]',
+ );
- expect(dropdownItems.length).toEqual(1);
- expect(dropdownItems[0].textContent.trim()).toEqual(component.currentEnvironmentName);
- done();
- });
+ expect(dropdownItems.length).toEqual(1);
+ done();
+ })
+ .catch(done.fail);
});
it('hides the dropdown', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
environmentsEndpoint: '',
- showTimeWindowDropdown: false,
},
+ store,
});
Vue.nextTick(() => {
@@ -210,57 +244,73 @@ describe('Dashboard', () => {
});
});
- it('does not show the time window dropdown when the feature flag is not set', done => {
- const component = new DashboardComponent({
+ it('renders the time window dropdown with a set of options', done => {
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
+ store,
});
+ const numberOfTimeWindows = Object.keys(timeWindows).length;
setTimeout(() => {
const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
+ const timeWindowDropdownEls = component.$el.querySelectorAll(
+ '.js-time-window-dropdown .dropdown-item',
+ );
- expect(timeWindowDropdown).toBeNull();
+ expect(timeWindowDropdown).not.toBeNull();
+ expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
done();
});
});
- it('renders the time window dropdown with a set of options', done => {
- const component = new DashboardComponent({
+ it('fetches the metrics data with proper time window', done => {
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: true,
},
+ store,
});
- const numberOfTimeWindows = Object.keys(timeWindows).length;
- setTimeout(() => {
- const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
- const timeWindowDropdownEls = component.$el.querySelectorAll(
- '.js-time-window-dropdown .dropdown-item',
- );
+ spyOn(component.$store, 'dispatch').and.stub();
+ const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff');
- expect(timeWindowDropdown).not.toBeNull();
- expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
+ component.$store.commit(
+ `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`,
+ '/environments',
+ );
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
- done();
- });
+ component.$mount();
+
+ Vue.nextTick()
+ .then(() => {
+ expect(component.$store.dispatch).toHaveBeenCalled();
+ expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow);
+
+ done();
+ })
+ .catch(done.fail);
});
it('shows a specific time window selected from the url params', done => {
spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']);
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true },
+ propsData: { ...propsData, hasMetrics: true },
+ store,
});
setTimeout(() => {
@@ -278,9 +328,10 @@ describe('Dashboard', () => {
'<script>alert("XSS")</script>',
]);
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: true },
+ propsData: { ...propsData, hasMetrics: true },
+ store,
});
Vue.nextTick(() => {
@@ -302,14 +353,14 @@ describe('Dashboard', () => {
});
it('sets elWidth to page width when the sidebar is resized', done => {
- const component = new DashboardComponent({
+ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
},
+ store,
});
expect(component.elWidth).toEqual(0);
@@ -331,16 +382,10 @@ describe('Dashboard', () => {
});
describe('external dashboard link', () => {
- let component;
-
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
});
- afterEach(() => {
- component.$destroy();
- });
-
describe('with feature flag enabled', () => {
beforeEach(() => {
component = new DashboardComponent({
@@ -349,9 +394,9 @@ describe('Dashboard', () => {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
- externalDashboardPath: '/mockPath',
+ externalDashboardUrl: '/mockUrl',
},
+ store,
});
});
@@ -374,9 +419,9 @@ describe('Dashboard', () => {
...propsData,
hasMetrics: true,
showPanels: false,
- showTimeWindowDropdown: false,
- externalDashboardPath: '',
+ externalDashboardUrl: '',
},
+ store,
});
});
diff --git a/spec/javascripts/monitoring/helpers.js b/spec/javascripts/monitoring/helpers.js
new file mode 100644
index 00000000000..672e3b948c4
--- /dev/null
+++ b/spec/javascripts/monitoring/helpers.js
@@ -0,0 +1,8 @@
+// eslint-disable-next-line import/prefer-default-export
+export const resetStore = store => {
+ store.replaceState({
+ showEmptyState: true,
+ emptyState: 'loading',
+ groups: [],
+ });
+};
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 6d4ef960c1a..d9d8cb66749 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -685,6 +685,47 @@ export const metricsGroupsAPIResponse = {
last_update: '2017-05-25T13:18:34.949Z',
};
+export const singleGroupResponse = [
+ {
+ group: 'System metrics (Kubernetes)',
+ priority: 5,
+ metrics: [
+ {
+ title: 'Memory Usage (Total)',
+ weight: 0,
+ y_label: 'Total Memory Used',
+ queries: [
+ {
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^production-(.*)",namespace="autodevops-deploy-33"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'GB',
+ label: 'Total',
+ result: [
+ {
+ metric: {},
+ values: [
+ [1558453960.079, '0.0357666015625'],
+ [1558454020.079, '0.035675048828125'],
+ [1558454080.079, '0.035152435302734375'],
+ [1558454140.079, '0.035221099853515625'],
+ [1558454200.079, '0.0352325439453125'],
+ [1558454260.079, '0.03479766845703125'],
+ [1558454320.079, '0.034793853759765625'],
+ [1558454380.079, '0.034931182861328125'],
+ [1558454440.079, '0.034816741943359375'],
+ [1558454500.079, '0.034816741943359375'],
+ [1558454560.079, '0.034816741943359375'],
+ ],
+ },
+ ],
+ },
+ ],
+ id: 15,
+ },
+ ],
+ },
+];
+
export default metricsGroupsAPIResponse;
export const deploymentData = [
@@ -738,5836 +779,6 @@ export const statePaths = {
documentationPath: '/help/administration/monitoring/prometheus/index.md',
};
-export const singleRowMetricsMultipleSeries = [
- {
- title: 'Multiple Time Series',
- weight: 1,
- y_label: 'Request Rates',
- queries: [
- {
- query_range:
- 'sum(rate(nginx_responses_total{environment="production"}[2m])) by (status_code)',
- label: 'Requests',
- unit: 'Req/sec',
- result: [
- {
- metric: {
- status_code: '1xx',
- },
- values: [
- {
- time: '2017-08-27T11:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T11:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T12:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T13:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T14:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T15:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T16:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T17:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:01:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:02:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:03:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:04:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:05:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:06:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:07:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:08:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:09:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:10:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:11:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:12:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:13:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:14:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:15:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:16:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:17:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:18:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:19:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:20:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:21:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:22:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:23:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:24:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:25:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:26:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:27:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:28:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:29:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:30:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:31:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:32:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:33:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:34:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:35:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:36:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:37:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:38:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:39:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:40:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:41:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:42:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:43:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:44:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:45:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:46:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:47:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:48:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:49:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:50:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:51:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:52:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:53:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:54:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:55:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:56:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:57:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:58:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T18:59:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T19:00:51.462Z',
- value: '0',
- },
- {
- time: '2017-08-27T19:01:51.462Z',
- value: '0',
- },
- ],
- },
- {
- metric: {
- status_code: '2xx',
- },
- values: [
- {
- time: '2017-08-27T11:01:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:02:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T11:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:04:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:05:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:07:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:08:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:09:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:14:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:16:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:18:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:19:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:20:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:21:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:23:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:24:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:25:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:26:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:27:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:29:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:31:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:32:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:33:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:34:51.462Z',
- value: '1.333320635041571',
- },
- {
- time: '2017-08-27T11:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:36:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:37:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:38:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:39:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:40:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:41:51.462Z',
- value: '1.3333587306424883',
- },
- {
- time: '2017-08-27T11:42:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:43:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:44:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:45:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:46:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:47:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:48:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:49:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T11:50:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:51:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:53:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:55:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:57:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T11:58:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T11:59:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:00:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:01:51.462Z',
- value: '1.3333460318669703',
- },
- {
- time: '2017-08-27T12:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:04:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:05:51.462Z',
- value: '1.31427319739812',
- },
- {
- time: '2017-08-27T12:06:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:07:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:08:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:09:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:10:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:13:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:16:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:18:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:19:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:20:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:21:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:23:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:24:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:25:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:26:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:27:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:29:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:31:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:32:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:33:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:34:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:36:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:37:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:38:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:39:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:41:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:42:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:43:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:44:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:45:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:46:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:47:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:48:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:49:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:50:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:51:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:53:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T12:55:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:56:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:57:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T12:58:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T12:59:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:00:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:01:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T13:02:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:03:51.462Z',
- value: '1.2952627669098458',
- },
- {
- time: '2017-08-27T13:04:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:05:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:06:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:07:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:08:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:09:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:14:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:15:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T13:16:51.462Z',
- value: '1.3333587306424883',
- },
- {
- time: '2017-08-27T13:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:18:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:19:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:20:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:21:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:22:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:23:51.462Z',
- value: '1.276190476190476',
- },
- {
- time: '2017-08-27T13:24:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T13:25:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:26:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:27:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:29:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:31:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:32:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:33:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:34:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:35:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:36:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:37:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:38:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:39:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:40:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:41:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:42:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:43:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:44:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:45:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:46:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T13:47:51.462Z',
- value: '1.276190476190476',
- },
- {
- time: '2017-08-27T13:48:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:49:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T13:50:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:51:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:52:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:53:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:54:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:55:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:56:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T13:57:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T13:58:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T13:59:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T14:00:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:01:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:04:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:05:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:07:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:08:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:09:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:16:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:17:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:18:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:19:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:20:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:21:51.462Z',
- value: '1.3333079369916765',
- },
- {
- time: '2017-08-27T14:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:23:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:24:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:25:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:26:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:27:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:29:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:30:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:31:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:32:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:33:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T14:34:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:36:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:37:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:38:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:39:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:40:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:41:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:42:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:43:51.462Z',
- value: '1.276190476190476',
- },
- {
- time: '2017-08-27T14:44:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T14:45:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:46:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:47:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:48:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:49:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:50:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:51:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:53:51.462Z',
- value: '1.333320635041571',
- },
- {
- time: '2017-08-27T14:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:55:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T14:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:57:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T14:58:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T14:59:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:00:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:01:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:04:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T15:05:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:07:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:08:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:09:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:10:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:11:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:12:51.462Z',
- value: '1.31427319739812',
- },
- {
- time: '2017-08-27T15:13:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:16:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:18:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:19:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:20:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:21:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:22:51.462Z',
- value: '1.3333460318669703',
- },
- {
- time: '2017-08-27T15:23:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:24:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:25:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:26:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:27:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:28:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:29:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:31:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:32:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:33:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:34:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:35:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:36:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:37:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:38:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:39:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:41:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:42:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:43:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:44:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:45:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:46:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:47:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:48:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:49:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T15:50:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:51:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:53:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:55:51.462Z',
- value: '1.3333587306424883',
- },
- {
- time: '2017-08-27T15:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T15:57:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:58:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T15:59:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:00:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:01:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:03:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:04:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:05:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:07:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:08:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:09:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:12:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:15:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:16:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:17:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:18:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:19:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:20:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:21:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:23:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:24:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T16:25:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:26:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:27:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:28:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:29:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:30:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:31:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:32:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:33:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:34:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:36:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:37:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:38:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:39:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:41:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:42:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:43:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:44:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:45:51.462Z',
- value: '1.3142982314117277',
- },
- {
- time: '2017-08-27T16:46:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:47:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:48:51.462Z',
- value: '1.333320635041571',
- },
- {
- time: '2017-08-27T16:49:51.462Z',
- value: '1.31427319739812',
- },
- {
- time: '2017-08-27T16:50:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:51:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:53:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:55:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T16:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:57:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T16:58:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T16:59:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:00:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:01:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:02:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:03:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:04:51.462Z',
- value: '1.2952504309564854',
- },
- {
- time: '2017-08-27T17:05:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:06:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:07:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:08:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:09:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:12:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:16:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:18:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:19:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:20:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:21:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:22:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:23:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:24:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:25:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:26:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:27:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:28:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:29:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T17:30:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:31:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:32:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:33:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:34:51.462Z',
- value: '1.295225759754669',
- },
- {
- time: '2017-08-27T17:35:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:36:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:37:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:38:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:39:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:41:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:42:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:43:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:44:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:45:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:46:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:47:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:48:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:49:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:50:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:51:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:52:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:53:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:54:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:55:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T17:56:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:57:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T17:58:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T17:59:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:00:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:01:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:02:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:03:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:04:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:05:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:06:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:07:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:08:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:09:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:10:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:11:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:12:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:13:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:14:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:15:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:16:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:17:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:18:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:19:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:20:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:21:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:22:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:23:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:24:51.462Z',
- value: '1.2571428571428571',
- },
- {
- time: '2017-08-27T18:25:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:26:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:27:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:28:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:29:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:30:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:31:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:32:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:33:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:34:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:35:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:36:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:37:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:38:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:39:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:40:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:41:51.462Z',
- value: '1.580952380952381',
- },
- {
- time: '2017-08-27T18:42:51.462Z',
- value: '1.7333333333333334',
- },
- {
- time: '2017-08-27T18:43:51.462Z',
- value: '2.057142857142857',
- },
- {
- time: '2017-08-27T18:44:51.462Z',
- value: '2.1904761904761902',
- },
- {
- time: '2017-08-27T18:45:51.462Z',
- value: '1.8285714285714287',
- },
- {
- time: '2017-08-27T18:46:51.462Z',
- value: '2.1142857142857143',
- },
- {
- time: '2017-08-27T18:47:51.462Z',
- value: '1.619047619047619',
- },
- {
- time: '2017-08-27T18:48:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:49:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:50:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:51:51.462Z',
- value: '1.2952504309564854',
- },
- {
- time: '2017-08-27T18:52:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:53:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:54:51.462Z',
- value: '1.3333333333333333',
- },
- {
- time: '2017-08-27T18:55:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:56:51.462Z',
- value: '1.314285714285714',
- },
- {
- time: '2017-08-27T18:57:51.462Z',
- value: '1.295238095238095',
- },
- {
- time: '2017-08-27T18:58:51.462Z',
- value: '1.7142857142857142',
- },
- {
- time: '2017-08-27T18:59:51.462Z',
- value: '1.7333333333333334',
- },
- {
- time: '2017-08-27T19:00:51.462Z',
- value: '1.3904761904761904',
- },
- {
- time: '2017-08-27T19:01:51.462Z',
- value: '1.5047619047619047',
- },
- ],
- },
- ],
- when: [
- {
- value: 'hundred(s)',
- color: 'green',
- },
- ],
- },
- ],
- },
- {
- title: 'Throughput',
- weight: 1,
- y_label: 'Requests / Sec',
- queries: [
- {
- query_range:
- "sum(rate(nginx_requests_total{server_zone!='*', server_zone!='_', container_name!='POD',environment='production'}[2m]))",
- label: 'Total',
- unit: 'req / sec',
- result: [
- {
- metric: {},
- values: [
- {
- time: '2017-08-27T11:01:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:02:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T11:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:04:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:05:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:07:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:08:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:09:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:14:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:16:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:18:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:19:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:20:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:21:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:23:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:24:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:25:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:26:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:27:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:29:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:31:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:32:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:33:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:34:51.462Z',
- value: '0.4952333787297264',
- },
- {
- time: '2017-08-27T11:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:36:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:37:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:38:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:39:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:40:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:41:51.462Z',
- value: '0.49524752852435283',
- },
- {
- time: '2017-08-27T11:42:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:43:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:44:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:45:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:46:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:47:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:48:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:49:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T11:50:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:51:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:53:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:55:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:57:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T11:58:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T11:59:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:00:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:01:51.462Z',
- value: '0.49524281183630325',
- },
- {
- time: '2017-08-27T12:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:04:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:05:51.462Z',
- value: '0.4857096599080009',
- },
- {
- time: '2017-08-27T12:06:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:07:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:08:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:09:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:10:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:13:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:16:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:18:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:19:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:20:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:21:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:23:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:24:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:25:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:26:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:27:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:29:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:31:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:32:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:33:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:34:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:36:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:37:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:38:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:39:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:41:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:42:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:43:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:44:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:45:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:46:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:47:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:48:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:49:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:50:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:51:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:53:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T12:55:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:56:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:57:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T12:58:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T12:59:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:00:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:01:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T13:02:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:03:51.462Z',
- value: '0.4761995466580315',
- },
- {
- time: '2017-08-27T13:04:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:05:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:06:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:07:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:08:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:09:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:14:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:15:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T13:16:51.462Z',
- value: '0.49524752852435283',
- },
- {
- time: '2017-08-27T13:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:18:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:19:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:20:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:21:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:22:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:23:51.462Z',
- value: '0.4666666666666667',
- },
- {
- time: '2017-08-27T13:24:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T13:25:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:26:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:27:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:29:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:31:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:32:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:33:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:34:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:35:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:36:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:37:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:38:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:39:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:40:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:41:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:42:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:43:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:44:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:45:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:46:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T13:47:51.462Z',
- value: '0.4666666666666667',
- },
- {
- time: '2017-08-27T13:48:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:49:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T13:50:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:51:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:52:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:53:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:54:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:55:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:56:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T13:57:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T13:58:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T13:59:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T14:00:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:01:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:04:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:05:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:07:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:08:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:09:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:16:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:17:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:18:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:19:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:20:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:21:51.462Z',
- value: '0.4952286623111941',
- },
- {
- time: '2017-08-27T14:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:23:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:24:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:25:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:26:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:27:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:29:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:30:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:31:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:32:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:33:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T14:34:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:36:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:37:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:38:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:39:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:40:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:41:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:42:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:43:51.462Z',
- value: '0.4666666666666667',
- },
- {
- time: '2017-08-27T14:44:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T14:45:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:46:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:47:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:48:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:49:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:50:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:51:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:53:51.462Z',
- value: '0.4952333787297264',
- },
- {
- time: '2017-08-27T14:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:55:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T14:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:57:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T14:58:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T14:59:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:00:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:01:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:04:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T15:05:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:07:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:08:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:09:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:10:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:11:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:12:51.462Z',
- value: '0.4857096599080009',
- },
- {
- time: '2017-08-27T15:13:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:16:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:18:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:19:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:20:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:21:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:22:51.462Z',
- value: '0.49524281183630325',
- },
- {
- time: '2017-08-27T15:23:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:24:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:25:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:26:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:27:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:28:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:29:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:31:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:32:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:33:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:34:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:35:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:36:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:37:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:38:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:39:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:41:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:42:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:43:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:44:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:45:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:46:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:47:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:48:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:49:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T15:50:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:51:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:53:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:55:51.462Z',
- value: '0.49524752852435283',
- },
- {
- time: '2017-08-27T15:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T15:57:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:58:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T15:59:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:00:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:01:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:03:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:04:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:05:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:07:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:08:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:09:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:12:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:15:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:16:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:17:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:18:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:19:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:20:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:21:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:23:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:24:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T16:25:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:26:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:27:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:28:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:29:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:30:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:31:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:32:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:33:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:34:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:36:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:37:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:38:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:39:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:41:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:42:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:43:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:44:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:45:51.462Z',
- value: '0.485718911608682',
- },
- {
- time: '2017-08-27T16:46:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:47:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:48:51.462Z',
- value: '0.4952333787297264',
- },
- {
- time: '2017-08-27T16:49:51.462Z',
- value: '0.4857096599080009',
- },
- {
- time: '2017-08-27T16:50:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:51:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:53:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:55:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T16:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:57:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T16:58:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T16:59:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:00:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:01:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:02:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:03:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:04:51.462Z',
- value: '0.47619501138106085',
- },
- {
- time: '2017-08-27T17:05:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:06:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:07:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:08:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:09:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:12:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:16:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:18:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:19:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:20:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:21:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:22:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:23:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:24:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:25:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:26:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:27:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:28:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:29:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T17:30:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:31:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:32:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:33:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:34:51.462Z',
- value: '0.4761859410862754',
- },
- {
- time: '2017-08-27T17:35:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:36:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:37:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:38:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:39:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:41:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:42:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:43:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:44:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:45:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:46:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:47:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:48:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:49:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:50:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:51:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:52:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:53:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:54:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:55:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T17:56:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:57:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T17:58:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T17:59:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:00:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:01:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:02:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:03:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:04:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:05:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:06:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:07:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:08:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:09:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:10:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:11:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:12:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:13:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:14:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:15:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:16:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:17:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:18:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:19:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:20:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:21:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:22:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:23:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:24:51.462Z',
- value: '0.45714285714285713',
- },
- {
- time: '2017-08-27T18:25:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:26:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:27:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:28:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:29:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:30:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:31:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:32:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:33:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:34:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:35:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:36:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:37:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:38:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:39:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:40:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:41:51.462Z',
- value: '0.6190476190476191',
- },
- {
- time: '2017-08-27T18:42:51.462Z',
- value: '0.6952380952380952',
- },
- {
- time: '2017-08-27T18:43:51.462Z',
- value: '0.857142857142857',
- },
- {
- time: '2017-08-27T18:44:51.462Z',
- value: '0.9238095238095239',
- },
- {
- time: '2017-08-27T18:45:51.462Z',
- value: '0.7428571428571429',
- },
- {
- time: '2017-08-27T18:46:51.462Z',
- value: '0.8857142857142857',
- },
- {
- time: '2017-08-27T18:47:51.462Z',
- value: '0.638095238095238',
- },
- {
- time: '2017-08-27T18:48:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:49:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:50:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:51:51.462Z',
- value: '0.47619501138106085',
- },
- {
- time: '2017-08-27T18:52:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:53:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:54:51.462Z',
- value: '0.4952380952380952',
- },
- {
- time: '2017-08-27T18:55:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:56:51.462Z',
- value: '0.4857142857142857',
- },
- {
- time: '2017-08-27T18:57:51.462Z',
- value: '0.47619047619047616',
- },
- {
- time: '2017-08-27T18:58:51.462Z',
- value: '0.6857142857142856',
- },
- {
- time: '2017-08-27T18:59:51.462Z',
- value: '0.6952380952380952',
- },
- {
- time: '2017-08-27T19:00:51.462Z',
- value: '0.5238095238095237',
- },
- {
- time: '2017-08-27T19:01:51.462Z',
- value: '0.5904761904761905',
- },
- ],
- },
- ],
- },
- ],
- },
-];
-
export const queryWithoutData = {
title: 'HTTP Error rate',
weight: 10,
diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js
deleted file mode 100644
index 5bf6937c92e..00000000000
--- a/spec/javascripts/monitoring/monitoring_store_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import MonitoringStore from '~/monitoring/stores/monitoring_store';
-import MonitoringMock, { deploymentData, environmentData } from './mock_data';
-
-describe('MonitoringStore', () => {
- const store = new MonitoringStore();
- store.storeMetrics(MonitoringMock.data);
-
- it('contains two groups that contains, one of which has two queries sorted by priority', () => {
- expect(store.groups).toBeDefined();
- expect(store.groups.length).toEqual(2);
- expect(store.groups[0].metrics.length).toEqual(2);
- });
-
- it('gets the metrics count for every group', () => {
- expect(store.getMetricsCount()).toEqual(3);
- });
-
- it('contains deployment data', () => {
- store.storeDeploymentData(deploymentData);
-
- expect(store.deploymentData).toBeDefined();
- expect(store.deploymentData.length).toEqual(3);
- expect(typeof store.deploymentData[0]).toEqual('object');
- });
-
- it('only stores environment data that contains deployments', () => {
- store.storeEnvironmentsData(environmentData);
-
- expect(store.environmentsData.length).toEqual(2);
- });
-
- it('removes the data if all the values from a query are not defined', () => {
- expect(store.groups[1].metrics[0].queries[0].result.length).toEqual(0);
- });
-
- it('assigns queries a metric id', () => {
- expect(store.groups[1].metrics[0].queries[0].metricId).toEqual('100');
- });
-
- it('assigns metric id of null if metric has no id', () => {
- const noId = MonitoringMock.data.map(group => ({
- ...group,
- ...{
- metrics: group.metrics.map(metric => {
- const { id, ...metricWithoutId } = metric;
-
- return metricWithoutId;
- }),
- },
- }));
- store.storeMetrics(noId);
-
- store.groups.forEach(group => {
- group.metrics.forEach(metric => {
- expect(metric.queries.every(query => query.metricId === null)).toBe(true);
- });
- });
- });
-});
diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js
new file mode 100644
index 00000000000..a848cd24fe3
--- /dev/null
+++ b/spec/javascripts/monitoring/store/actions_spec.js
@@ -0,0 +1,158 @@
+import axios from '~/lib/utils/axios_utils';
+import MockAdapter from 'axios-mock-adapter';
+import store from '~/monitoring/stores';
+import * as types from '~/monitoring/stores/mutation_types';
+import {
+ fetchDeploymentsData,
+ fetchEnvironmentsData,
+ requestMetricsData,
+ setEndpoints,
+ setGettingStartedEmptyState,
+} from '~/monitoring/stores/actions';
+import storeState from '~/monitoring/stores/state';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { resetStore } from '../helpers';
+import { deploymentData, environmentData } from '../mock_data';
+
+describe('Monitoring store actions', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ resetStore(store);
+ mock.restore();
+ });
+
+ describe('requestMetricsData', () => {
+ it('sets emptyState to loading', () => {
+ const commit = jasmine.createSpy();
+ const { state } = store;
+
+ requestMetricsData({ state, commit });
+
+ expect(commit).toHaveBeenCalledWith(types.REQUEST_METRICS_DATA);
+ });
+ });
+
+ describe('fetchDeploymentsData', () => {
+ it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => {
+ const dispatch = jasmine.createSpy();
+ const { state } = store;
+ state.deploymentEndpoint = '/success';
+
+ mock.onGet(state.deploymentEndpoint).reply(200, {
+ deployments: deploymentData,
+ });
+
+ fetchDeploymentsData({ state, dispatch })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataSuccess', deploymentData);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('commits RECEIVE_DEPLOYMENTS_DATA_FAILURE on error', done => {
+ const dispatch = jasmine.createSpy();
+ const { state } = store;
+ state.deploymentEndpoint = '/error';
+
+ mock.onGet(state.deploymentEndpoint).reply(500);
+
+ fetchDeploymentsData({ state, dispatch })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveDeploymentsDataFailure');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('fetchEnvironmentsData', () => {
+ it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on error', done => {
+ const dispatch = jasmine.createSpy();
+ const { state } = store;
+ state.environmentsEndpoint = '/success';
+
+ mock.onGet(state.environmentsEndpoint).reply(200, {
+ environments: environmentData,
+ });
+
+ fetchEnvironmentsData({ state, dispatch })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataSuccess', environmentData);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('commits RECEIVE_ENVIRONMENTS_DATA_FAILURE on error', done => {
+ const dispatch = jasmine.createSpy();
+ const { state } = store;
+ state.environmentsEndpoint = '/error';
+
+ mock.onGet(state.environmentsEndpoint).reply(500);
+
+ fetchEnvironmentsData({ state, dispatch })
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataFailure');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('Set endpoints', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = storeState();
+ });
+
+ it('should commit SET_ENDPOINTS mutation', done => {
+ testAction(
+ setEndpoints,
+ {
+ metricsEndpoint: 'additional_metrics.json',
+ deploymentsEndpoint: 'deployments.json',
+ environmentsEndpoint: 'deployments.json',
+ },
+ mockedState,
+ [
+ {
+ type: types.SET_ENDPOINTS,
+ payload: {
+ metricsEndpoint: 'additional_metrics.json',
+ deploymentsEndpoint: 'deployments.json',
+ environmentsEndpoint: 'deployments.json',
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('Set empty states', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = storeState();
+ });
+
+ it('should commit SET_METRICS_ENDPOINT mutation', done => {
+ testAction(
+ setGettingStartedEmptyState,
+ null,
+ mockedState,
+ [{ type: types.SET_GETTING_STARTED_EMPTY_STATE }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js
new file mode 100644
index 00000000000..882ee1dec14
--- /dev/null
+++ b/spec/javascripts/monitoring/store/mutations_spec.js
@@ -0,0 +1,92 @@
+import mutations from '~/monitoring/stores/mutations';
+import * as types from '~/monitoring/stores/mutation_types';
+import state from '~/monitoring/stores/state';
+import { metricsGroupsAPIResponse, deploymentData } from '../mock_data';
+
+describe('Monitoring mutations', () => {
+ let stateCopy;
+
+ beforeEach(() => {
+ stateCopy = state();
+ });
+
+ describe(types.RECEIVE_METRICS_DATA_SUCCESS, () => {
+ beforeEach(() => {
+ stateCopy.groups = [];
+ const groups = metricsGroupsAPIResponse.data;
+
+ mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, groups);
+ });
+
+ it('normalizes values', () => {
+ const expectedTimestamp = '2017-05-25T08:22:34.925Z';
+ const expectedValue = 0.0010794445585559514;
+ const [timestamp, value] = stateCopy.groups[0].metrics[0].queries[0].result[0].values[0];
+
+ expect(timestamp).toEqual(expectedTimestamp);
+ expect(value).toEqual(expectedValue);
+ });
+
+ it('contains two groups that contains, one of which has two queries sorted by priority', () => {
+ expect(stateCopy.groups).toBeDefined();
+ expect(stateCopy.groups.length).toEqual(2);
+ expect(stateCopy.groups[0].metrics.length).toEqual(2);
+ });
+
+ it('assigns queries a metric id', () => {
+ expect(stateCopy.groups[1].metrics[0].queries[0].metricId).toEqual('100');
+ });
+
+ it('removes the data if all the values from a query are not defined', () => {
+ expect(stateCopy.groups[1].metrics[0].queries[0].result.length).toEqual(0);
+ });
+
+ it('assigns metric id of null if metric has no id', () => {
+ stateCopy.groups = [];
+ const groups = metricsGroupsAPIResponse.data;
+ const noId = groups.map(group => ({
+ ...group,
+ ...{
+ metrics: group.metrics.map(metric => {
+ const { id, ...metricWithoutId } = metric;
+
+ return metricWithoutId;
+ }),
+ },
+ }));
+
+ mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, noId);
+
+ stateCopy.groups.forEach(group => {
+ group.metrics.forEach(metric => {
+ expect(metric.queries.every(query => query.metricId === null)).toBe(true);
+ });
+ });
+ });
+ });
+
+ describe(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, () => {
+ it('stores the deployment data', () => {
+ stateCopy.deploymentData = [];
+ mutations[types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](stateCopy, deploymentData);
+
+ expect(stateCopy.deploymentData).toBeDefined();
+ expect(stateCopy.deploymentData.length).toEqual(3);
+ expect(typeof stateCopy.deploymentData[0]).toEqual('object');
+ });
+ });
+
+ describe('SET_ENDPOINTS', () => {
+ it('should set all the endpoints', () => {
+ mutations[types.SET_ENDPOINTS](stateCopy, {
+ metricsEndpoint: 'additional_metrics.json',
+ environmentsEndpoint: 'environments.json',
+ deploymentsEndpoint: 'deployments.json',
+ });
+
+ expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
+ expect(stateCopy.environmentsEndpoint).toEqual('environments.json');
+ expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index 0cfcc994234..1f2c07385a7 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -58,6 +58,7 @@ describe('noteActions', () => {
it('should render emoji link', () => {
expect(wrapper.find('.js-add-award').exists()).toBe(true);
+ expect(wrapper.find('.js-add-award').attributes('data-position')).toBe('right');
});
describe('actions dropdown', () => {
@@ -65,7 +66,7 @@ describe('noteActions', () => {
expect(wrapper.find('.js-note-edit').exists()).toBe(true);
});
- it('should be possible to report abuse to GitLab', () => {
+ it('should be possible to report abuse to admin', () => {
expect(wrapper.find(`a[href="${props.reportAbusePath}"]`).exists()).toBe(true);
});
diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js
index 03ead6cd8ba..8eef9166b8d 100644
--- a/spec/javascripts/pipelines/mock_data.js
+++ b/spec/javascripts/pipelines/mock_data.js
@@ -1,5 +1,6 @@
export const pipelineWithStages = {
id: 20333396,
+ iid: 304399,
user: {
id: 128633,
name: 'Rémy Coutable',
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index aa196af2f33..88c0137dc58 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -13,6 +13,7 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {},
},
@@ -28,6 +29,7 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {},
},
@@ -47,6 +49,7 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {
latest: true,
@@ -78,6 +81,7 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {
latest: true,
@@ -100,6 +104,7 @@ describe('Pipeline Url Component', () => {
propsData: {
pipeline: {
id: 1,
+ iid: 1,
path: 'foo',
flags: {
failure_reason: true,
diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js
index b61e0ac872f..106a3ba94e4 100644
--- a/spec/javascripts/projects/project_new_spec.js
+++ b/spec/javascripts/projects/project_new_spec.js
@@ -10,7 +10,17 @@ describe('New Project', () => {
setFixtures(`
<div class='toggle-import-form'>
<div class='import-url-data'>
- <input id="project_import_url" />
+ <div class="form-group">
+ <input id="project_import_url" />
+ </div>
+ <div id="import-url-auth-method">
+ <div class="form-group">
+ <input id="project-import-url-user" />
+ </div>
+ <div class="form-group">
+ <input id="project_import_url_password" />
+ </div>
+ </div>
<input id="project_name" />
<input id="project_path" />
</div>
@@ -119,7 +129,7 @@ describe('New Project', () => {
});
it('changes project path for HTTPS URL in $projectImportUrl', () => {
- $projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git');
+ $projectImportUrl.val('https://gitlab.company.com/group/project.git');
projectNew.deriveProjectPathFromUrl($projectImportUrl);
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 87ef0885d8c..8c80a425581 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -111,7 +111,7 @@ let longRunningTestTimeoutHandle;
beforeEach(done => {
longRunningTestTimeoutHandle = setTimeout(() => {
done.fail('Test is running too long!');
- }, 2000);
+ }, 4000);
done();
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 75017d20473..a2308b0dfdb 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -103,7 +103,7 @@ describe('MRWidgetPipeline', () => {
it('should render pipeline ID', () => {
expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
+ `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`,
);
});
@@ -150,7 +150,7 @@ describe('MRWidgetPipeline', () => {
it('should render pipeline ID', () => {
expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual(
- `#${mockData.pipeline.id}`,
+ `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`,
);
});
@@ -222,9 +222,9 @@ describe('MRWidgetPipeline', () => {
sourceBranchLink: mockCopy.source_branch_link,
});
- const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${
- pipeline.commit.short_id
- } on ${mockCopy.source_branch_link}`;
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${
+ pipeline.details.status.label
+ } for ${pipeline.commit.short_id} on ${mockCopy.source_branch_link}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
@@ -247,11 +247,11 @@ describe('MRWidgetPipeline', () => {
sourceBranchLink: mockCopy.source_branch_link,
});
- const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${
- pipeline.commit.short_id
- } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch} into ${
- pipeline.merge_request.target_branch
- }`;
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${
+ pipeline.details.status.label
+ } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${
+ pipeline.merge_request.source_branch
+ } into ${pipeline.merge_request.target_branch}`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
@@ -274,9 +274,11 @@ describe('MRWidgetPipeline', () => {
sourceBranchLink: mockCopy.source_branch_link,
});
- const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${
- pipeline.commit.short_id
- } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch}`;
+ const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${
+ pipeline.details.status.label
+ } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${
+ pipeline.merge_request.source_branch
+ }`;
const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText);
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
index b9718a78fa4..8e0415b813b 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js
@@ -21,7 +21,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
canCancelAutomaticMerge: true,
mergeUserId: 1,
currentUserId: 1,
- setToMWPSBy: {},
+ setToAutoMergeBy: {},
sha,
targetBranchPath,
targetBranch,
@@ -106,7 +106,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(vm.service.merge).toHaveBeenCalledWith({
sha,
- merge_when_pipeline_succeeds: true,
+ auto_merge_strategy: 'merge_when_pipeline_succeeds',
should_remove_source_branch: true,
});
done();
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 368c997d318..3ae773b6ccb 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -80,7 +80,7 @@ describe('ReadyToMerge', () => {
it('should have default data', () => {
expect(vm.mergeWhenBuildSucceeds).toBeFalsy();
expect(vm.useCommitMessageWithDescription).toBeFalsy();
- expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy();
+ expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.showCommitMessageEditor).toBeFalsy();
expect(vm.isMakingRequest).toBeFalsy();
expect(vm.isMergingImmediately).toBeFalsy();
@@ -91,17 +91,17 @@ describe('ReadyToMerge', () => {
});
describe('computed', () => {
- describe('shouldShowMergeWhenPipelineSucceedsText', () => {
+ describe('shouldShowAutoMergeText', () => {
it('should return true with active pipeline', () => {
vm.mr.isPipelineActive = true;
- expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeTruthy();
+ expect(vm.shouldShowAutoMergeText).toBeTruthy();
});
it('should return false with inactive pipeline', () => {
vm.mr.isPipelineActive = false;
- expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeFalsy();
+ expect(vm.shouldShowAutoMergeText).toBeFalsy();
});
});
@@ -325,16 +325,20 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(true);
setTimeout(() => {
- expect(vm.setToMergeWhenPipelineSucceeds).toBeTruthy();
+ expect(vm.autoMergeStrategy).toBe('merge_when_pipeline_succeeds');
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
const params = vm.service.merge.calls.argsFor(0)[0];
- expect(params.sha).toEqual(vm.mr.sha);
- expect(params.commit_message).toEqual(vm.mr.commitMessage);
- expect(params.should_remove_source_branch).toBeFalsy();
- expect(params.merge_when_pipeline_succeeds).toBeTruthy();
+ expect(params).toEqual(
+ jasmine.objectContaining({
+ sha: vm.mr.sha,
+ commit_message: vm.mr.commitMessage,
+ should_remove_source_branch: false,
+ auto_merge_strategy: 'merge_when_pipeline_succeeds',
+ }),
+ );
done();
}, 333);
});
@@ -345,7 +349,7 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(false, true);
setTimeout(() => {
- expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy();
+ expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
@@ -363,7 +367,7 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick();
setTimeout(() => {
- expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy();
+ expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy();
expect(vm.initiateMergePolling).toHaveBeenCalled();
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index bec16b0aab0..edbd0d54151 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -62,6 +62,7 @@ export default {
"Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
pipeline: {
id: 172,
+ iid: 32,
user: {
name: 'Administrator',
username: 'root',
@@ -241,6 +242,8 @@ export default {
export const mockStore = {
pipeline: {
id: 0,
+ iid: 0,
+ path: '/root/acets-app/pipelines/0',
details: {
status: {
details_path: '/root/review-app-tester/pipelines/66',
@@ -258,6 +261,8 @@ export const mockStore = {
},
mergePipeline: {
id: 1,
+ iid: 1,
+ path: '/root/acets-app/pipelines/0',
details: {
status: {
details_path: '/root/review-app-tester/pipelines/66',
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index a0628fdcebe..918717c4547 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -21,7 +21,6 @@ describe('mrWidgetOptions', () => {
const COLLABORATION_MESSAGE = 'Allows commits from members who can merge to the target branch';
beforeEach(() => {
- gon.features = { approvalRules: false };
// Prevent component mounting
delete mrWidgetOptions.el;
@@ -32,7 +31,6 @@ describe('mrWidgetOptions', () => {
});
afterEach(() => {
- gon.features = null;
vm.$destroy();
});
@@ -600,6 +598,7 @@ describe('mrWidgetOptions', () => {
];
const deploymentMockData = {
id: 15,
+ iid: 7,
name: 'review/diplo',
url: '/root/acets-review-apps/environments/15',
stop_url: '/root/acets-review-apps/environments/15/stop',
@@ -646,6 +645,7 @@ describe('mrWidgetOptions', () => {
vm.mr.state = 'merged';
vm.mr.mergePipeline = {
id: 127,
+ iid: 35,
user: {
id: 1,
name: 'Administrator',
diff --git a/spec/javascripts/vue_shared/components/pagination/graphql_pagination_spec.js b/spec/javascripts/vue_shared/components/pagination/graphql_pagination_spec.js
new file mode 100644
index 00000000000..7445da6cdee
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/pagination/graphql_pagination_spec.js
@@ -0,0 +1,70 @@
+import { shallowMount } from '@vue/test-utils';
+import GraphqlPagination from '~/vue_shared/components/pagination/graphql_pagination.vue';
+
+describe('Graphql Pagination component', () => {
+ let wrapper;
+ function factory({ hasNextPage = true, hasPreviousPage = true }) {
+ wrapper = shallowMount(GraphqlPagination, {
+ propsData: {
+ hasNextPage,
+ hasPreviousPage,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('without previous page', () => {
+ beforeEach(() => {
+ factory({ hasPreviousPage: false });
+ });
+
+ it('renders disabled previous button', () => {
+ expect(wrapper.find('.js-prev-btn').attributes().disabled).toEqual('true');
+ });
+ });
+
+ describe('with previous page', () => {
+ beforeEach(() => {
+ factory({ hasPreviousPage: true });
+ });
+
+ it('renders enabled previous button', () => {
+ expect(wrapper.find('.js-prev-btn').attributes().disabled).toEqual(undefined);
+ });
+
+ it('emits previousClicked on click', () => {
+ wrapper.find('.js-prev-btn').vm.$emit('click');
+
+ expect(wrapper.emitted().previousClicked.length).toBe(1);
+ });
+ });
+
+ describe('without next page', () => {
+ beforeEach(() => {
+ factory({ hasNextPage: false });
+ });
+
+ it('renders disabled next button', () => {
+ expect(wrapper.find('.js-next-btn').attributes().disabled).toEqual('true');
+ });
+ });
+
+ describe('with next page', () => {
+ beforeEach(() => {
+ factory({ hasNextPage: true });
+ });
+
+ it('renders enabled next button', () => {
+ expect(wrapper.find('.js-next-btn').attributes().disabled).toEqual(undefined);
+ });
+
+ it('emits nextClicked on click', () => {
+ wrapper.find('.js-next-btn').vm.$emit('click');
+
+ expect(wrapper.emitted().nextClicked.length).toBe(1);
+ });
+ });
+});
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index 6e215ea1561..c788da55cd2 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -2,8 +2,12 @@ require 'spec_helper'
describe API::Helpers::Pagination do
let(:resource) { Project.all }
- let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:8080/api/v4/projects" }
- let(:canonical_api_projects_url) { "#{Gitlab.config.gitlab.url}/api/v4/projects" }
+ let(:custom_port) { 8080 }
+ let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" }
+
+ before do
+ stub_config_setting(port: custom_port)
+ end
subject do
Class.new.include(described_class).new
@@ -48,7 +52,7 @@ describe API::Helpers::Pagination do
it 'adds appropriate headers' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}")
+ expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
@@ -71,7 +75,7 @@ describe API::Helpers::Pagination do
it 'adds appropriate headers' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}")
+ expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
@@ -171,7 +175,7 @@ describe API::Helpers::Pagination do
it 'returns the right link to the next page' do
expect_header('X-Per-Page', '2')
- expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}")
+ expect_header('X-Next-Page', "#{incoming_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}")
expect_header('Link', anything) do |_key, val|
expect(val).to include('rel="next"')
end
@@ -224,9 +228,9 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
expect(val).not_to include('rel="prev"')
end
@@ -290,8 +294,8 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next"))
expect(val).not_to include('rel="last"')
expect(val).not_to include('rel="prev"')
end
@@ -318,9 +322,9 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '1')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev"))
expect(val).not_to include('rel="next"')
end
@@ -367,8 +371,8 @@ describe API::Helpers::Pagination do
expect_header('X-Prev-Page', '')
expect_header('Link', anything) do |_key, val|
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
- expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first"))
+ expect(val).to include(%Q(<#{incoming_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last"))
expect(val).not_to include('rel="prev"')
expect(val).not_to include('rel="next"')
expect(val).not_to include('page=0')
diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb
index 1f53657c59c..316dbf052c3 100644
--- a/spec/lib/banzai/commit_renderer_spec.rb
+++ b/spec/lib/banzai/commit_renderer_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Banzai::CommitRenderer do
- describe '.render' do
+ describe '.render', :clean_gitlab_redis_cache do
it 'renders a commit description and title' do
user = build(:user)
project = create(:project, :repository)
@@ -13,7 +13,7 @@ describe Banzai::CommitRenderer do
described_class::ATTRIBUTES.each do |attr|
expect_any_instance_of(Banzai::ObjectRenderer).to receive(:render).with([project.commit], attr).once.and_call_original
- expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr, {})
+ expect(Banzai::Renderer).to receive(:cacheless_render_field).with(project.commit, attr, { skip_project_check: false }).and_call_original
end
described_class.render([project.commit], project, user)
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index 43222ddb5e2..7c94cf37e32 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -155,6 +155,13 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do
it_behaves_like "external issue tracker"
end
+
+ context "with a lowercase prefix" do
+ let(:issue) { ExternalIssue.new("gl-030", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
end
context "jira project" do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 4c94e4fdae0..f0a5dc8d0d7 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -295,6 +295,25 @@ describe Banzai::Filter::MilestoneReferenceFilter do
end
end
+ shared_examples 'references with HTML entities' do
+ before do
+ milestone.update!(title: '&lt;html&gt;')
+ end
+
+ it 'links to a valid reference' do
+ doc = reference_filter('See %"&lt;html&gt;"')
+
+ expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone)
+ expect(doc.text).to eq 'See %<html>'
+ end
+
+ it 'ignores invalid milestone names and escapes entities' do
+ act = %(Milestone %"&lt;non valid&gt;")
+
+ expect(reference_filter(act).to_html).to eq act
+ end
+ end
+
shared_context 'project milestones' do
let(:reference) { milestone.to_reference(format: :iid) }
@@ -307,6 +326,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
it_behaves_like 'cross-project / cross-namespace complete reference'
it_behaves_like 'cross-project / same-namespace complete reference'
it_behaves_like 'cross project shorthand reference'
+ it_behaves_like 'references with HTML entities'
end
shared_context 'group milestones' do
@@ -317,6 +337,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
it_behaves_like 'String-based single-word references'
it_behaves_like 'String-based multi-word references in quotes'
it_behaves_like 'referencing a milestone in a link href'
+ it_behaves_like 'references with HTML entities'
it 'does not support references by IID' do
doc = reference_filter("See #{Milestone.reference_prefix}#{milestone.iid}")
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index b9059b85fdc..cce1cd0b284 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -70,5 +70,47 @@ describe Banzai::Filter::WikiLinkFilter do
expect(filtered_link.attribute('href').value).to eq(invalid_link)
end
end
+
+ context "when the slug is deemed unsafe or invalid" do
+ let(:link) { "alert(1);" }
+
+ invalid_slugs = [
+ "javascript:",
+ "JaVaScRiPt:",
+ "\u0001java\u0003script:",
+ "javascript :",
+ "javascript: ",
+ "javascript : ",
+ ":javascript:",
+ "javascript&#58;",
+ "javascript&#0058;",
+ "javascript&#x3A;",
+ "javascript&#x003A;",
+ "java\0script:",
+ " &#14; javascript:"
+ ]
+
+ invalid_slugs.each do |slug|
+ context "with the slug #{slug}" do
+ it "doesn't rewrite a (.) relative link" do
+ filtered_link = filter(
+ "<a href='.#{link}'>Link</a>",
+ project_wiki: wiki,
+ page_slug: slug).children[0]
+
+ expect(filtered_link.attribute('href').value).not_to include(slug)
+ end
+
+ it "doesn't rewrite a (..) relative link" do
+ filtered_link = filter(
+ "<a href='..#{link}'>Link</a>",
+ project_wiki: wiki,
+ page_slug: slug).children[0]
+
+ expect(filtered_link.attribute('href').value).not_to include(slug)
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 3b52f6666d0..7b855251a74 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -11,7 +11,7 @@ describe Banzai::ObjectRenderer do
)
end
- let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) }
+ let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) }
describe '#render' do
context 'with cache' do
@@ -60,24 +60,38 @@ describe Banzai::ObjectRenderer do
end
context 'without cache' do
- let(:commit) { project.commit }
+ let(:cacheless_class) do
+ Class.new do
+ attr_accessor :title, :redacted_title_html, :project
+
+ def banzai_render_context(field)
+ { project: project, pipeline: :single_line }
+ end
+ end
+ end
+ let(:cacheless_thing) do
+ cacheless_class.new.tap do |thing|
+ thing.title = "Merge branch 'branch-merged' into 'master'"
+ thing.project = project
+ end
+ end
it 'renders and redacts an Array of objects' do
- renderer.render([commit], :title)
+ renderer.render([cacheless_thing], :title)
- expect(commit.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
+ expect(cacheless_thing.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
end
it 'calls Banzai::Redactor to perform redaction' do
expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
- renderer.render([commit], :title)
+ renderer.render([cacheless_thing], :title)
end
it 'retrieves field content using Banzai::Renderer.cacheless_render_field' do
- expect(Banzai::Renderer).to receive(:cacheless_render_field).with(commit, :title, {}).and_call_original
+ expect(Banzai::Renderer).to receive(:cacheless_render_field).with(cacheless_thing, :title, {}).and_call_original
- renderer.render([commit], :title)
+ renderer.render([cacheless_thing], :title)
end
end
end
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 91b0499375d..7119c826bca 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -117,4 +117,27 @@ describe Banzai::Pipeline::GfmPipeline do
expect(output).not_to include("javascript")
end
end
+
+ describe 'emoji in references' do
+ set(:project) { create(:project, :public) }
+ let(:emoji) { '💯' }
+
+ it 'renders a label reference with emoji inside' do
+ create(:label, project: project, name: emoji)
+
+ output = described_class.to_html("#{Label.reference_prefix}\"#{emoji}\"", project: project)
+
+ expect(output).to include(emoji)
+ expect(output).to include(Gitlab::Routing.url_helpers.project_issues_path(project, label_name: emoji))
+ end
+
+ it 'renders a milestone reference with emoji inside' do
+ milestone = create(:milestone, project: project, title: emoji)
+
+ output = described_class.to_html("#{Milestone.reference_prefix}\"#{emoji}\"", project: project)
+
+ expect(output).to include(emoji)
+ expect(output).to include(Gitlab::Routing.url_helpers.milestone_path(milestone))
+ end
+ end
end
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb
index aaeec953e4b..718649e0e10 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/redactor_spec.rb
@@ -13,10 +13,10 @@ describe Banzai::Redactor do
it 'redacts an array of documents' do
doc1 = Nokogiri::HTML
- .fragment('<a class="gfm" data-reference-type="issue">foo</a>')
+ .fragment('<a class="gfm" href="https://www.gitlab.com" data-reference-type="issue">foo</a>')
doc2 = Nokogiri::HTML
- .fragment('<a class="gfm" data-reference-type="issue">bar</a>')
+ .fragment('<a class="gfm" href="https://www.gitlab.com" data-reference-type="issue">bar</a>')
redacted_data = redactor.redact([doc1, doc2])
@@ -27,7 +27,7 @@ describe Banzai::Redactor do
end
it 'replaces redacted reference with inner HTML' do
- doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue'>foo</a>")
+ doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue'>foo</a>")
redactor.redact([doc])
expect(doc.to_html).to eq('foo')
end
@@ -35,20 +35,24 @@ describe Banzai::Redactor do
context 'when data-original attribute provided' do
let(:original_content) { '<code>foo</code>' }
it 'replaces redacted reference with original content' do
- doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
+ doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
redactor.redact([doc])
expect(doc.to_html).to eq(original_content)
end
- end
-
- it 'returns <a> tag with original href if it is originally a link reference' do
- href = 'http://localhost:3000'
- doc = Nokogiri::HTML
- .fragment("<a class='gfm' data-reference-type='issue' data-original=#{href} data-link-reference='true'>#{href}</a>")
- redactor.redact([doc])
+ it 'does not replace redacted reference with original content if href is given' do
+ html = "<a href='https://www.gitlab.com' data-link-reference='true' class='gfm' data-reference-type='issue' data-reference-type='issue' data-original='Marge'>Marge</a>"
+ doc = Nokogiri::HTML.fragment(html)
+ redactor.redact([doc])
+ expect(doc.to_html).to eq('<a href="https://www.gitlab.com">Marge</a>')
+ end
- expect(doc.to_html).to eq('<a href="http://localhost:3000">http://localhost:3000</a>')
+ it 'uses the original content as the link content if given' do
+ html = "<a href='https://www.gitlab.com' data-link-reference='true' class='gfm' data-reference-type='issue' data-reference-type='issue' data-original='Homer'>Marge</a>"
+ doc = Nokogiri::HTML.fragment(html)
+ redactor.redact([doc])
+ expect(doc.to_html).to eq('<a href="https://www.gitlab.com">Homer</a>')
+ end
end
end
@@ -61,7 +65,7 @@ describe Banzai::Redactor do
end
it 'redacts an issue attached' do
- doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-issue='#{issue.id}'>foo</a>")
+ doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-issue='#{issue.id}'>foo</a>")
redactor.redact([doc])
@@ -69,7 +73,7 @@ describe Banzai::Redactor do
end
it 'redacts an external issue' do
- doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-external-issue='#{issue.id}' data-project='#{project.id}'>foo</a>")
+ doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-external-issue='#{issue.id}' data-project='#{project.id}'>foo</a>")
redactor.redact([doc])
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index 650cecfc778..aa828e2f0e9 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -11,16 +11,24 @@ describe Banzai::Renderer do
object
end
+ def fake_cacheless_object
+ object = double('cacheless object')
+
+ allow(object).to receive(:respond_to?).with(:cached_markdown_fields).and_return(false)
+
+ object
+ end
+
describe '#render_field' do
let(:renderer) { described_class }
context 'without cache' do
- let(:commit) { create(:project, :repository).commit }
+ let(:commit) { fake_cacheless_object }
it 'returns cacheless render field' do
- expect(renderer).to receive(:cacheless_render_field).with(commit, :title, {})
+ expect(renderer).to receive(:cacheless_render_field).with(commit, :field, {})
- renderer.render_field(commit, :title)
+ renderer.render_field(commit, :field)
end
end
diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
index 27281333348..0a5b99d27e7 100644
--- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
+++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
@@ -3,6 +3,12 @@ require 'spec_helper'
# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do
describe '#perform' do
+ before do
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ end
+
context 'when diff files can be deleted' do
let(:merge_request) { create(:merge_request, :merged) }
let!(:merge_request_diff) do
diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
index 3e009fed0f1..c6bc3db88a3 100644
--- a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
@@ -9,6 +9,9 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration
before do
# This migration was created before we introduced metadata configs
stub_feature_flags(ci_build_metadata_config: false)
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
end
let!(:internal_pipeline) { create(:ci_pipeline, source: :web) }
diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
new file mode 100644
index 00000000000..740781f1aa5
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::BackgroundMigration::ResetMergeStatus, :migration, schema: 20190528180441 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+
+ def create_merge_request(id, extra_params = {})
+ params = {
+ id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}"
+ }.merge(extra_params)
+
+ merge_requests.create!(params)
+ end
+
+ it 'correctly updates opened mergeable MRs to unchecked' do
+ create_merge_request(1, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(2, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(3, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(4, state: 'merged', merge_status: 'can_be_merged')
+ create_merge_request(5, state: 'opened', merge_status: 'cannot_be_merged')
+
+ subject.perform(1, 5)
+
+ expected_rows = [
+ { id: 1, state: 'opened', merge_status: 'unchecked' },
+ { id: 2, state: 'opened', merge_status: 'unchecked' },
+ { id: 3, state: 'opened', merge_status: 'unchecked' },
+ { id: 4, state: 'merged', merge_status: 'can_be_merged' },
+ { id: 5, state: 'opened', merge_status: 'cannot_be_merged' }
+ ]
+
+ rows = merge_requests.order(:id).map do |row|
+ row.attributes.slice('id', 'state', 'merge_status').symbolize_keys
+ end
+
+ expect(rows).to eq(expected_rows)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
new file mode 100644
index 00000000000..d494ce68c5b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb')
+
+describe ScheduleCalculateWikiSizes, :migration, :sidekiq do
+ let(:migration_class) { Gitlab::BackgroundMigration::CalculateWikiSizes }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:project_statistics) { table(:project_statistics) }
+
+ context 'when missing wiki sizes exist' do
+ before do
+ namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration')
+ projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1)
+ projects.create!(id: 2, name: 'wiki-project-2', path: 'wiki-project-2', namespace_id: 1)
+ projects.create!(id: 3, name: 'wiki-project-3', path: 'wiki-project-3', namespace_id: 1)
+ project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000)
+ project_statistics.create!(id: 2, project_id: 2, namespace_id: 1, wiki_size: nil)
+ project_statistics.create!(id: 3, project_id: 3, namespace_id: 1, wiki_size: nil)
+ end
+
+ it 'schedules a background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(migration_name).to be_scheduled_delayed_migration(5.minutes, 2, 3)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 1
+ end
+ end
+ end
+
+ it 'calculates missing wiki sizes' do
+ expect(project_statistics.find_by(id: 2).wiki_size).to be_nil
+ expect(project_statistics.find_by(id: 3).wiki_size).to be_nil
+
+ migrate!
+
+ expect(project_statistics.find_by(id: 2).wiki_size).not_to be_nil
+ expect(project_statistics.find_by(id: 3).wiki_size).not_to be_nil
+ end
+ end
+
+ context 'when missing wiki sizes do not exist' do
+ before do
+ namespaces.create!(id: 1, name: 'wiki-migration', path: 'wiki-migration')
+ projects.create!(id: 1, name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: 1)
+ project_statistics.create!(id: 1, project_id: 1, namespace_id: 1, wiki_size: 1000)
+ end
+
+ it 'does not schedule a background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index a02c00e3340..2e90f6c7f71 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -5,6 +5,7 @@ describe Gitlab::BitbucketImport::Importer do
before do
stub_omniauth_provider('bitbucket')
+ stub_feature_flags(stricter_mr_branch_name: false)
end
let(:statuses) do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 0560eb42e4d..e0552ae8c57 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -94,7 +94,7 @@ describe Gitlab::Ci::Config::Entry::Job do
it 'returns error about wrong value type' do
expect(entry).not_to be_valid
- expect(entry.errors).to include "job extends should be a string"
+ expect(entry.errors).to include "job extends should be an array of strings or a string"
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index d5bd139b5f1..d31866a1987 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -112,6 +112,16 @@ describe Gitlab::Ci::Config::Entry::Service do
it 'is valid' do
expect(entry).to be_valid
end
+
+ context 'when unknown port keys detected' do
+ let(:ports) { [{ number: 80, invalid_key: 'foo' }] }
+
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ expect(entry.errors.first)
+ .to match /port config contains unknown keys: invalid_key/
+ end
+ end
end
describe '#ports' do
diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
index 0a148375d11..d63612053b6 100644
--- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
@@ -44,12 +44,12 @@ describe Gitlab::Ci::Config::Extendable::Entry do
end
end
- describe '#extends_key' do
+ describe '#extends_keys' do
context 'when entry is extensible' do
it 'returns symbolized extends key value' do
entry = described_class.new(:test, test: { extends: 'something' })
- expect(entry.extends_key).to eq :something
+ expect(entry.extends_keys).to eq [:something]
end
end
@@ -57,7 +57,7 @@ describe Gitlab::Ci::Config::Extendable::Entry do
it 'returns nil' do
entry = described_class.new(:test, test: 'something')
- expect(entry.extends_key).to be_nil
+ expect(entry.extends_keys).to be_nil
end
end
end
@@ -76,7 +76,7 @@ describe Gitlab::Ci::Config::Extendable::Entry do
end
end
- describe '#base_hash!' do
+ describe '#base_hashes!' do
subject { described_class.new(:test, hash) }
context 'when base hash is not extensible' do
@@ -87,8 +87,8 @@ describe Gitlab::Ci::Config::Extendable::Entry do
}
end
- it 'returns unchanged base hash' do
- expect(subject.base_hash!).to eq(script: 'rspec')
+ it 'returns unchanged base hashes' do
+ expect(subject.base_hashes!).to eq([{ script: 'rspec' }])
end
end
@@ -101,12 +101,12 @@ describe Gitlab::Ci::Config::Extendable::Entry do
}
end
- it 'extends the base hash first' do
- expect(subject.base_hash!).to eq(extends: 'first', script: 'rspec')
+ it 'extends the base hashes first' do
+ expect(subject.base_hashes!).to eq([{ extends: 'first', script: 'rspec' }])
end
it 'mutates original context' do
- subject.base_hash!
+ subject.base_hashes!
expect(hash.fetch(:second)).to eq(extends: 'first', script: 'rspec')
end
@@ -171,6 +171,34 @@ describe Gitlab::Ci::Config::Extendable::Entry do
end
end
+ context 'when extending multiple hashes correctly' do
+ let(:hash) do
+ {
+ first: { script: 'my value', image: 'ubuntu' },
+ second: { image: 'alpine' },
+ test: { extends: %w(first second) }
+ }
+ end
+
+ let(:result) do
+ {
+ first: { script: 'my value', image: 'ubuntu' },
+ second: { image: 'alpine' },
+ test: { extends: %w(first second), script: 'my value', image: 'alpine' }
+ }
+ end
+
+ it 'returns extended part of the hash' do
+ expect(subject.extend!).to eq result[:test]
+ end
+
+ it 'mutates original context' do
+ subject.extend!
+
+ expect(hash).to eq result
+ end
+ end
+
context 'when hash is not extensible' do
let(:hash) do
{
diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index d8a61618e77..46d68097fff 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Remote do
+ include StubRequests
+
let(:context) { described_class::Context.new(nil, '12345', nil, Set.new) }
let(:params) { { remote: location } }
let(:remote_file) { described_class.new(params, context) }
@@ -46,7 +48,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
describe "#valid?" do
context 'when is a valid remote url' do
before do
- WebMock.stub_request(:get, location).to_return(body: remote_file_content)
+ stub_full_request(location).to_return(body: remote_file_content)
end
it 'returns true' do
@@ -92,7 +94,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
describe "#content" do
context 'with a valid remote file' do
before do
- WebMock.stub_request(:get, location).to_return(body: remote_file_content)
+ stub_full_request(location).to_return(body: remote_file_content)
end
it 'returns the content of the file' do
@@ -114,7 +116,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
let(:location) { 'https://asdasdasdaj48ggerexample.com' }
before do
- WebMock.stub_request(:get, location).to_raise(SocketError.new('Some HTTP error'))
+ stub_full_request(location).to_raise(SocketError.new('Some HTTP error'))
end
it 'is nil' do
@@ -144,7 +146,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when timeout error has been raised' do
before do
- WebMock.stub_request(:get, location).to_timeout
+ stub_full_request(location).to_timeout
end
it 'returns error message about a timeout' do
@@ -154,7 +156,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when HTTP error has been raised' do
before do
- WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error)
+ stub_full_request(location).to_raise(Gitlab::HTTP::Error)
end
it 'returns error message about a HTTP error' do
@@ -164,7 +166,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when response has 404 status' do
before do
- WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404)
+ stub_full_request(location).to_return(body: remote_file_content, status: 404)
end
it 'returns error message about a timeout' do
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 136974569de..e068b786b02 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Mapper do
+ include StubRequests
+
set(:project) { create(:project, :repository) }
set(:user) { create(:user) }
@@ -18,7 +20,7 @@ describe Gitlab::Ci::Config::External::Mapper do
end
before do
- WebMock.stub_request(:get, remote_url).to_return(body: file_content)
+ stub_full_request(remote_url).to_return(body: file_content)
end
describe '#process' do
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index 0f58a4f1d44..856187371e1 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Processor do
+ include StubRequests
+
set(:project) { create(:project, :repository) }
set(:another_project) { create(:project, :repository) }
set(:user) { create(:user) }
@@ -42,7 +44,7 @@ describe Gitlab::Ci::Config::External::Processor do
let(:values) { { include: remote_file, image: 'ruby:2.2' } }
before do
- WebMock.stub_request(:get, remote_file).to_raise(SocketError.new('Some HTTP error'))
+ stub_full_request(remote_file).and_raise(SocketError.new('Some HTTP error'))
end
it 'raises an error' do
@@ -75,7 +77,7 @@ describe Gitlab::Ci::Config::External::Processor do
end
before do
- WebMock.stub_request(:get, remote_file).to_return(body: external_file_content)
+ stub_full_request(remote_file).to_return(body: external_file_content)
end
it 'appends the file to the values' do
@@ -145,7 +147,7 @@ describe Gitlab::Ci::Config::External::Processor do
allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
.to receive(:fetch_local_content).and_return(local_file_content)
- WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
+ stub_full_request(remote_file).to_return(body: remote_file_content)
end
it 'appends the files to the values' do
@@ -191,7 +193,8 @@ describe Gitlab::Ci::Config::External::Processor do
end
it 'takes precedence' do
- WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
+ stub_full_request(remote_file).to_return(body: remote_file_content)
+
expect(processor.perform[:image]).to eq('ruby:2.2')
end
end
@@ -231,7 +234,8 @@ describe Gitlab::Ci::Config::External::Processor do
HEREDOC
end
- WebMock.stub_request(:get, 'http://my.domain.com/config.yml').to_return(body: 'remote_build: { script: echo Hello World }')
+ stub_full_request('http://my.domain.com/config.yml')
+ .to_return(body: 'remote_build: { script: echo Hello World }')
end
context 'when project is public' do
@@ -273,8 +277,10 @@ describe Gitlab::Ci::Config::External::Processor do
context 'when config includes an external configuration file via SSL web request' do
before do
- stub_request(:get, 'https://sha256.badssl.com/fake.yml').to_return(body: 'image: ruby:2.6', status: 200)
- stub_request(:get, 'https://self-signed.badssl.com/fake.yml')
+ stub_full_request('https://sha256.badssl.com/fake.yml', ip_address: '8.8.8.8')
+ .to_return(body: 'image: ruby:2.6', status: 200)
+
+ stub_full_request('https://self-signed.badssl.com/fake.yml', ip_address: '8.8.8.9')
.to_raise(OpenSSL::SSL::SSLError.new('SSL_connect returned=1 errno=0 state=error: certificate verify failed (self signed certificate)'))
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index fd2a29e4ddb..7f336ee853e 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::Ci::Config do
+ include StubRequests
+
set(:user) { create(:user) }
let(:config) do
@@ -190,7 +192,6 @@ describe Gitlab::Ci::Config do
let(:remote_file_content) do
<<~HEREDOC
variables:
- AUTO_DEVOPS_DOMAIN: domain.example.com
POSTGRES_USER: user
POSTGRES_PASSWORD: testing-password
POSTGRES_ENABLED: "true"
@@ -217,8 +218,7 @@ describe Gitlab::Ci::Config do
end
before do
- WebMock.stub_request(:get, remote_location)
- .to_return(body: remote_file_content)
+ stub_full_request(remote_location).to_return(body: remote_file_content)
allow(project.repository)
.to receive(:blob_data_at).and_return(local_file_content)
@@ -232,7 +232,6 @@ describe Gitlab::Ci::Config do
"bundle install --jobs $(nproc) \"${FLAGS[@]}\""
]
variables = {
- AUTO_DEVOPS_DOMAIN: "domain.example.com",
POSTGRES_USER: "user",
POSTGRES_PASSWORD: "testing-password",
POSTGRES_ENABLED: "true",
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
new file mode 100644
index 00000000000..006ce4d8078
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
@@ -0,0 +1,77 @@
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do
+ let(:left) { double('left', evaluate: nil) }
+ let(:right) { double('right', evaluate: nil) }
+
+ describe '.build' do
+ it 'creates a new instance of the token' do
+ expect(described_class.build('&&', left, right)).to be_a(described_class)
+ end
+
+ context 'with non-evaluable operands' do
+ let(:left) { double('left') }
+ let(:right) { double('right') }
+
+ it 'raises an operator error' do
+ expect { described_class.build('&&', left, right) }.to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+ end
+
+ describe '.type' do
+ it 'is an operator' do
+ expect(described_class.type).to eq :operator
+ end
+ end
+
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
+ describe '#evaluate' do
+ let(:operator) { described_class.new(left, right) }
+
+ subject { operator.evaluate }
+
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
+ end
+
+ context 'when left and right are truthy' do
+ where(:left_value, :right_value) do
+ [true, 1, 'a'].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_truthy }
+ it { is_expected.to eq(right_value) }
+ end
+ end
+
+ context 'when left or right is falsey' do
+ where(:left_value, :right_value) do
+ [true, false, nil].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when left and right are falsey' do
+ where(:left_value, :right_value) do
+ [false, nil].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_falsey }
+ it { is_expected.to eq(left_value) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
index 019a2ed184d..fcbd2863289 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
@@ -5,9 +5,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('==', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('==', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('my-string')
+
+ expect(described_class.build('==', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -17,23 +29,40 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns false when left and right are not equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(2)
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate(VARIABLE: 3)).to eq false
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns true when left and right are equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(1)
+ context 'when left and right are equal' do
+ where(:left_value, :right_value) do
+ [%w(string string)]
+ end
+
+ with_them do
+ it { is_expected.to eq(true) }
+ end
+ end
- operator = described_class.new(left, right)
+ context 'when left and right are not equal' do
+ where(:left_value, :right_value) do
+ ['one string', 'two string'].permutation(2).to_a
+ end
- expect(operator.evaluate(VARIABLE: 3)).to eq true
+ with_them do
+ it { is_expected.to eq(false) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
index 49e5af52f4d..97da66d2bcc 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -6,9 +6,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('=~', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('=~', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('/my-string/')
+
+ expect(described_class.build('=~', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -18,63 +30,93 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns false when left and right do not match' do
- allow(left).to receive(:evaluate).and_return('my-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('something'))
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate).to eq false
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns true when left and right match' do
- allow(left).to receive(:evaluate).and_return('my-awesome-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('awesome.string$'))
+ context 'when left and right do not match' do
+ let(:left_value) { 'my-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('something') }
- operator = described_class.new(left, right)
-
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(nil) }
end
- it 'supports matching against a nil value' do
- allow(left).to receive(:evaluate).and_return(nil)
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('pattern'))
+ context 'when left and right match' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') }
+
+ it { is_expected.to eq(3) }
+ end
- operator = described_class.new(left, right)
+ context 'when left is nil' do
+ let(:left_value) { nil }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
- expect(operator.evaluate).to eq false
+ it { is_expected.to eq(nil) }
end
- it 'supports multiline strings' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My awesome contents
+ context 'when left is a multiline string and matches right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
+
+ My-text-string!
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
+
+ it { is_expected.to eq(24) }
+ end
- My-text-string!
- TEXT
+ context 'when left is a multiline string and does not match right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('text-string'))
+ My-terrible-string!
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(nil) }
end
- it 'supports regexp flags' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My AWESOME content
- TEXT
+ context 'when a matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') }
+
+ it { is_expected.to eq(3) }
+ end
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('(?i)awesome'))
+ context 'when a non-matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') }
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(nil) }
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
index 9aa2f4efd67..38d30c9035a 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
@@ -5,9 +5,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('!=', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('!=', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('my-string')
+
+ expect(described_class.build('!=', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -17,23 +29,45 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns true when left and right are not equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(2)
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate(VARIABLE: 3)).to eq true
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns false when left and right are equal' do
- allow(left).to receive(:evaluate).and_return(1)
- allow(right).to receive(:evaluate).and_return(1)
+ context 'when left and right are equal' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:left_value, :right_value) do
+ 'string' | 'string'
+ 1 | 1
+ '' | ''
+ nil | nil
+ end
+
+ with_them do
+ it { is_expected.to eq(false) }
+ end
+ end
- operator = described_class.new(left, right)
+ context 'when left and right are not equal' do
+ where(:left_value, :right_value) do
+ ['one string', 'two string', 1, 2, '', nil, false, true].permutation(2).to_a
+ end
- expect(operator.evaluate(VARIABLE: 3)).to eq false
+ with_them do
+ it { is_expected.to eq(true) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
index fa3b9651fb4..99110ff8d88 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -6,9 +6,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
let(:right) { double('right') }
describe '.build' do
- it 'creates a new instance of the token' do
- expect(described_class.build('!~', left, right))
- .to be_a(described_class)
+ context 'with non-evaluable operands' do
+ it 'creates a new instance of the token' do
+ expect { described_class.build('!~', left, right) }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'with evaluable operands' do
+ it 'creates a new instance of the token' do
+ allow(left).to receive(:evaluate).and_return('my-string')
+ allow(right).to receive(:evaluate).and_return('my-string')
+
+ expect(described_class.build('!~', left, right))
+ .to be_a(described_class)
+ end
end
end
@@ -18,63 +30,93 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
end
end
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
describe '#evaluate' do
- it 'returns true when left and right do not match' do
- allow(left).to receive(:evaluate).and_return('my-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('something'))
+ let(:operator) { described_class.new(left, right) }
- operator = described_class.new(left, right)
+ subject { operator.evaluate }
- expect(operator.evaluate).to eq true
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
end
- it 'returns false when left and right match' do
- allow(left).to receive(:evaluate).and_return('my-awesome-string')
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('awesome.string$'))
+ context 'when left and right do not match' do
+ let(:left_value) { 'my-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('something') }
- operator = described_class.new(left, right)
-
- expect(operator.evaluate).to eq false
+ it { is_expected.to eq(true) }
end
- it 'supports matching against a nil value' do
- allow(left).to receive(:evaluate).and_return(nil)
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('pattern'))
+ context 'when left and right match' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') }
+
+ it { is_expected.to eq(false) }
+ end
- operator = described_class.new(left, right)
+ context 'when left is nil' do
+ let(:left_value) { nil }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
- expect(operator.evaluate).to eq true
+ it { is_expected.to eq(true) }
end
- it 'supports multiline strings' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My awesome contents
+ context 'when left is a multiline string and matches right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
+
+ My-text-string!
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
+
+ it { is_expected.to eq(false) }
+ end
- My-text-string!
- TEXT
+ context 'when left is a multiline string and does not match right' do
+ let(:left_value) do
+ <<~TEXT
+ My awesome contents
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('text-string'))
+ My-terrible-string!
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') }
- expect(operator.evaluate).to eq false
+ it { is_expected.to eq(true) }
end
- it 'supports regexp flags' do
- allow(left).to receive(:evaluate).and_return <<~TEXT
- My AWESOME content
- TEXT
+ context 'when a matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
+
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') }
+
+ it { is_expected.to eq(false) }
+ end
- allow(right).to receive(:evaluate)
- .and_return(Gitlab::UntrustedRegexp.new('(?i)awesome'))
+ context 'when a non-matching pattern uses regex flags' do
+ let(:left_value) do
+ <<~TEXT
+ My AWESOME content
+ TEXT
+ end
- operator = described_class.new(left, right)
+ let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') }
- expect(operator.evaluate).to eq false
+ it { is_expected.to eq(true) }
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
new file mode 100644
index 00000000000..d542eebc613
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
@@ -0,0 +1,77 @@
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do
+ let(:left) { double('left', evaluate: nil) }
+ let(:right) { double('right', evaluate: nil) }
+
+ describe '.build' do
+ it 'creates a new instance of the token' do
+ expect(described_class.build('||', left, right)).to be_a(described_class)
+ end
+
+ context 'with non-evaluable operands' do
+ let(:left) { double('left') }
+ let(:right) { double('right') }
+
+ it 'raises an operator error' do
+ expect { described_class.build('||', left, right) }.to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+ end
+
+ describe '.type' do
+ it 'is an operator' do
+ expect(described_class.type).to eq :operator
+ end
+ end
+
+ describe '.precedence' do
+ it 'has a precedence' do
+ expect(described_class.precedence).to be_an Integer
+ end
+ end
+
+ describe '#evaluate' do
+ let(:operator) { described_class.new(left, right) }
+
+ subject { operator.evaluate }
+
+ before do
+ allow(left).to receive(:evaluate).and_return(left_value)
+ allow(right).to receive(:evaluate).and_return(right_value)
+ end
+
+ context 'when left and right are truthy' do
+ where(:left_value, :right_value) do
+ [true, 1, 'a'].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_truthy }
+ it { is_expected.to eq(left_value) }
+ end
+ end
+
+ context 'when left or right is truthy' do
+ where(:left_value, :right_value) do
+ [true, false, 'a'].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when left and right are falsey' do
+ where(:left_value, :right_value) do
+ [false, nil].permutation(2).to_a
+ end
+
+ with_them do
+ it { is_expected.to be_falsey }
+ it { is_expected.to eq(right_value) }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
index cff7f57ceff..30ea3f3e28e 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -1,4 +1,4 @@
-require 'fast_spec_helper'
+require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
describe '.build' do
@@ -30,16 +30,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
.to eq Gitlab::UntrustedRegexp.new('pattern')
end
- it 'is a greedy scanner for regexp boundaries' do
- scanner = StringScanner.new('/some .* / pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
- end
-
it 'does not allow to use an empty pattern' do
scanner = StringScanner.new(%(//))
@@ -68,12 +58,90 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
.to eq Gitlab::UntrustedRegexp.new('(?im)pattern')
end
- it 'does not support arbitrary flags' do
+ it 'ignores unsupported flags' do
scanner = StringScanner.new('/pattern/x')
token = described_class.scan(scanner)
- expect(token).to be_nil
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('pattern')
+ end
+
+ it 'is a eager scanner for regexp boundaries' do
+ scanner = StringScanner.new('/some .* / pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* ')
+ end
+
+ it 'does not match on escaped regexp boundaries' do
+ scanner = StringScanner.new('/some .* \/ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
+ end
+
+ it 'recognizes \ as an escape character for /' do
+ scanner = StringScanner.new('/some numeric \/$ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some numeric /$ pattern')
+ end
+
+ it 'does not recognize \ as an escape character for $' do
+ scanner = StringScanner.new('/some numeric \$ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
+ end
+
+ context 'with the ci_variables_complex_expressions feature flag disabled' do
+ before do
+ stub_feature_flags(ci_variables_complex_expressions: false)
+ end
+
+ it 'is a greedy scanner for regexp boundaries' do
+ scanner = StringScanner.new('/some .* / pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
+ end
+
+ it 'does not recognize the \ escape character for /' do
+ scanner = StringScanner.new('/some .* \/ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some .* \/ pattern')
+ end
+
+ it 'does not recognize the \ escape character for $' do
+ scanner = StringScanner.new('/some numeric \$ pattern/')
+
+ token = described_class.scan(scanner)
+
+ expect(token).not_to be_nil
+ expect(token.build.evaluate)
+ .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index 3f11b3f7673..d8db9c262a1 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -58,6 +58,56 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
expect { lexer.tokens }
.to raise_error described_class::SyntaxError
end
+
+ context 'with complex expressions' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(expression).tokens.map(&:value) }
+
+ where(:expression, :tokens) do
+ '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '&&', '$EMPTY_VARIABLE', '=~', '/nope/']
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE']
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | ['$EMPTY_VARIABLE', '==', '""', '&&', '$PRESENT_VARIABLE', '!=', '"nope"']
+ '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ['$PRESENT_VARIABLE', '&&', '$EMPTY_VARIABLE']
+ '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | ['$PRESENT_VARIABLE', '=~', '/my var/', '||', '$EMPTY_VARIABLE', '=~', '/nope/']
+ '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE']
+ '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE != "nope"' | ['$EMPTY_VARIABLE', '==', '""', '||', '$PRESENT_VARIABLE', '!=', '"nope"']
+ '$PRESENT_VARIABLE || $EMPTY_VARIABLE' | ['$PRESENT_VARIABLE', '||', '$EMPTY_VARIABLE']
+ '$PRESENT_VARIABLE && null || $EMPTY_VARIABLE == ""' | ['$PRESENT_VARIABLE', '&&', 'null', '||', '$EMPTY_VARIABLE', '==', '""']
+ end
+
+ with_them do
+ it { is_expected.to eq(tokens) }
+ end
+ end
+
+ context 'with the ci_variables_complex_expressions feature flag turned off' do
+ before do
+ stub_feature_flags(ci_variables_complex_expressions: false)
+ end
+
+ it 'incorrectly tokenizes conjunctive match statements as one match statement' do
+ tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/').tokens
+
+ expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ && $EMPTY_VARIABLE =~ /nope/'])
+ end
+
+ it 'incorrectly tokenizes disjunctive match statements as one statement' do
+ tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/').tokens
+
+ expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ || $EMPTY_VARIABLE =~ /nope/'])
+ end
+
+ it 'raises an error about && operators' do
+ expect { described_class.new('$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE').tokens }
+ .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
+ end
+
+ it 'raises an error about || operators' do
+ expect { described_class.new('$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE').tokens }
+ .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
+ end
+ end
end
describe '#lexemes' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
index 2b78b1dd4a7..e88ec5561b6 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
@@ -2,25 +2,67 @@ require 'fast_spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Parser do
describe '#tree' do
- context 'when using operators' do
+ context 'when using two operators' do
+ it 'returns a reverse descent parse tree' do
+ expect(described_class.seed('$VAR1 == "123"').tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
+ end
+ end
+
+ context 'when using three operators' do
it 'returns a reverse descent parse tree' do
expect(described_class.seed('$VAR1 == "123" == $VAR2').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
end
end
- context 'when using a single token' do
+ context 'when using a single variable token' do
it 'returns a single token instance' do
expect(described_class.seed('$VAR').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
end
end
+ context 'when using a single string token' do
+ it 'returns a single token instance' do
+ expect(described_class.seed('"some value"').tree)
+ .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::String
+ end
+ end
+
context 'when expression is empty' do
it 'returns a null token' do
- expect(described_class.seed('').tree)
+ expect { described_class.seed('').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError
+ end
+ end
+
+ context 'when expression is null' do
+ it 'returns a null token' do
+ expect(described_class.seed('null').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Null
end
end
+
+ context 'when two value tokens have no operator' do
+ it 'raises a parsing error' do
+ expect { described_class.seed('$VAR "text"').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError
+ end
+ end
+
+ context 'when an operator has no left side' do
+ it 'raises an OperatorError' do
+ expect { described_class.seed('== "123"').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
+
+ context 'when an operator has no right side' do
+ it 'raises an OperatorError' do
+ expect { described_class.seed('$VAR ==').tree }
+ .to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index a9fd809409b..057e2f3fbe8 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,4 +1,6 @@
-require 'fast_spec_helper'
+# TODO switch this back after the "ci_variables_complex_expressions" feature flag is removed
+# require 'fast_spec_helper'
+require 'spec_helper'
require 'rspec-parameterized'
describe Gitlab::Ci::Pipeline::Expression::Statement do
@@ -7,8 +9,12 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
end
let(:variables) do
- { 'PRESENT_VARIABLE' => 'my variable',
- EMPTY_VARIABLE: '' }
+ {
+ 'PRESENT_VARIABLE' => 'my variable',
+ 'PATH_VARIABLE' => 'a/path/variable/value',
+ 'FULL_PATH_VARIABLE' => '/a/full/path/variable/value',
+ 'EMPTY_VARIABLE' => ''
+ }
end
describe '.new' do
@@ -21,105 +27,158 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
end
end
- describe '#parse_tree' do
- context 'when expression is empty' do
- let(:text) { '' }
-
- it 'raises an error' do
- expect { subject.parse_tree }
- .to raise_error described_class::StatementError
- end
- end
+ describe '#evaluate' do
+ using RSpec::Parameterized::TableSyntax
- context 'when expression grammar is incorrect' do
- table = [
- '$VAR "text"', # missing operator
- '== "123"', # invalid left side
- '"some string"', # only string provided
- '$VAR ==', # invalid right side
- 'null', # missing lexemes
- '' # empty statement
- ]
-
- table.each do |syntax|
- context "when expression grammar is #{syntax.inspect}" do
- let(:text) { syntax }
-
- it 'raises a statement error exception' do
- expect { subject.parse_tree }
- .to raise_error described_class::StatementError
- end
-
- it 'is an invalid statement' do
- expect(subject).not_to be_valid
- end
- end
- end
+ where(:expression, :value) do
+ '$PRESENT_VARIABLE == "my variable"' | true
+ '"my variable" == $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE == null' | false
+ '$EMPTY_VARIABLE == null' | false
+ '"" == $EMPTY_VARIABLE' | true
+ '$EMPTY_VARIABLE' | ''
+ '$UNDEFINED_VARIABLE == null' | true
+ 'null == $UNDEFINED_VARIABLE' | true
+ '$PRESENT_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE' | nil
+ "$PRESENT_VARIABLE =~ /var.*e$/" | 3
+ '$PRESENT_VARIABLE =~ /va\r.*e$/' | nil
+ '$PRESENT_VARIABLE =~ /va\/r.*e$/' | nil
+ "$PRESENT_VARIABLE =~ /var.*e$/" | 3
+ "$PRESENT_VARIABLE =~ /^var.*/" | nil
+ "$EMPTY_VARIABLE =~ /var.*/" | nil
+ "$UNDEFINED_VARIABLE =~ /var.*/" | nil
+ "$PRESENT_VARIABLE =~ /VAR.*/i" | 3
+ '$PATH_VARIABLE =~ /path\/variable/' | 2
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | 0
+ '$FULL_PATH_VARIABLE =~ /\\/path\\/variable\\/value$/' | 7
+ '$PRESENT_VARIABLE != "my variable"' | false
+ '"my variable" != $PRESENT_VARIABLE' | false
+ '$PRESENT_VARIABLE != null' | true
+ '$EMPTY_VARIABLE != null' | true
+ '"" != $EMPTY_VARIABLE' | false
+ '$UNDEFINED_VARIABLE != null' | false
+ 'null != $UNDEFINED_VARIABLE' | false
+ "$PRESENT_VARIABLE !~ /var.*e$/" | false
+ "$PRESENT_VARIABLE !~ /^var.*/" | true
+ '$PRESENT_VARIABLE !~ /^v\ar.*/' | true
+ '$PRESENT_VARIABLE !~ /^v\/ar.*/' | true
+ "$EMPTY_VARIABLE !~ /var.*/" | true
+ "$UNDEFINED_VARIABLE !~ /var.*/" | true
+ "$PRESENT_VARIABLE !~ /VAR.*/i" | false
+
+ '$PRESENT_VARIABLE && "string"' | 'string'
+ '$PRESENT_VARIABLE && $PRESENT_VARIABLE' | 'my variable'
+ '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ''
+ '$PRESENT_VARIABLE && null' | nil
+ '"string" && $PRESENT_VARIABLE' | 'my variable'
+ '$EMPTY_VARIABLE && $PRESENT_VARIABLE' | 'my variable'
+ 'null && $PRESENT_VARIABLE' | nil
+ '$EMPTY_VARIABLE && "string"' | 'string'
+ '$EMPTY_VARIABLE && $EMPTY_VARIABLE' | ''
+ '"string" && $EMPTY_VARIABLE' | ''
+ '"string" && null' | nil
+ 'null && "string"' | nil
+ '"string" && "string"' | 'string'
+ 'null && null' | nil
+
+ '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | nil
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | 'my variable'
+ '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | true
+ '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | ''
+ '$PRESENT_VARIABLE && $UNDEFINED_VARIABLE' | nil
+ '$UNDEFINED_VARIABLE && $EMPTY_VARIABLE' | nil
+ '$UNDEFINED_VARIABLE && $PRESENT_VARIABLE' | nil
+
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | 2
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | nil
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil
+
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 0
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 2
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | 0
+ '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | nil
+
+ '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | 0
+ '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE != "nope" || $EMPTY_VARIABLE == ""' | true
+
+ '$PRESENT_VARIABLE && null || $EMPTY_VARIABLE == ""' | true
+ '$PRESENT_VARIABLE || $UNDEFINED_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE || $PRESENT_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE == null || $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE || $UNDEFINED_VARIABLE == null' | 'my variable'
end
- context 'when expression grammar is correct' do
- context 'when using an operator' do
- let(:text) { '$VAR == "value"' }
-
- it 'returns a reverse descent parse tree' do
- expect(subject.parse_tree)
- .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
- end
+ with_them do
+ let(:text) { expression }
- it 'is a valid statement' do
- expect(subject).to be_valid
- end
+ it "evaluates to `#{params[:value].inspect}`" do
+ expect(subject.evaluate).to eq(value)
end
- context 'when using a single token' do
- let(:text) { '$PRESENT_VARIABLE' }
+ # This test is used to ensure that our parser
+ # returns exactly the same results as if we
+ # were evaluating using ruby's `eval`
+ context 'when using Ruby eval' do
+ let(:expression_ruby) do
+ expression
+ .gsub(/null/, 'nil')
+ .gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/) { "variables['#{Regexp.last_match(1)}']" }
+ end
- it 'returns a single token instance' do
- expect(subject.parse_tree)
- .to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
+ it 'behaves exactly the same' do
+ expect(instance_eval(expression_ruby)).to eq(subject.evaluate)
end
end
end
- end
- describe '#evaluate' do
- using RSpec::Parameterized::TableSyntax
+ context 'with the ci_variables_complex_expressions feature flag disabled' do
+ before do
+ stub_feature_flags(ci_variables_complex_expressions: false)
+ end
- where(:expression, :value) do
- '$PRESENT_VARIABLE == "my variable"' | true
- '"my variable" == $PRESENT_VARIABLE' | true
- '$PRESENT_VARIABLE == null' | false
- '$EMPTY_VARIABLE == null' | false
- '"" == $EMPTY_VARIABLE' | true
- '$EMPTY_VARIABLE' | ''
- '$UNDEFINED_VARIABLE == null' | true
- 'null == $UNDEFINED_VARIABLE' | true
- '$PRESENT_VARIABLE' | 'my variable'
- '$UNDEFINED_VARIABLE' | nil
- "$PRESENT_VARIABLE =~ /var.*e$/" | true
- "$PRESENT_VARIABLE =~ /^var.*/" | false
- "$EMPTY_VARIABLE =~ /var.*/" | false
- "$UNDEFINED_VARIABLE =~ /var.*/" | false
- "$PRESENT_VARIABLE =~ /VAR.*/i" | true
- '$PRESENT_VARIABLE != "my variable"' | false
- '"my variable" != $PRESENT_VARIABLE' | false
- '$PRESENT_VARIABLE != null' | true
- '$EMPTY_VARIABLE != null' | true
- '"" != $EMPTY_VARIABLE' | false
- '$UNDEFINED_VARIABLE != null' | false
- 'null != $UNDEFINED_VARIABLE' | false
- "$PRESENT_VARIABLE !~ /var.*e$/" | false
- "$PRESENT_VARIABLE !~ /^var.*/" | true
- "$EMPTY_VARIABLE !~ /var.*/" | true
- "$UNDEFINED_VARIABLE !~ /var.*/" | true
- "$PRESENT_VARIABLE !~ /VAR.*/i" | false
- end
+ where(:expression, :value) do
+ '$PRESENT_VARIABLE == "my variable"' | true
+ '"my variable" == $PRESENT_VARIABLE' | true
+ '$PRESENT_VARIABLE == null' | false
+ '$EMPTY_VARIABLE == null' | false
+ '"" == $EMPTY_VARIABLE' | true
+ '$EMPTY_VARIABLE' | ''
+ '$UNDEFINED_VARIABLE == null' | true
+ 'null == $UNDEFINED_VARIABLE' | true
+ '$PRESENT_VARIABLE' | 'my variable'
+ '$UNDEFINED_VARIABLE' | nil
+ "$PRESENT_VARIABLE =~ /var.*e$/" | true
+ "$PRESENT_VARIABLE =~ /^var.*/" | false
+ "$EMPTY_VARIABLE =~ /var.*/" | false
+ "$UNDEFINED_VARIABLE =~ /var.*/" | false
+ "$PRESENT_VARIABLE =~ /VAR.*/i" | true
+ '$PATH_VARIABLE =~ /path/variable/' | true
+ '$PATH_VARIABLE =~ /path\/variable/' | true
+ '$FULL_PATH_VARIABLE =~ /^/a/full/path/variable/value$/' | true
+ '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true
+ '$PRESENT_VARIABLE != "my variable"' | false
+ '"my variable" != $PRESENT_VARIABLE' | false
+ '$PRESENT_VARIABLE != null' | true
+ '$EMPTY_VARIABLE != null' | true
+ '"" != $EMPTY_VARIABLE' | false
+ '$UNDEFINED_VARIABLE != null' | false
+ 'null != $UNDEFINED_VARIABLE' | false
+ "$PRESENT_VARIABLE !~ /var.*e$/" | false
+ "$PRESENT_VARIABLE !~ /^var.*/" | true
+ "$EMPTY_VARIABLE !~ /var.*/" | true
+ "$UNDEFINED_VARIABLE !~ /var.*/" | true
+ "$PRESENT_VARIABLE !~ /VAR.*/i" | false
+ end
- with_them do
- let(:text) { expression }
+ with_them do
+ let(:text) { expression }
- it "evaluates to `#{params[:value].inspect}`" do
- expect(subject.evaluate).to eq value
+ it "evaluates to `#{params[:value].inspect}`" do
+ expect(subject.evaluate).to eq value
+ end
end
end
end
@@ -137,6 +196,8 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
'$INVALID = 1' | false
"$PRESENT_VARIABLE =~ /var.*/" | true
"$UNDEFINED_VARIABLE =~ /var.*/" | false
+ "$PRESENT_VARIABLE !~ /var.*/" | false
+ "$UNDEFINED_VARIABLE !~ /var.*/" | true
end
with_them do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 025439f1b6e..b6231510b91 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -163,11 +163,11 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'matches correct extended statuses' do
expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Build::Canceled]
+ .to eq [Gitlab::Ci::Status::Build::Canceled, Gitlab::Ci::Status::Build::Retryable]
end
- it 'does not fabricate a retryable build status' do
- expect(status).not_to be_a Gitlab::Ci::Status::Build::Retryable
+ it 'fabricates a retryable build status' do
+ expect(status).to be_a Gitlab::Ci::Status::Build::Retryable
end
it 'fabricates status with correct details' do
@@ -177,7 +177,7 @@ describe Gitlab::Ci::Status::Build::Factory do
expect(status.illustration).to include(:image, :size, :title)
expect(status.label).to eq 'canceled'
expect(status).to have_details
- expect(status).not_to have_action
+ expect(status).to have_action
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 0d998d89d73..635b4e556e8 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -3,6 +3,8 @@ require 'spec_helper'
module Gitlab
module Ci
describe YamlProcessor do
+ include StubRequests
+
subject { described_class.new(config, user: nil) }
describe '#build_attributes' do
@@ -648,7 +650,7 @@ module Gitlab
end
before do
- WebMock.stub_request(:get, 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml')
+ stub_full_request('https://gitlab.com/awesome-project/raw/master/.before-script-template.yml')
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
@@ -1468,7 +1470,7 @@ module Gitlab
expect { Gitlab::Ci::YamlProcessor.new(config) }
.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError,
- 'rspec: unknown key in `extends`')
+ 'rspec: unknown keys in `extends` (something)')
end
end
diff --git a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
new file mode 100644
index 00000000000..180520b27e7
--- /dev/null
+++ b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Cluster::PumaWorkerKillerObserver do
+ let(:counter) { Gitlab::Metrics::NullMetric.instance }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter)
+ .with(any_args)
+ .and_return(counter)
+ end
+
+ describe '#callback' do
+ subject { described_class.new }
+
+ it 'increments timeout counter' do
+ worker = double(index: 0)
+
+ expect(counter)
+ .to receive(:increment)
+ .with({ worker: 'worker_0' })
+
+ subject.callback.call(worker)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 32b90041c64..f7642182a17 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -2,7 +2,6 @@
require 'fast_spec_helper'
require 'rspec-parameterized'
-require 'webmock/rspec'
require 'gitlab/danger/helper'
@@ -19,39 +18,6 @@ describe Gitlab::Danger::Helper do
end
end
- let(:teammate_json) do
- <<~JSON
- [
- {
- "username": "in-gitlab-ce",
- "name": "CE maintainer",
- "projects":{ "gitlab-ce": "maintainer backend" }
- },
- {
- "username": "in-gitlab-ee",
- "name": "EE reviewer",
- "projects":{ "gitlab-ee": "reviewer frontend" }
- }
- ]
- JSON
- end
-
- let(:ce_teammate_matcher) do
- satisfy do |teammate|
- teammate.username == 'in-gitlab-ce' &&
- teammate.name == 'CE maintainer' &&
- teammate.projects == { 'gitlab-ce' => 'maintainer backend' }
- end
- end
-
- let(:ee_teammate_matcher) do
- satisfy do |teammate|
- teammate.username == 'in-gitlab-ee' &&
- teammate.name == 'EE reviewer' &&
- teammate.projects == { 'gitlab-ee' => 'reviewer frontend' }
- end
- end
-
let(:fake_git) { double('fake-git') }
subject(:helper) { FakeDanger.new(git: fake_git) }
@@ -119,69 +85,6 @@ describe Gitlab::Danger::Helper do
end
end
- describe '#team' do
- subject(:team) { helper.team }
-
- context 'HTTP failure' do
- before do
- WebMock
- .stub_request(:get, 'https://about.gitlab.com/roulette.json')
- .to_return(status: 404)
- end
-
- it 'raises a pretty error' do
- expect { team }.to raise_error(/Failed to read/)
- end
- end
-
- context 'JSON failure' do
- before do
- WebMock
- .stub_request(:get, 'https://about.gitlab.com/roulette.json')
- .to_return(body: 'INVALID JSON')
- end
-
- it 'raises a pretty error' do
- expect { team }.to raise_error(/Failed to parse/)
- end
- end
-
- context 'success' do
- before do
- WebMock
- .stub_request(:get, 'https://about.gitlab.com/roulette.json')
- .to_return(body: teammate_json)
- end
-
- it 'returns an array of teammates' do
- is_expected.to contain_exactly(ce_teammate_matcher, ee_teammate_matcher)
- end
-
- it 'memoizes the result' do
- expect(team.object_id).to eq(helper.team.object_id)
- end
- end
- end
-
- describe '#project_team' do
- subject { helper.project_team }
-
- before do
- WebMock
- .stub_request(:get, 'https://about.gitlab.com/roulette.json')
- .to_return(body: teammate_json)
- end
-
- it 'filters team by project_name' do
- expect(helper)
- .to receive(:project_name)
- .at_least(:once)
- .and_return('gitlab-ce')
-
- is_expected.to contain_exactly(ce_teammate_matcher)
- end
- end
-
describe '#changes_by_category' do
it 'categorizes changed files' do
expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/foo qa/foo ee/changelogs/foo.yml] }
diff --git a/spec/lib/gitlab/danger/roulette_spec.rb b/spec/lib/gitlab/danger/roulette_spec.rb
new file mode 100644
index 00000000000..40dce0c5378
--- /dev/null
+++ b/spec/lib/gitlab/danger/roulette_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'webmock/rspec'
+
+require 'gitlab/danger/roulette'
+
+describe Gitlab::Danger::Roulette do
+ let(:teammate_json) do
+ <<~JSON
+ [
+ {
+ "username": "in-gitlab-ce",
+ "name": "CE maintainer",
+ "projects":{ "gitlab-ce": "maintainer backend" }
+ },
+ {
+ "username": "in-gitlab-ee",
+ "name": "EE reviewer",
+ "projects":{ "gitlab-ee": "reviewer frontend" }
+ }
+ ]
+ JSON
+ end
+
+ let(:ce_teammate_matcher) do
+ satisfy do |teammate|
+ teammate.username == 'in-gitlab-ce' &&
+ teammate.name == 'CE maintainer' &&
+ teammate.projects == { 'gitlab-ce' => 'maintainer backend' }
+ end
+ end
+
+ let(:ee_teammate_matcher) do
+ satisfy do |teammate|
+ teammate.username == 'in-gitlab-ee' &&
+ teammate.name == 'EE reviewer' &&
+ teammate.projects == { 'gitlab-ee' => 'reviewer frontend' }
+ end
+ end
+
+ subject(:roulette) { Object.new.extend(described_class) }
+
+ describe '#team' do
+ subject(:team) { roulette.team }
+
+ context 'HTTP failure' do
+ before do
+ WebMock
+ .stub_request(:get, described_class::ROULETTE_DATA_URL)
+ .to_return(status: 404)
+ end
+
+ it 'raises a pretty error' do
+ expect { team }.to raise_error(/Failed to read/)
+ end
+ end
+
+ context 'JSON failure' do
+ before do
+ WebMock
+ .stub_request(:get, described_class::ROULETTE_DATA_URL)
+ .to_return(body: 'INVALID JSON')
+ end
+
+ it 'raises a pretty error' do
+ expect { team }.to raise_error(/Failed to parse/)
+ end
+ end
+
+ context 'success' do
+ before do
+ WebMock
+ .stub_request(:get, described_class::ROULETTE_DATA_URL)
+ .to_return(body: teammate_json)
+ end
+
+ it 'returns an array of teammates' do
+ is_expected.to contain_exactly(ce_teammate_matcher, ee_teammate_matcher)
+ end
+
+ it 'memoizes the result' do
+ expect(team.object_id).to eq(roulette.team.object_id)
+ end
+ end
+ end
+
+ describe '#project_team' do
+ subject { roulette.project_team('gitlab-ce') }
+
+ before do
+ WebMock
+ .stub_request(:get, described_class::ROULETTE_DATA_URL)
+ .to_return(body: teammate_json)
+ end
+
+ it 'filters team by project_name' do
+ is_expected.to contain_exactly(ce_teammate_matcher)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index 4bc0a4c1398..6a6cf1429c8 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -1,39 +1,70 @@
# frozen_string_literal: true
+require 'fast_spec_helper'
+
+require 'gitlab/danger/teammate'
+
describe Gitlab::Danger::Teammate do
- subject { described_class.new({ 'projects' => projects }) }
+ subject { described_class.new(options) }
+ let(:options) { { 'projects' => projects, 'role' => role } }
let(:projects) { { project => capabilities } }
+ let(:role) { 'Engineer, Manage' }
+ let(:labels) { [] }
let(:project) { double }
- describe 'multiple roles project project' do
- let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer database'] }
+ context 'when having multiple capabilities' do
+ let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer qa'] }
it '#reviewer? supports multiple roles per project' do
- expect(subject.reviewer?(project, 'backend')).to be_truthy
+ expect(subject.reviewer?(project, :backend, labels)).to be_truthy
end
it '#traintainer? supports multiple roles per project' do
- expect(subject.traintainer?(project, 'database')).to be_truthy
+ expect(subject.traintainer?(project, :qa, labels)).to be_truthy
end
it '#maintainer? supports multiple roles per project' do
- expect(subject.maintainer?(project, 'frontend')).to be_truthy
+ expect(subject.maintainer?(project, :frontend, labels)).to be_truthy
+ end
+
+ context 'when labels contain Create and the category is test' do
+ let(:labels) { ['Create'] }
+
+ context 'when role is Test Automation Engineer, Create' do
+ let(:role) { 'Test Automation Engineer, Create' }
+
+ it '#reviewer? returns true' do
+ expect(subject.reviewer?(project, :test, labels)).to be_truthy
+ end
+
+ it '#maintainer? returns false' do
+ expect(subject.maintainer?(project, :test, labels)).to be_falsey
+ end
+ end
+
+ context 'when role is Test Automation Engineer, Manage' do
+ let(:role) { 'Test Automation Engineer, Manage' }
+
+ it '#reviewer? returns false' do
+ expect(subject.reviewer?(project, :test, labels)).to be_falsey
+ end
+ end
end
end
- describe 'one role project project' do
+ context 'when having single capability' do
let(:capabilities) { 'reviewer backend' }
it '#reviewer? supports one role per project' do
- expect(subject.reviewer?(project, 'backend')).to be_truthy
+ expect(subject.reviewer?(project, :backend, labels)).to be_truthy
end
it '#traintainer? supports one role per project' do
- expect(subject.traintainer?(project, 'database')).to be_falsey
+ expect(subject.traintainer?(project, :database, labels)).to be_falsey
end
it '#maintainer? supports one role per project' do
- expect(subject.maintainer?(project, 'frontend')).to be_falsey
+ expect(subject.maintainer?(project, :frontend, labels)).to be_falsey
end
end
end
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index b236c1a9c49..ed9a1e23529 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -53,6 +53,8 @@ describe Gitlab::DataBuilder::Note do
.to eq(issue.reload.hook_attrs.except('updated_at'))
expect(data[:issue]['updated_at'])
.to be >= issue.hook_attrs['updated_at']
+ expect(data[:issue]['labels'])
+ .to eq(issue.hook_attrs['labels'])
end
context 'with confidential issue' do
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 9ef987a0826..1f36fd5c6ef 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -50,5 +50,14 @@ describe Gitlab::DataBuilder::Pipeline do
it { expect(attributes[:variables]).to be_a(Array) }
it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) }
end
+
+ context 'when pipeline is a detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.first }
+
+ it 'returns a source ref' do
+ expect(attributes[:ref]).to eq(merge_request.source_branch)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 7644d83992f..e72fb9c6fbc 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -29,51 +29,6 @@ describe Gitlab::Git::Repository, :seed_helper do
let(:storage_path) { TestEnv.repos_path }
let(:user) { build(:user) }
- describe '.create_hooks' do
- let(:repo_path) { File.join(storage_path, 'hook-test.git') }
- let(:hooks_dir) { File.join(repo_path, 'hooks') }
- let(:target_hooks_dir) { Gitlab::Shell.new.hooks_path }
- let(:existing_target) { File.join(repo_path, 'foobar') }
-
- before do
- FileUtils.rm_rf(repo_path)
- FileUtils.mkdir_p(repo_path)
- end
-
- context 'hooks is a directory' do
- let(:existing_file) { File.join(hooks_dir, 'my-file') }
-
- before do
- FileUtils.mkdir_p(hooks_dir)
- FileUtils.touch(existing_file)
- described_class.create_hooks(repo_path, target_hooks_dir)
- end
-
- it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
- it { expect(Dir[File.join(repo_path, "hooks.old.*/my-file")].count).to eq(1) }
- end
-
- context 'hooks is a valid symlink' do
- before do
- FileUtils.mkdir_p existing_target
- File.symlink(existing_target, hooks_dir)
- described_class.create_hooks(repo_path, target_hooks_dir)
- end
-
- it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
- end
-
- context 'hooks is a broken symlink' do
- before do
- FileUtils.rm_f(existing_target)
- File.symlink(existing_target, hooks_dir)
- described_class.create_hooks(repo_path, target_hooks_dir)
- end
-
- it { expect(File.readlink(hooks_dir)).to eq(target_hooks_dir) }
- end
- end
-
describe "Respond to" do
subject { repository }
@@ -231,6 +186,18 @@ describe Gitlab::Git::Repository, :seed_helper do
it { is_expected.to be < 2 }
end
+ describe '#object_directory_size' do
+ before do
+ allow(repository.gitaly_repository_client)
+ .to receive(:get_object_directory_size)
+ .and_return(2)
+ end
+
+ subject { repository.object_directory_size }
+
+ it { is_expected.to eq 2048 }
+ end
+
describe '#empty?' do
it { expect(repository).not_to be_empty }
end
@@ -1959,13 +1926,6 @@ describe Gitlab::Git::Repository, :seed_helper do
expect { imported_repo.fsck }.not_to raise_exception
end
- it 'creates a symlink to the global hooks dir' do
- imported_repo.create_from_bundle(valid_bundle_path)
- hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') }
-
- expect(File.readlink(hooks_path)).to eq(Gitlab::Shell.new.hooks_path)
- end
-
it 'raises an error if the bundle is an attempted malicious payload' do
expect do
imported_repo.create_from_bundle(malicious_bundle_path)
diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb
index 3ab04a1c46d..b63389af29f 100644
--- a/spec/lib/gitlab/git_ref_validator_spec.rb
+++ b/spec/lib/gitlab/git_ref_validator_spec.rb
@@ -1,31 +1,69 @@
require 'spec_helper'
describe Gitlab::GitRefValidator do
- it { expect(described_class.validate('feature/new')).to be_truthy }
- it { expect(described_class.validate('implement_@all')).to be_truthy }
- it { expect(described_class.validate('my_new_feature')).to be_truthy }
- it { expect(described_class.validate('my-branch')).to be_truthy }
- it { expect(described_class.validate('#1')).to be_truthy }
- it { expect(described_class.validate('feature/refs/heads/foo')).to be_truthy }
- it { expect(described_class.validate('feature/~new/')).to be_falsey }
- it { expect(described_class.validate('feature/^new/')).to be_falsey }
- it { expect(described_class.validate('feature/:new/')).to be_falsey }
- it { expect(described_class.validate('feature/?new/')).to be_falsey }
- it { expect(described_class.validate('feature/*new/')).to be_falsey }
- it { expect(described_class.validate('feature/[new/')).to be_falsey }
- it { expect(described_class.validate('feature/new/')).to be_falsey }
- it { expect(described_class.validate('feature/new.')).to be_falsey }
- it { expect(described_class.validate('feature\@{')).to be_falsey }
- it { expect(described_class.validate('feature\new')).to be_falsey }
- it { expect(described_class.validate('feature//new')).to be_falsey }
- it { expect(described_class.validate('feature new')).to be_falsey }
- it { expect(described_class.validate('refs/heads/')).to be_falsey }
- it { expect(described_class.validate('refs/remotes/')).to be_falsey }
- it { expect(described_class.validate('refs/heads/feature')).to be_falsey }
- it { expect(described_class.validate('refs/remotes/origin')).to be_falsey }
- it { expect(described_class.validate('-')).to be_falsey }
- it { expect(described_class.validate('-branch')).to be_falsey }
- it { expect(described_class.validate('.tag')).to be_falsey }
- it { expect(described_class.validate('my branch')).to be_falsey }
- it { expect(described_class.validate("\xA0\u0000\xB0")).to be_falsey }
+ using RSpec::Parameterized::TableSyntax
+
+ context '.validate' do
+ it { expect(described_class.validate('feature/new')).to be true }
+ it { expect(described_class.validate('implement_@all')).to be true }
+ it { expect(described_class.validate('my_new_feature')).to be true }
+ it { expect(described_class.validate('my-branch')).to be true }
+ it { expect(described_class.validate('#1')).to be true }
+ it { expect(described_class.validate('feature/refs/heads/foo')).to be true }
+ it { expect(described_class.validate('feature/~new/')).to be false }
+ it { expect(described_class.validate('feature/^new/')).to be false }
+ it { expect(described_class.validate('feature/:new/')).to be false }
+ it { expect(described_class.validate('feature/?new/')).to be false }
+ it { expect(described_class.validate('feature/*new/')).to be false }
+ it { expect(described_class.validate('feature/[new/')).to be false }
+ it { expect(described_class.validate('feature/new/')).to be false }
+ it { expect(described_class.validate('feature/new.')).to be false }
+ it { expect(described_class.validate('feature\@{')).to be false }
+ it { expect(described_class.validate('feature\new')).to be false }
+ it { expect(described_class.validate('feature//new')).to be false }
+ it { expect(described_class.validate('feature new')).to be false }
+ it { expect(described_class.validate('refs/heads/')).to be false }
+ it { expect(described_class.validate('refs/remotes/')).to be false }
+ it { expect(described_class.validate('refs/heads/feature')).to be false }
+ it { expect(described_class.validate('refs/remotes/origin')).to be false }
+ it { expect(described_class.validate('-')).to be false }
+ it { expect(described_class.validate('-branch')).to be false }
+ it { expect(described_class.validate('+foo:bar')).to be false }
+ it { expect(described_class.validate('foo:bar')).to be false }
+ it { expect(described_class.validate('.tag')).to be false }
+ it { expect(described_class.validate('my branch')).to be false }
+ it { expect(described_class.validate("\xA0\u0000\xB0")).to be false }
+ end
+
+ context '.validate_merge_request_branch' do
+ it { expect(described_class.validate_merge_request_branch('HEAD')).to be true }
+ it { expect(described_class.validate_merge_request_branch('feature/new')).to be true }
+ it { expect(described_class.validate_merge_request_branch('implement_@all')).to be true }
+ it { expect(described_class.validate_merge_request_branch('my_new_feature')).to be true }
+ it { expect(described_class.validate_merge_request_branch('my-branch')).to be true }
+ it { expect(described_class.validate_merge_request_branch('#1')).to be true }
+ it { expect(described_class.validate_merge_request_branch('feature/refs/heads/foo')).to be true }
+ it { expect(described_class.validate_merge_request_branch('feature/~new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/^new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/:new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/?new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/*new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/[new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/new/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature/new.')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature\@{')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature\new')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature//new')).to be false }
+ it { expect(described_class.validate_merge_request_branch('feature new')).to be false }
+ it { expect(described_class.validate_merge_request_branch('refs/heads/master')).to be true }
+ it { expect(described_class.validate_merge_request_branch('refs/heads/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('refs/remotes/')).to be false }
+ it { expect(described_class.validate_merge_request_branch('-')).to be false }
+ it { expect(described_class.validate_merge_request_branch('-branch')).to be false }
+ it { expect(described_class.validate_merge_request_branch('+foo:bar')).to be false }
+ it { expect(described_class.validate_merge_request_branch('foo:bar')).to be false }
+ it { expect(described_class.validate_merge_request_branch('.tag')).to be false }
+ it { expect(described_class.validate_merge_request_branch('my branch')).to be false }
+ it { expect(described_class.validate_merge_request_branch("\xA0\u0000\xB0")).to be false }
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
index e4fe01a671f..52630ba0223 100644
--- a/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/conflicts_service_spec.rb
@@ -35,7 +35,7 @@ describe Gitlab::GitalyClient::ConflictsService do
end
let(:source_branch) { 'master' }
let(:target_branch) { 'feature' }
- let(:commit_message) { 'Solving conflicts' }
+ let(:commit_message) { 'Solving conflicts\n\nTést' }
let(:resolution) do
Gitlab::Git::Conflict::Resolution.new(user, files, commit_message)
end
@@ -51,6 +51,25 @@ describe Gitlab::GitalyClient::ConflictsService do
subject
end
+ context 'with branches with UTF-8 characters' do
+ let(:source_branch) { 'testòbranch' }
+ let(:target_branch) { 'ábranch' }
+
+ it 'handles commit messages with UTF-8 characters' do
+ allow(::Gitlab::GitalyClient).to receive(:call).and_call_original
+ expect(::Gitlab::GitalyClient).to receive(:call).with(anything, :conflicts_service, :resolve_conflicts, any_args) do |*args|
+ # Force the generation of request messages by iterating through the enumerator
+ message = args[3].to_a.first
+ params = [message.header.commit_message, message.header.source_branch, message.header.target_branch]
+ expect(params.map(&:encoding).uniq).to eq([Encoding::ASCII_8BIT])
+
+ double(resolution_error: nil)
+ end
+
+ subject
+ end
+ end
+
it 'raises a relevant exception if resolution_error is present' do
expect_any_instance_of(Gitaly::ConflictsService::Stub).to receive(:resolve_conflicts)
.with(kind_of(Enumerator), kind_of(Hash)).and_return(double(resolution_error: "something happened"))
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 7579a6577b9..18663a72fcd 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -78,6 +78,24 @@ describe Gitlab::GitalyClient::OperationService do
subject
end
+ describe '#user_merge_to_ref' do
+ let(:branch) { 'my-branch' }
+ let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
+ let(:ref) { 'refs/merge-requests/x/merge' }
+ let(:message) { 'validación' }
+ let(:response) { Gitaly::UserMergeToRefResponse.new(commit_id: 'new-commit-id') }
+
+ subject { client.user_merge_to_ref(user, source_sha, branch, ref, message) }
+
+ it 'sends a user_merge_to_ref message' do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_merge_to_ref).with(kind_of(Gitaly::UserMergeToRefRequest), kind_of(Hash))
+ .and_return(response)
+
+ subject
+ end
+ end
+
context "when pre_receive_error is present" do
let(:response) do
Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "GitLab: something failed")
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 09de7ca6afd..a3808adb376 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -73,6 +73,17 @@ describe Gitlab::GitalyClient::RepositoryService do
end
end
+ describe '#get_object_directory_size' do
+ it 'sends a get_object_directory_size message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:get_object_directory_size)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(size: 0)
+
+ client.get_object_directory_size
+ end
+ end
+
describe '#apply_gitattributes' do
let(:revision) { 'master' }
diff --git a/spec/lib/gitlab/github_import/parallel_importer_spec.rb b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
index f5df38c9aaf..ecab64a372a 100644
--- a/spec/lib/gitlab/github_import/parallel_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_importer_spec.rb
@@ -25,18 +25,9 @@ describe Gitlab::GithubImport::ParallelImporter do
end
it 'sets the JID in Redis' do
- expect(Gitlab::SidekiqStatus)
- .to receive(:set)
- .with("github-importer/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
- .and_call_original
+ expect(Gitlab::Import::SetAsyncJid).to receive(:set_jid).with(project).and_call_original
importer.execute
end
-
- it 'updates the import JID of the project' do
- importer.execute
-
- expect(project.import_state.reload.jid).to eq("github-importer/#{project.id}")
- end
end
end
diff --git a/spec/lib/gitlab/graphql/loaders/batch_commit_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_commit_loader_spec.rb
new file mode 100644
index 00000000000..0137b1029cd
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/batch_commit_loader_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::Graphql::Loaders::BatchCommitLoader do
+ include GraphqlHelpers
+
+ set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'files/lfs/lfs_object.iso'), repository) }
+ let(:otherblob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'README'), repository) }
+
+ describe '#find' do
+ it 'batch-resolves LFS blob IDs' do
+ expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).once.and_call_original
+
+ result = batch do
+ [blob, otherblob].map { |b| described_class.new(repository, b.id).find }
+ end
+
+ expect(result.first).to eq(blob.lfs_oid)
+ expect(result.last).to eq(nil)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
new file mode 100644
index 00000000000..ec2fcad31e5
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/batch_project_statistics_loader_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader do
+ describe '#find' do
+ it 'only queries once for project statistics' do
+ stats = create_list(:project_statistics, 2)
+ project1 = stats.first.project
+ project2 = stats.last.project
+
+ expect do
+ described_class.new(project1.id).find
+ described_class.new(project2.id).find
+ end.not_to exceed_query_limit(1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
new file mode 100644
index 00000000000..66033736e01
--- /dev/null
+++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
+ subject { described_class.new }
+
+ describe '#analyze?' do
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(graphql_logging: false)
+ end
+
+ it 'disables the analyzer' do
+ expect(subject.analyze?(anything)).to be_falsey
+ end
+ end
+
+ context 'feature flag enabled by default' do
+ it 'enables the analyzer' do
+ expect(subject.analyze?(anything)).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
new file mode 100644
index 00000000000..d45e690160c
--- /dev/null
+++ b/spec/lib/gitlab/graphql/representation/tree_entry_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Representation::TreeEntry do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ describe '.decorate' do
+ it 'returns NilClass when given nil' do
+ expect(described_class.decorate(nil, repository)).to be_nil
+ end
+
+ it 'returns array of TreeEntry' do
+ entries = described_class.decorate(repository.tree.blobs, repository)
+
+ expect(entries.first).to be_a(described_class)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql_logger_spec.rb b/spec/lib/gitlab/graphql_logger_spec.rb
new file mode 100644
index 00000000000..4977f98b83e
--- /dev/null
+++ b/spec/lib/gitlab/graphql_logger_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::GraphqlLogger do
+ subject { described_class.new('/dev/null') }
+
+ let(:now) { Time.now }
+
+ it 'builds a logger once' do
+ expect(::Logger).to receive(:new).and_call_original
+
+ subject.info('hello world')
+ subject.error('hello again')
+ end
+
+ context 'logging a GraphQL query' do
+ let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) }
+
+ it 'logs a query from JSON' do
+ analyzer_memo = {
+ query_string: query,
+ variables: {},
+ complexity: 181,
+ depth: 0,
+ duration: 7
+ }
+
+ output = subject.format_message('INFO', now, 'test', analyzer_memo)
+
+ data = JSON.parse(output)
+ expect(data['severity']).to eq('INFO')
+ expect(data['time']).to eq(now.utc.iso8601(3))
+ expect(data['complexity']).to eq(181)
+ expect(data['variables']).to eq({})
+ expect(data['depth']).to eq(0)
+ expect(data['duration']).to eq(7)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
new file mode 100644
index 00000000000..930d1f62272
--- /dev/null
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::HTTPConnectionAdapter do
+ describe '#connection' do
+ context 'when local requests are not allowed' do
+ it 'sets up the connection' do
+ uri = URI('https://example.org')
+
+ connection = described_class.new(uri).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('93.184.216.34')
+ expect(connection.hostname_override).to eq('example.org')
+ expect(connection.addr_port).to eq('example.org')
+ expect(connection.port).to eq(443)
+ end
+
+ it 'raises error when it is a request to local address' do
+ uri = URI('http://172.16.0.0/12')
+
+ expect { described_class.new(uri).connection }
+ .to raise_error(Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://172.16.0.0/12' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request to localhost address' do
+ uri = URI('http://127.0.0.1')
+
+ expect { described_class.new(uri).connection }
+ .to raise_error(Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed")
+ end
+
+ context 'when port different from URL scheme is used' do
+ it 'sets up the addr_port accordingly' do
+ uri = URI('https://example.org:8080')
+
+ connection = described_class.new(uri).connection
+
+ expect(connection.address).to eq('93.184.216.34')
+ expect(connection.hostname_override).to eq('example.org')
+ expect(connection.addr_port).to eq('example.org:8080')
+ expect(connection.port).to eq(8080)
+ end
+ end
+ end
+
+ context 'when DNS rebinding protection is disabled' do
+ it 'sets up the connection' do
+ stub_application_setting(dns_rebinding_protection_enabled: false)
+
+ uri = URI('https://example.org')
+
+ connection = described_class.new(uri).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('example.org')
+ expect(connection.hostname_override).to eq(nil)
+ expect(connection.addr_port).to eq('example.org')
+ expect(connection.port).to eq(443)
+ end
+ end
+
+ context 'when http(s) environment variable is set' do
+ it 'sets up the connection' do
+ stub_env('https_proxy' => 'https://my.proxy')
+
+ uri = URI('https://example.org')
+
+ connection = described_class.new(uri).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('example.org')
+ expect(connection.hostname_override).to eq(nil)
+ expect(connection.addr_port).to eq('example.org')
+ expect(connection.port).to eq(443)
+ end
+ end
+
+ context 'when local requests are allowed' do
+ it 'sets up the connection' do
+ uri = URI('https://example.org')
+
+ connection = described_class.new(uri, allow_local_requests: true).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('93.184.216.34')
+ expect(connection.hostname_override).to eq('example.org')
+ expect(connection.addr_port).to eq('example.org')
+ expect(connection.port).to eq(443)
+ end
+
+ it 'sets up the connection when it is a local network' do
+ uri = URI('http://172.16.0.0/12')
+
+ connection = described_class.new(uri, allow_local_requests: true).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('172.16.0.0')
+ expect(connection.hostname_override).to be(nil)
+ expect(connection.addr_port).to eq('172.16.0.0')
+ expect(connection.port).to eq(80)
+ end
+
+ it 'sets up the connection when it is localhost' do
+ uri = URI('http://127.0.0.1')
+
+ connection = described_class.new(uri, allow_local_requests: true).connection
+
+ expect(connection).to be_a(Net::HTTP)
+ expect(connection.address).to eq('127.0.0.1')
+ expect(connection.hostname_override).to be(nil)
+ expect(connection.addr_port).to eq('127.0.0.1')
+ expect(connection.port).to eq(80)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index 6c37c157f5d..158f77cab2c 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -1,6 +1,28 @@
require 'spec_helper'
describe Gitlab::HTTP do
+ include StubRequests
+
+ context 'when allow_local_requests' do
+ it 'sends the request to the correct URI' do
+ stub_full_request('https://example.org:8080', ip_address: '8.8.8.8').to_return(status: 200)
+
+ described_class.get('https://example.org:8080', allow_local_requests: false)
+
+ expect(WebMock).to have_requested(:get, 'https://8.8.8.8:8080').once
+ end
+ end
+
+ context 'when not allow_local_requests' do
+ it 'sends the request to the correct URI' do
+ stub_full_request('https://example.org:8080')
+
+ described_class.get('https://example.org:8080', allow_local_requests: true)
+
+ expect(WebMock).to have_requested(:get, 'https://8.8.8.9:8080').once
+ end
+ end
+
describe 'allow_local_requests_from_hooks_and_services is' do
before do
WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
@@ -21,6 +43,8 @@ describe Gitlab::HTTP do
context 'if allow_local_requests set to true' do
it 'override the global value and allow requests to localhost or private network' do
+ stub_full_request('http://localhost:3003')
+
expect { described_class.get('http://localhost:3003', allow_local_requests: true) }.not_to raise_error
end
end
@@ -32,6 +56,8 @@ describe Gitlab::HTTP do
end
it 'allow requests to localhost' do
+ stub_full_request('http://localhost:3003')
+
expect { described_class.get('http://localhost:3003') }.not_to raise_error
end
@@ -49,7 +75,7 @@ describe Gitlab::HTTP do
describe 'handle redirect loops' do
before do
- WebMock.stub_request(:any, "http://example.org").to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep"))
+ stub_full_request("http://example.org", method: :any).to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep"))
end
it 'handles GET requests' do
diff --git a/spec/lib/gitlab/import/set_async_jid_spec.rb b/spec/lib/gitlab/import/set_async_jid_spec.rb
new file mode 100644
index 00000000000..51397280138
--- /dev/null
+++ b/spec/lib/gitlab/import/set_async_jid_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::Import::SetAsyncJid do
+ describe '.set_jid', :clean_gitlab_redis_shared_state do
+ let(:project) { create(:project, :import_scheduled) }
+
+ it 'sets the JID in Redis' do
+ expect(Gitlab::SidekiqStatus)
+ .to receive(:set)
+ .with("async-import/#{project.id}", StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+ .and_call_original
+
+ described_class.set_jid(project)
+ end
+
+ it 'updates the import JID of the project' do
+ described_class.set_jid(project)
+
+ expect(project.import_state.reload.jid).to eq("async-import/#{project.id}")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
index 7c4ac62790e..21a227335cd 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
+ include StubRequests
+
let(:example_url) { 'http://www.example.com' }
let(:strategy) { subject.new(url: example_url, http_method: 'post') }
let!(:project) { create(:project, :with_export) }
@@ -35,7 +37,7 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
context 'when upload fails' do
it 'stores the export error' do
- stub_request(:post, example_url).to_return(status: [404, 'Page not found'])
+ stub_full_request(example_url, method: :post).to_return(status: [404, 'Page not found'])
strategy.execute(user, project)
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
index 536cc359d39..99669285d5b 100644
--- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -18,7 +18,11 @@ describe Gitlab::ImportExport::AttributeCleaner do
'notid' => 99,
'import_source' => 'whatever',
'import_type' => 'whatever',
- 'non_existent_attr' => 'whatever'
+ 'non_existent_attr' => 'whatever',
+ 'some_html' => '<p>dodgy html</p>',
+ 'legit_html' => '<p>legit html</p>',
+ '_html' => '<p>perfectly ordinary html</p>',
+ 'cached_markdown_version' => 12345
}
end
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 67e4c289906..b95b5dfe791 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -12,7 +12,6 @@ describe Gitlab::ImportExport::MembersMapper do
"access_level" => 40,
"source_id" => 14,
"source_type" => "Project",
- "user_id" => 19,
"notification_level" => 3,
"created_at" => "2016-03-11T10:21:44.822Z",
"updated_at" => "2016-03-11T10:21:44.822Z",
@@ -25,7 +24,8 @@ describe Gitlab::ImportExport::MembersMapper do
"id" => exported_user_id,
"email" => user2.email,
"username" => 'test'
- }
+ },
+ "user_id" => 19
},
{
"id" => 3,
@@ -73,6 +73,22 @@ describe Gitlab::ImportExport::MembersMapper do
expect(user2.authorized_project?(project)).to be true
end
+ it 'maps an owner as a maintainer' do
+ exported_members.first['access_level'] = ProjectMember::OWNER
+
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ expect(ProjectMember.find_by_user_id(user2.id).access_level).to eq(ProjectMember::MAINTAINER)
+ end
+
+ it 'removes old user_id from member_hash to avoid conflict with user key' do
+ expect(ProjectMember).to receive(:create)
+ .twice
+ .with(hash_excluding('user_id'))
+ .and_call_original
+
+ members_mapper.map
+ end
+
context 'user is not an admin' do
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 4a7accc4c52..6512fe80a3b 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -158,6 +158,8 @@
{
"id": 351,
"note": "Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi.",
+ "note_html": "<p>something else entirely</p>",
+ "cached_markdown_version": 917504,
"noteable_type": "Issue",
"author_id": 26,
"created_at": "2016-06-14T15:02:47.770Z",
@@ -2363,6 +2365,8 @@
{
"id": 671,
"note": "Sit voluptatibus eveniet architecto quidem.",
+ "note_html": "<p>something else entirely</p>",
+ "cached_markdown_version": 917504,
"noteable_type": "MergeRequest",
"author_id": 26,
"created_at": "2016-06-14T15:02:56.632Z",
@@ -6219,7 +6223,10 @@
"erased_by_id": null,
"erased_at": null,
"type": "Ci::Build",
- "token": "abcd"
+ "token": "abcd",
+ "artifacts_file_store": 1,
+ "artifacts_metadata_store": 1,
+ "artifacts_size": 10
},
{
"id": 72,
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 651aa600fb2..ca46006ea58 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -58,6 +58,26 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(Milestone.find_by_description('test milestone').issues.count).to eq(2)
end
+ context 'when importing a project with cached_markdown_version and note_html' do
+ context 'for an Issue' do
+ it 'does not import note_html' do
+ note_content = 'Quo reprehenderit aliquam qui dicta impedit cupiditate eligendi'
+ issue_note = Issue.find_by(description: 'Aliquam enim illo et possimus.').notes.select { |n| n.note.match(/#{note_content}/)}.first
+
+ expect(issue_note.note_html).to match(/#{note_content}/)
+ end
+ end
+
+ context 'for a Merge Request' do
+ it 'does not import note_html' do
+ note_content = 'Sit voluptatibus eveniet architecto quidem'
+ merge_request_note = MergeRequest.find_by(title: 'MR1').notes.select { |n| n.note.match(/#{note_content}/)}.first
+
+ expect(merge_request_note.note_html).to match(/#{note_content}/)
+ end
+ end
+ end
+
it 'creates a valid pipeline note' do
expect(Ci::Pipeline.find_by_sha('sha-notes').notes).not_to be_empty
end
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 8a699eb1461..e2ffb2adb9b 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -34,11 +34,5 @@ describe Gitlab::ImportExport::RepoRestorer do
it 'restores the repo successfully' do
expect(restorer.restore).to be_truthy
end
-
- it 'has the webhooks' do
- restorer.restore
-
- expect(project_hook_exists?(project)).to be true
- end
end
end
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
index 94abf9679c4..8060b5d4448 100644
--- a/spec/lib/gitlab/import_sources_spec.rb
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -14,7 +14,8 @@ describe Gitlab::ImportSources do
'Repo by URL' => 'git',
'GitLab export' => 'gitlab_project',
'Gitea' => 'gitea',
- 'Manifest file' => 'manifest'
+ 'Manifest file' => 'manifest',
+ 'Phabricator' => 'phabricator'
}
expect(described_class.options).to eq(expected)
@@ -35,6 +36,7 @@ describe Gitlab::ImportSources do
gitlab_project
gitea
manifest
+ phabricator
)
expect(described_class.values).to eq(expected)
@@ -53,6 +55,7 @@ describe Gitlab::ImportSources do
fogbugz
gitlab_project
gitea
+ phabricator
)
expect(described_class.importer_names).to eq(expected)
@@ -70,7 +73,8 @@ describe Gitlab::ImportSources do
'git' => nil,
'gitlab_project' => Gitlab::ImportExport::Importer,
'gitea' => Gitlab::LegacyGithubImport::Importer,
- 'manifest' => nil
+ 'manifest' => nil,
+ 'phabricator' => Gitlab::PhabricatorImport::Importer
}
import_sources.each do |name, klass|
@@ -91,7 +95,8 @@ describe Gitlab::ImportSources do
'git' => 'Repo by URL',
'gitlab_project' => 'GitLab export',
'gitea' => 'Gitea',
- 'manifest' => 'Manifest file'
+ 'manifest' => 'Manifest file',
+ 'phabricator' => 'Phabricator'
}
import_sources.each do |name, title|
@@ -102,7 +107,7 @@ describe Gitlab::ImportSources do
end
describe 'imports_repository? checker' do
- let(:allowed_importers) { %w[github gitlab_project bitbucket_server] }
+ let(:allowed_importers) { %w[github gitlab_project bitbucket_server phabricator] }
it 'fails if any importer other than the allowed ones implements this method' do
current_importers = described_class.values.select { |kind| described_class.importer(kind).try(:imports_repository?) }
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index 24ce397ec3d..0de809833e6 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -36,6 +36,8 @@ describe Gitlab::Kubernetes::Helm::Api do
describe '#uninstall' do
before do
allow(client).to receive(:create_pod).and_return(nil)
+ allow(client).to receive(:get_config_map).and_return(nil)
+ allow(client).to receive(:create_config_map).and_return(nil)
allow(client).to receive(:delete_pod).and_return(nil)
allow(namespace).to receive(:ensure_exists!).once
end
@@ -53,6 +55,28 @@ describe Gitlab::Kubernetes::Helm::Api do
subject.uninstall(command)
end
+
+ context 'with a ConfigMap' do
+ let(:resource) { Gitlab::Kubernetes::ConfigMap.new(application_name, files).generate }
+
+ it 'creates a ConfigMap on kubeclient' do
+ expect(client).to receive(:create_config_map).with(resource).once
+
+ subject.install(command)
+ end
+
+ context 'config map already exists' do
+ before do
+ expect(client).to receive(:get_config_map).with("values-content-configuration-#{application_name}", gitlab_namespace).and_return(resource)
+ end
+
+ it 'updates the config map' do
+ expect(client).to receive(:update_config_map).with(resource).once
+
+ subject.install(command)
+ end
+ end
+ end
end
describe '#install' do
diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
index 74622f356de..fcd92586362 100644
--- a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb
@@ -3,23 +3,11 @@
require 'spec_helper'
describe ::Gitlab::LetsEncrypt::Challenge do
- delegated_methods = {
- url: 'https://example.com/',
- status: 'pending',
- token: 'tokenvalue',
- file_content: 'hereisfilecontent',
- request_validation: true
- }
+ include LetsEncryptHelpers
- let(:acme_challenge) do
- acme_challenge = instance_double('Acme::Client::Resources::Challenge')
- allow(acme_challenge).to receive_messages(delegated_methods)
- acme_challenge
- end
-
- let(:challenge) { described_class.new(acme_challenge) }
+ let(:challenge) { described_class.new(acme_challenge_double) }
- delegated_methods.each do |method, value|
+ LetsEncryptHelpers::ACME_CHALLENGE_METHODS.each do |method, value|
describe "##{method}" do
it 'delegates to Acme::Client::Resources::Challenge' do
expect(challenge.public_send(method)).to eq(value)
diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb
index 16a16acfd25..5454d9c1af4 100644
--- a/spec/lib/gitlab/lets_encrypt/client_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb
@@ -26,6 +26,36 @@ describe ::Gitlab::LetsEncrypt::Client do
)
end
+ it 'generates and stores private key and initialize acme client with it' do
+ expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil)
+
+ subject
+
+ saved_private_key = Gitlab::CurrentSettings.lets_encrypt_private_key
+
+ expect(saved_private_key).to be
+ expect(Acme::Client).to have_received(:new).with(
+ hash_including(private_key: eq_pem(saved_private_key))
+ )
+ end
+
+ context 'when private key is saved in settings' do
+ let!(:saved_private_key) do
+ key = OpenSSL::PKey::RSA.new(4096).to_pem
+ Gitlab::CurrentSettings.current_application_settings.update(lets_encrypt_private_key: key)
+ key
+ end
+
+ it 'uses current value of private key' do
+ subject
+
+ expect(Acme::Client).to have_received(:new).with(
+ hash_including(private_key: eq_pem(saved_private_key))
+ )
+ expect(Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(saved_private_key)
+ end
+ end
+
context 'when acme integration is disabled' do
before do
stub_application_setting(lets_encrypt_terms_of_service_accepted: false)
@@ -92,6 +122,18 @@ describe ::Gitlab::LetsEncrypt::Client do
context 'when terms of service are accepted' do
it { is_expected.to eq(true) }
+ context "when private_key isn't present and database is read only" do
+ before do
+ allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
+ end
+
+ it 'returns false' do
+ expect(::Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil)
+
+ is_expected.to eq(false)
+ end
+ end
+
context 'when feature flag is disabled' do
before do
stub_feature_flags(pages_auto_ssl: false)
diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb
index ee7058baf8d..1a759103c44 100644
--- a/spec/lib/gitlab/lets_encrypt/order_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb
@@ -3,20 +3,13 @@
require 'spec_helper'
describe ::Gitlab::LetsEncrypt::Order do
- delegated_methods = {
- url: 'https://example.com/',
- status: 'valid'
- }
-
- let(:acme_order) do
- acme_order = instance_double('Acme::Client::Resources::Order')
- allow(acme_order).to receive_messages(delegated_methods)
- acme_order
- end
+ include LetsEncryptHelpers
+
+ let(:acme_order) { acme_order_double }
let(:order) { described_class.new(acme_order) }
- delegated_methods.each do |method, value|
+ LetsEncryptHelpers::ACME_ORDER_METHODS.each do |method, value|
describe "##{method}" do
it 'delegates to Acme::Client::Resources::Order' do
expect(order.public_send(method)).to eq(value)
@@ -25,15 +18,24 @@ describe ::Gitlab::LetsEncrypt::Order do
end
describe '#new_challenge' do
- before do
- challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01')
- authorization = instance_double('Acme::Client::Resources::Authorization')
- allow(authorization).to receive(:http).and_return(challenge)
- allow(acme_order).to receive(:authorizations).and_return([authorization])
- end
-
it 'returns challenge' do
expect(order.new_challenge).to be_a(::Gitlab::LetsEncrypt::Challenge)
end
end
+
+ describe '#request_certificate' do
+ let(:private_key) do
+ OpenSSL::PKey::RSA.new(4096).to_pem
+ end
+
+ it 'generates csr and finalizes order' do
+ expect(acme_order).to receive(:finalize) do |csr:|
+ expect do
+ csr.csr # it's being evaluated lazily
+ end.not_to raise_error
+ end
+
+ order.request_certificate(domain: 'example.com', private_key: private_key)
+ end
+ end
end
diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
new file mode 100644
index 00000000000..18052b1991c
--- /dev/null
+++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::MarkdownCache::ActiveRecord::Extension do
+ let(:klass) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'issues'
+ include CacheMarkdownField
+ cache_markdown_field :title, whitelisted: true
+ cache_markdown_field :description, pipeline: :single_line
+
+ attr_accessor :author, :project
+ end
+ end
+
+ let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+
+ let(:markdown) { '`Foo`' }
+ let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' }
+
+ let(:updated_markdown) { '`Bar`' }
+ let(:updated_html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Bar</code></p>' }
+
+ context 'an unchanged markdown field' do
+ let(:thing) { klass.new(title: markdown) }
+
+ before do
+ thing.title = thing.title
+ thing.save
+ end
+
+ it { expect(thing.title).to eq(markdown) }
+ it { expect(thing.title_html).to eq(html) }
+ it { expect(thing.title_html_changed?).not_to be_truthy }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
+ end
+
+ context 'a changed markdown field' do
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+
+ before do
+ thing.title = updated_markdown
+ thing.save
+ end
+
+ it { expect(thing.title_html).to eq(updated_html) }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
+ end
+
+ context 'when a markdown field is set repeatedly to an empty string' do
+ it do
+ expect(thing).to receive(:refresh_markdown_cache).once
+ thing.title = ''
+ thing.save
+ thing.title = ''
+ thing.save
+ end
+ end
+
+ context 'when a markdown field is set repeatedly to a string which renders as empty html' do
+ it do
+ expect(thing).to receive(:refresh_markdown_cache).once
+ thing.title = '[//]: # (This is also a comment.)'
+ thing.save
+ thing.title = '[//]: # (This is also a comment.)'
+ thing.save
+ end
+ end
+
+ context 'a non-markdown field changed' do
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+
+ before do
+ thing.state = 'closed'
+ thing.save
+ end
+
+ it { expect(thing.state).to eq('closed') }
+ it { expect(thing.title).to eq(markdown) }
+ it { expect(thing.title_html).to eq(html) }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
+ end
+
+ context 'version is out of date' do
+ let(:thing) { klass.new(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
+
+ before do
+ thing.save
+ end
+
+ it { expect(thing.title_html).to eq(updated_html) }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
+ end
+
+ context 'when an invalidating field is changed' do
+ it 'invalidates the cache when project changes' do
+ thing.project = :new_project
+ allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
+
+ thing.save
+
+ expect(thing.title_html).to eq(updated_html)
+ expect(thing.description_html).to eq(updated_html)
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+
+ it 'invalidates the cache when author changes' do
+ thing.author = :new_author
+ allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
+
+ thing.save
+
+ expect(thing.title_html).to eq(updated_html)
+ expect(thing.description_html).to eq(updated_html)
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+ end
+
+ describe '.attributes' do
+ it 'excludes cache attributes that is blacklisted by default' do
+ expect(thing.attributes.keys.sort).not_to include(%w[description_html])
+ end
+ end
+
+ describe '#cached_html_up_to_date?' do
+ let(:thing) { klass.create(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
+ subject { thing.cached_html_up_to_date?(:title) }
+
+ it 'returns false if markdown has been changed but html has not' do
+ thing.title = "changed!"
+
+ is_expected.to be_falsy
+ end
+
+ it 'returns true if markdown has not been changed but html has' do
+ thing.title_html = updated_html
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns true if markdown and html have both been changed' do
+ thing.title = updated_markdown
+ thing.title_html = updated_html
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns false if the markdown field is set but the html is not' do
+ thing.title_html = nil
+
+ is_expected.to be_falsy
+ end
+ end
+
+ describe '#refresh_markdown_cache!' do
+ before do
+ thing.title = updated_markdown
+ end
+
+ it 'skips saving if not persisted' do
+ expect(thing).to receive(:persisted?).and_return(false)
+ expect(thing).not_to receive(:update_columns)
+
+ thing.refresh_markdown_cache!
+ end
+
+ it 'saves the changes' do
+ expect(thing).to receive(:persisted?).and_return(true)
+
+ expect(thing).to receive(:update_columns)
+ .with("title_html" => updated_html,
+ "description_html" => "",
+ "cached_markdown_version" => cache_version)
+
+ thing.refresh_markdown_cache!
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown_cache/field_data_spec.rb b/spec/lib/gitlab/markdown_cache/field_data_spec.rb
new file mode 100644
index 00000000000..393bf85aa43
--- /dev/null
+++ b/spec/lib/gitlab/markdown_cache/field_data_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::MarkdownCache::FieldData do
+ subject(:field_data) { described_class.new }
+
+ before do
+ field_data[:description] = { project: double('project in context') }
+ end
+
+ it 'translates a markdown field name into a html field name' do
+ expect(field_data.html_field(:description)).to eq("description_html")
+ end
+end
diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
new file mode 100644
index 00000000000..b6a781de426
--- /dev/null
+++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cache do
+ let(:klass) do
+ Class.new do
+ include CacheMarkdownField
+
+ def initialize(title: nil, description: nil)
+ @title, @description = title, description
+ end
+
+ attr_reader :title, :description
+
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :description
+
+ def cache_key
+ "cache-key"
+ end
+ end
+ end
+
+ let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
+ let(:thing) { klass.new(title: "`Hello`", description: "`World`") }
+ let(:expected_cache_key) { "markdown_cache:cache-key" }
+
+ it 'defines the html attributes' do
+ expect(thing).to respond_to(:title_html, :description_html, :cached_markdown_version)
+ end
+
+ it 'loads the markdown from the cache only once' do
+ expect(thing).to receive(:load_cached_markdown).once.and_call_original
+
+ thing.title_html
+ thing.description_html
+ end
+
+ it 'correctly loads the markdown if it was stored in redis' do
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmset(expected_cache_key,
+ title_html: 'hello',
+ description_html: 'world',
+ cached_markdown_version: cache_version)
+ end
+
+ expect(thing.title_html).to eq('hello')
+ expect(thing.description_html).to eq('world')
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+
+ describe "#refresh_markdown_cache!" do
+ it "stores the value in redis" do
+ expected_results = { "title_html" => "`Hello`",
+ "description_html" => "<p data-sourcepos=\"1:1-1:7\" dir=\"auto\"><code>World</code></p>",
+ "cached_markdown_version" => cache_version.to_s }
+
+ thing.refresh_markdown_cache!
+
+ results = Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmget(expected_cache_key,
+ "title_html", "description_html", "cached_markdown_version")
+ end
+
+ expect(results).to eq(expected_results)
+ end
+
+ it "assigns the values" do
+ thing.refresh_markdown_cache!
+
+ expect(thing.title_html).to eq('`Hello`')
+ expect(thing.description_html).to eq("<p data-sourcepos=\"1:1-1:7\" dir=\"auto\"><code>World</code></p>")
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
new file mode 100644
index 00000000000..95c68e7d491
--- /dev/null
+++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
+ let(:storable_class) do
+ Class.new do
+ cattr_reader :cached_markdown_fields do
+ Gitlab::MarkdownCache::FieldData.new.tap do |field_data|
+ field_data[:field_1] = {}
+ field_data[:field_2] = {}
+ end
+ end
+
+ attr_accessor :field_1, :field_2, :field_1_html, :field_2_html, :cached_markdown_version
+
+ def cache_key
+ "cache-key"
+ end
+ end
+ end
+ let(:storable) { storable_class.new }
+ let(:cache_key) { "markdown_cache:#{storable.cache_key}" }
+
+ subject(:store) { described_class.new(storable) }
+
+ def read_values
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmget(cache_key,
+ :field_1_html, :field_2_html, :cached_markdown_version)
+ end
+ end
+
+ def store_values(values)
+ Gitlab::Redis::Cache.with do |r|
+ r.mapped_hmset(cache_key,
+ values)
+ end
+ end
+
+ describe '#save' do
+ it 'stores updates to html fields and version' do
+ values_to_store = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 }
+
+ store.save(values_to_store)
+
+ expect(read_values)
+ .to eq({ field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1" })
+ end
+ end
+
+ describe '#read' do
+ it 'reads the html fields and version from redis if they were stored' do
+ stored_values = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 }
+
+ store_values(stored_values)
+
+ expect(store.read.symbolize_keys).to eq(stored_values)
+ end
+
+ it 'is mared loaded after reading' do
+ expect(store).not_to be_loaded
+
+ store.read
+
+ expect(store).to be_loaded
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
index e88eb140b35..bdcb5914575 100644
--- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
include MetricsDashboardHelpers
set(:project) { build(:project) }
- set(:environment) { build(:environment, project: project) }
+ set(:environment) { create(:environment, project: project) }
let(:system_dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH}
describe '.find' do
@@ -27,6 +27,13 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
end
+ context 'when the dashboard contains a metric without a query' do
+ let(:dashboard) { { 'panel_groups' => [{ 'panels' => [{ 'metrics' => [{ 'id' => 'mock' }] }] }] } }
+ let(:project) { project_with_dashboard(dashboard_path, dashboard.to_yaml) }
+
+ it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
+ end
+
context 'when the system dashboard is specified' do
let(:dashboard_path) { system_dashboard_path }
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index be3c1095bd7..797d4daabe3 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -4,13 +4,19 @@ require 'spec_helper'
describe Gitlab::Metrics::Dashboard::Processor do
let(:project) { build(:project) }
- let(:environment) { build(:environment, project: project) }
+ let(:environment) { create(:environment, project: project) }
let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
describe 'process' do
let(:process_params) { [project, environment, dashboard_yml] }
let(:dashboard) { described_class.new(*process_params).process(insert_project_metrics: true) }
+ it 'includes a path for the prometheus endpoint with each metric' do
+ expect(all_metrics).to satisfy_all do |metric|
+ metric[:prometheus_endpoint_path] == prometheus_path(metric[:query_range])
+ end
+ end
+
context 'when dashboard config corresponds to common metrics' do
let!(:common_metric) { create(:prometheus_metric, :common, identifier: 'metric_a1') }
@@ -61,7 +67,7 @@ describe Gitlab::Metrics::Dashboard::Processor do
shared_examples_for 'errors with message' do |expected_message|
it 'raises a DashboardLayoutError' do
- error_class = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardLayoutError
+ error_class = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError
expect { dashboard }.to raise_error(error_class, expected_message)
end
@@ -84,6 +90,12 @@ describe Gitlab::Metrics::Dashboard::Processor do
it_behaves_like 'errors with message', 'Each "panel" must define an array :metrics'
end
+
+ context 'when the dashboard contains a metric which is missing a query' do
+ let(:dashboard_yml) { { panel_groups: [{ panels: [{ metrics: [{}] }] }] } }
+
+ it_behaves_like 'errors with message', 'Each "metric" must define one of :query or :query_range'
+ end
end
private
@@ -99,7 +111,17 @@ describe Gitlab::Metrics::Dashboard::Processor do
query_range: metric.query,
unit: metric.unit,
label: metric.legend,
- metric_id: metric.id
+ metric_id: metric.id,
+ prometheus_endpoint_path: prometheus_path(metric.query)
}
end
+
+ def prometheus_path(query)
+ Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
+ project,
+ environment,
+ proxy_path: :query_range,
+ query: query
+ )
+ end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
index 162beb0268a..794ac5f109b 100644
--- a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
@@ -7,7 +7,7 @@ describe Gitlab::Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_m
set(:user) { build(:user) }
set(:project) { build(:project) }
- set(:environment) { build(:environment, project: project) }
+ set(:environment) { create(:environment, project: project) }
before do
project.add_maintainer(user)
diff --git a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
index e71ce2481a3..2648fe990de 100644
--- a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Metrics::Dashboard::SystemDashboardService, :use_clean_rails_me
include MetricsDashboardHelpers
set(:project) { build(:project) }
- set(:environment) { build(:environment, project: project) }
+ set(:environment) { create(:environment, project: project) }
describe 'get_dashboard' do
let(:dashboard_path) { described_class::SYSTEM_DASHBOARD_PATH }
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
new file mode 100644
index 00000000000..f4a6e1fc7d9
--- /dev/null
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Samplers::PumaSampler do
+ subject { described_class.new(5) }
+ let(:null_metric) { double('null_metric', set: nil, observe: nil) }
+
+ before do
+ allow(Gitlab::Metrics::NullMetric).to receive(:instance).and_return(null_metric)
+ end
+
+ describe '#sample' do
+ before do
+ expect(subject).to receive(:puma_stats).and_return(puma_stats)
+ end
+
+ context 'in cluster mode' do
+ let(:puma_stats) do
+ <<~EOS
+ {
+ "workers": 2,
+ "phase": 2,
+ "booted_workers": 2,
+ "old_workers": 0,
+ "worker_status": [{
+ "pid": 32534,
+ "index": 0,
+ "phase": 1,
+ "booted": true,
+ "last_checkin": "2019-05-15T07:57:55Z",
+ "last_status": {
+ "backlog":0,
+ "running":1,
+ "pool_capacity":4,
+ "max_threads": 4
+ }
+ }]
+ }
+ EOS
+ end
+
+ it 'samples master statistics' do
+ labels = { worker: 'master' }
+
+ expect(subject.metrics[:puma_workers]).to receive(:set).with(labels, 2)
+ expect(subject.metrics[:puma_running_workers]).to receive(:set).with(labels, 2)
+ expect(subject.metrics[:puma_stale_workers]).to receive(:set).with(labels, 0)
+ expect(subject.metrics[:puma_phase]).to receive(:set).once.with(labels, 2)
+ expect(subject.metrics[:puma_phase]).to receive(:set).once.with({ worker: 'worker_0' }, 1)
+
+ subject.sample
+ end
+
+ it 'samples worker statistics' do
+ labels = { worker: 'worker_0' }
+
+ expect_worker_stats(labels)
+
+ subject.sample
+ end
+ end
+
+ context 'with empty worker stats' do
+ let(:puma_stats) do
+ <<~EOS
+ {
+ "workers": 2,
+ "phase": 2,
+ "booted_workers": 2,
+ "old_workers": 0,
+ "worker_status": [{
+ "pid": 32534,
+ "index": 0,
+ "phase": 1,
+ "booted": true,
+ "last_checkin": "2019-05-15T07:57:55Z",
+ "last_status": {}
+ }]
+ }
+ EOS
+ end
+
+ it 'does not log worker stats' do
+ expect(subject).not_to receive(:set_worker_metrics)
+
+ subject.sample
+ end
+ end
+
+ context 'in single mode' do
+ let(:puma_stats) do
+ <<~EOS
+ {
+ "backlog":0,
+ "running":1,
+ "pool_capacity":4,
+ "max_threads": 4
+ }
+ EOS
+ end
+
+ it 'samples worker statistics' do
+ labels = {}
+
+ expect(subject.metrics[:puma_workers]).to receive(:set).with(labels, 1)
+ expect(subject.metrics[:puma_running_workers]).to receive(:set).with(labels, 1)
+ expect_worker_stats(labels)
+
+ subject.sample
+ end
+ end
+ end
+
+ def expect_worker_stats(labels)
+ expect(subject.metrics[:puma_queued_connections]).to receive(:set).with(labels, 0)
+ expect(subject.metrics[:puma_active_connections]).to receive(:set).with(labels, 0)
+ expect(subject.metrics[:puma_running]).to receive(:set).with(labels, 1)
+ expect(subject.metrics[:puma_pool_capacity]).to receive(:set).with(labels, 4)
+ expect(subject.metrics[:puma_max_threads]).to receive(:set).with(labels, 4)
+ expect(subject.metrics[:puma_idle_threads]).to receive(:set).with(labels, 1)
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 7972ff253fe..aaf8c9fa2a0 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -10,17 +10,20 @@ describe Gitlab::Metrics::Samplers::RubySampler do
describe '#sample' do
it 'samples various statistics' do
- expect(Gitlab::Metrics::System).to receive(:memory_usage)
+ expect(Gitlab::Metrics::System).to receive(:cpu_time)
expect(Gitlab::Metrics::System).to receive(:file_descriptor_count)
+ expect(Gitlab::Metrics::System).to receive(:memory_usage)
+ expect(Gitlab::Metrics::System).to receive(:process_start_time)
+ expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors)
expect(sampler).to receive(:sample_gc)
sampler.sample
end
- it 'adds a metric containing the memory usage' do
+ it 'adds a metric containing the process resident memory bytes' do
expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000)
- expect(sampler.metrics[:memory_usage]).to receive(:set).with({}, 9000)
+ expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000)
sampler.sample
end
@@ -34,6 +37,27 @@ describe Gitlab::Metrics::Samplers::RubySampler do
sampler.sample
end
+ it 'adds a metric containing the process total cpu time' do
+ expect(Gitlab::Metrics::System).to receive(:cpu_time).and_return(0.51)
+ expect(sampler.metrics[:process_cpu_seconds_total]).to receive(:set).with({}, 0.51)
+
+ sampler.sample
+ end
+
+ it 'adds a metric containing the process start time' do
+ expect(Gitlab::Metrics::System).to receive(:process_start_time).and_return(12345)
+ expect(sampler.metrics[:process_start_time_seconds]).to receive(:set).with({}, 12345)
+
+ sampler.sample
+ end
+
+ it 'adds a metric containing the process max file descriptors' do
+ expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors).and_return(1024)
+ expect(sampler.metrics[:process_max_fds]).to receive(:set).with({}, 1024)
+
+ sampler.sample
+ end
+
it 'clears any GC profiles' do
expect(GC::Profiler).to receive(:clear)
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index 4b03f3c2532..090e456644f 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -39,8 +39,8 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
it 'updates metrics type unix and with addr' do
labels = { socket_type: 'unix', socket_address: socket_address }
- expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
- expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+ expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active')
+ expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued')
subject.sample
end
@@ -50,7 +50,6 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
context 'unicorn listens on tcp sockets' do
let(:tcp_socket_address) { '0.0.0.0:8080' }
let(:tcp_sockets) { [tcp_socket_address] }
-
before do
allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
end
@@ -71,13 +70,29 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
it 'updates metrics type unix and with addr' do
labels = { socket_type: 'tcp', socket_address: tcp_socket_address }
- expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
- expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+ expect(subject.metrics[:unicorn_active_connections]).to receive(:set).with(labels, 'active')
+ expect(subject.metrics[:unicorn_queued_connections]).to receive(:set).with(labels, 'queued')
subject.sample
end
end
end
+
+ context 'additional metrics' do
+ let(:unicorn_workers) { 2 }
+
+ before do
+ allow(unicorn).to receive(:listener_names).and_return([""])
+ allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(3.14)
+ allow(subject).to receive(:unicorn_workers_count).and_return(unicorn_workers)
+ end
+
+ it "sets additional metrics" do
+ expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, unicorn_workers)
+
+ subject.sample
+ end
+ end
end
describe '#start' do
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index 14afcdf5daa..b0603d96eb2 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -13,6 +13,18 @@ describe Gitlab::Metrics::System do
expect(described_class.file_descriptor_count).to be > 0
end
end
+
+ describe '.max_open_file_descriptors' do
+ it 'returns the max allowed open file descriptors' do
+ expect(described_class.max_open_file_descriptors).to be > 0
+ end
+ end
+
+ describe '.process_start_time' do
+ it 'returns the process start time' do
+ expect(described_class.process_start_time).to be > 0
+ end
+ end
else
describe '.memory_usage' do
it 'returns 0.0' do
@@ -25,6 +37,18 @@ describe Gitlab::Metrics::System do
expect(described_class.file_descriptor_count).to eq(0)
end
end
+
+ describe '.max_open_file_descriptors' do
+ it 'returns 0' do
+ expect(described_class.max_open_file_descriptors).to eq(0)
+ end
+ end
+
+ describe 'process_start_time' do
+ it 'returns 0' do
+ expect(described_class.process_start_time).to eq(0)
+ end
+ end
end
describe '.cpu_time' do
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index d808b4d49e0..32296caf819 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -38,6 +38,28 @@ describe Gitlab::OmniauthInitializer do
subject.execute([hash_config])
end
+ it 'normalizes a String strategy_class' do
+ hash_config = { 'name' => 'hash', 'args' => { strategy_class: 'OmniAuth::Strategies::OAuth2Generic' } }
+
+ expect(devise_config).to receive(:omniauth).with(:hash, strategy_class: OmniAuth::Strategies::OAuth2Generic)
+
+ subject.execute([hash_config])
+ end
+
+ it 'allows a class to be specified in strategy_class' do
+ hash_config = { 'name' => 'hash', 'args' => { strategy_class: OmniAuth::Strategies::OAuth2Generic } }
+
+ expect(devise_config).to receive(:omniauth).with(:hash, strategy_class: OmniAuth::Strategies::OAuth2Generic)
+
+ subject.execute([hash_config])
+ end
+
+ it 'throws an error for an invalid strategy_class' do
+ hash_config = { 'name' => 'hash', 'args' => { strategy_class: 'OmniAuth::Strategies::Bogus' } }
+
+ expect { subject.execute([hash_config]) }.to raise_error(NameError)
+ end
+
it 'configures fail_with_empty_uid for shibboleth' do
shibboleth_config = { 'name' => 'shibboleth', 'args' => {} }
@@ -61,5 +83,13 @@ describe Gitlab::OmniauthInitializer do
subject.execute([cas3_config])
end
+
+ it 'configures name for openid_connect' do
+ openid_connect_config = { 'name' => 'openid_connect', 'args' => {} }
+
+ expect(devise_config).to receive(:omniauth).with(:openid_connect, name: 'openid_connect')
+
+ subject.execute([openid_connect_config])
+ end
end
end
diff --git a/spec/lib/gitlab/phabricator_import/base_worker_spec.rb b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb
new file mode 100644
index 00000000000..d46d908a3e3
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/base_worker_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::BaseWorker do
+ let(:subclass) do
+ # Creating an anonymous class for a worker is complicated, as we generate the
+ # queue name from the class name.
+ Gitlab::PhabricatorImport::ImportTasksWorker
+ end
+
+ describe '.schedule' do
+ let(:arguments) { %w[project_id the_next_page] }
+
+ it 'schedules the job' do
+ expect(subclass).to receive(:perform_async).with(*arguments)
+
+ subclass.schedule(*arguments)
+ end
+
+ it 'counts the scheduled job', :clean_gitlab_redis_shared_state do
+ state = Gitlab::PhabricatorImport::WorkerState.new('project_id')
+
+ allow(subclass).to receive(:remove_job) # otherwise the job is removed before we saw it
+
+ expect { subclass.schedule(*arguments) }.to change { state.running_count }.by(1)
+ end
+ end
+
+ describe '#perform' do
+ let(:project) { create(:project, :import_started, import_url: "https://a.phab.instance") }
+ let(:worker) { subclass.new }
+ let(:state) { Gitlab::PhabricatorImport::WorkerState.new(project.id) }
+
+ before do
+ allow(worker).to receive(:import)
+ end
+
+ it 'does not break for a non-existing project' do
+ expect { worker.perform('not a thing') }.not_to raise_error
+ end
+
+ it 'does not do anything when the import is not in progress' do
+ project = create(:project, :import_failed)
+
+ expect(worker).not_to receive(:import)
+
+ worker.perform(project.id)
+ end
+
+ it 'calls import for the project' do
+ expect(worker).to receive(:import).with(project, 'other_arg')
+
+ worker.perform(project.id, 'other_arg')
+ end
+
+ it 'marks the project as imported if there was only one job running' do
+ worker.perform(project.id)
+
+ expect(project.import_state.reload).to be_finished
+ end
+
+ it 'does not mark the job as finished when there are more scheduled jobs' do
+ 2.times { state.add_job }
+
+ worker.perform(project.id)
+
+ expect(project.import_state.reload).to be_in_progress
+ end
+
+ it 'decrements the job counter' do
+ expect { worker.perform(project.id) }.to change { state.running_count }.by(-1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
new file mode 100644
index 00000000000..52c7a02219f
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
+ set(:project) { create(:project) }
+ let(:redis) { Gitlab::Redis::Cache }
+ subject(:map) { described_class.new(project) }
+
+ describe '#get_gitlab_model' do
+ it 'returns nil if there was nothing cached for the phabricator id' do
+ expect(map.get_gitlab_model('does not exist')).to be_nil
+ end
+
+ it 'returns the object if it was set in redis' do
+ issue = create(:issue, project: project)
+ set_in_redis('exists', issue)
+
+ expect(map.get_gitlab_model('exists')).to eq(issue)
+ end
+
+ it 'extends the TTL for the cache key' do
+ set_in_redis('extend', create(:issue, project: project)) do |redis|
+ redis.expire(cache_key('extend'), 10.seconds.to_i)
+ end
+
+ map.get_gitlab_model('extend')
+
+ ttl = redis.with { |redis| redis.ttl(cache_key('extend')) }
+
+ expect(ttl).to be > 10.seconds
+ end
+ end
+
+ describe '#set_gitlab_model' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'sets the class and id in redis with a ttl' do
+ issue = create(:issue, project: project)
+
+ map.set_gitlab_model(issue, 'it is set')
+
+ set_data, ttl = redis.with do |redis|
+ redis.pipelined do |p|
+ p.mapped_hmget(cache_key('it is set'), :classname, :database_id)
+ p.ttl(cache_key('it is set'))
+ end
+ end
+
+ expect(set_data).to eq({ classname: 'Issue', database_id: issue.id.to_s })
+ expect(ttl).to be_within(1.second).of(StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
+ end
+ end
+
+ def set_in_redis(key, object)
+ redis.with do |redis|
+ redis.mapped_hmset(cache_key(key),
+ { classname: object.class, database_id: object.id })
+ yield(redis) if block_given?
+ end
+ end
+
+ def cache_key(phabricator_id)
+ subject.__send__(:cache_key_for_phabricator_id, phabricator_id)
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
new file mode 100644
index 00000000000..542b3cd060f
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/client_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::Client do
+ let(:client) do
+ described_class.new('https://see-ya-later.phabricator', 'api-token')
+ end
+
+ describe '#get' do
+ it 'performs and parses a request' do
+ params = { some: 'extra', values: %w[are passed] }
+ stub_valid_request(params)
+
+ response = client.get('test', params: params)
+
+ expect(response).to be_a(Gitlab::PhabricatorImport::Conduit::Response)
+ expect(response).to be_success
+ end
+
+ it 'wraps request errors in an `ApiError`' do
+ stub_timeout
+
+ expect { client.get('test') }.to raise_error(Gitlab::PhabricatorImport::Conduit::ApiError)
+ end
+
+ it 'raises response error' do
+ stub_error_response
+
+ expect { client.get('test') }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /has the wrong length/)
+ end
+ end
+
+ def stub_valid_request(params = {})
+ WebMock.stub_request(
+ :get, 'https://see-ya-later.phabricator/api/test'
+ ).with(
+ body: CGI.unescape(params.reverse_merge('api.token' => 'api-token').to_query)
+ ).and_return(
+ status: 200,
+ body: fixture_file('phabricator_responses/maniphest.search.json')
+ )
+ end
+
+ def stub_timeout
+ WebMock.stub_request(
+ :get, 'https://see-ya-later.phabricator/api/test'
+ ).to_timeout
+ end
+
+ def stub_error_response
+ WebMock.stub_request(
+ :get, 'https://see-ya-later.phabricator/api/test'
+ ).and_return(
+ status: 200,
+ body: fixture_file('phabricator_responses/auth_failed.json')
+ )
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
new file mode 100644
index 00000000000..0d7714649b9
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/maniphest_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::Maniphest do
+ let(:maniphest) do
+ described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token')
+ end
+
+ describe '#tasks' do
+ let(:fake_client) { double('Phabricator client') }
+
+ before do
+ allow(maniphest).to receive(:client).and_return(fake_client)
+ end
+
+ it 'calls the api with the correct params' do
+ expected_params = {
+ after: '123',
+ attachments: {
+ projects: 1, subscribers: 1, columns: 1
+ }
+ }
+
+ expect(fake_client).to receive(:get).with('maniphest.search',
+ params: expected_params)
+
+ maniphest.tasks(after: '123')
+ end
+
+ it 'returns a parsed response' do
+ response = Gitlab::PhabricatorImport::Conduit::Response
+ .new(fixture_file('phabricator_responses/maniphest.search.json'))
+
+ allow(fake_client).to receive(:get).and_return(response)
+
+ expect(maniphest.tasks).to be_a(Gitlab::PhabricatorImport::Conduit::TasksResponse)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
new file mode 100644
index 00000000000..a8596968f14
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::Response do
+ let(:response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))}
+ let(:error_response) { described_class.new(JSON.parse(fixture_file('phabricator_responses/auth_failed.json'))) }
+
+ describe '.parse!' do
+ it 'raises a ResponseError if the http response was not successfull' do
+ fake_response = double(:http_response, success?: false, status: 401)
+
+ expect { described_class.parse!(fake_response) }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /responded with 401/)
+ end
+
+ it 'raises a ResponseError if the response contained a Phabricator error' do
+ fake_response = double(:http_response,
+ success?: true,
+ status: 200,
+ body: fixture_file('phabricator_responses/auth_failed.json'))
+
+ expect { described_class.parse!(fake_response) }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /ERR-INVALID-AUTH: API token/)
+ end
+
+ it 'raises a ResponseError if JSON parsing failed' do
+ fake_response = double(:http_response,
+ success?: true,
+ status: 200,
+ body: 'This is no JSON')
+
+ expect { described_class.parse!(fake_response) }
+ .to raise_error(Gitlab::PhabricatorImport::Conduit::ResponseError, /unexpected token at/)
+ end
+
+ it 'returns a parsed response for valid input' do
+ fake_response = double(:http_response,
+ success?: true,
+ status: 200,
+ body: fixture_file('phabricator_responses/maniphest.search.json'))
+
+ expect(described_class.parse!(fake_response)).to be_a(described_class)
+ end
+ end
+
+ describe '#success?' do
+ it { expect(response).to be_success }
+ it { expect(error_response).not_to be_success }
+ end
+
+ describe '#error_code' do
+ it { expect(error_response.error_code).to eq('ERR-INVALID-AUTH') }
+ it { expect(response.error_code).to be_nil }
+ end
+
+ describe '#error_info' do
+ it 'returns the correct error info' do
+ expected_message = 'API token "api-token" has the wrong length. API tokens should be 32 characters long.'
+
+ expect(error_response.error_info).to eq(expected_message)
+ end
+
+ it { expect(response.error_info).to be_nil }
+ end
+
+ describe '#data' do
+ it { expect(error_response.data).to be_nil }
+ it { expect(response.data).to be_an(Array) }
+ end
+
+ describe '#pagination' do
+ it { expect(error_response.pagination).to be_nil }
+
+ it 'builds the pagination correctly' do
+ expect(response.pagination).to be_a(Gitlab::PhabricatorImport::Conduit::Pagination)
+ expect(response.pagination.next_page).to eq('284')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
new file mode 100644
index 00000000000..4b4c2a6276e
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/tasks_response_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::TasksResponse do
+ let(:conduit_response) do
+ Gitlab::PhabricatorImport::Conduit::Response
+ .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))
+ end
+
+ subject(:response) { described_class.new(conduit_response) }
+
+ describe '#pagination' do
+ it 'delegates to the conduit reponse' do
+ expect(response.pagination).to eq(conduit_response.pagination)
+ end
+ end
+
+ describe '#tasks' do
+ it 'builds the correct tasks representation' do
+ tasks = response.tasks
+
+ titles = tasks.map(&:issue_attributes).map { |attrs| attrs[:title] }
+
+ expect(titles).to contain_exactly('Things are slow', 'Things are broken')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb
new file mode 100644
index 00000000000..1e38ef8aaa5
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/import_tasks_worker_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::ImportTasksWorker do
+ describe '#perform' do
+ it 'calls the correct importer' do
+ project = create(:project, :import_started, import_url: "https://the.phab.ulr")
+ fake_importer = instance_double(Gitlab::PhabricatorImport::Issues::Importer)
+
+ expect(Gitlab::PhabricatorImport::Issues::Importer).to receive(:new).with(project).and_return(fake_importer)
+ expect(fake_importer).to receive(:execute)
+
+ described_class.new.perform(project.id)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb
new file mode 100644
index 00000000000..bf14010a187
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Importer do
+ it { expect(described_class).to be_async }
+
+ it "acts like it's importing repositories" do
+ expect(described_class).to be_imports_repository
+ end
+
+ describe '#execute' do
+ let(:project) { create(:project, :import_scheduled) }
+ subject(:importer) { described_class.new(project) }
+
+ it 'sets a custom jid that will be kept up to date' do
+ expect { importer.execute }.to change { project.import_state.reload.jid }
+ end
+
+ it 'starts importing tasks' do
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker).to receive(:schedule).with(project.id)
+
+ importer.execute
+ end
+
+ it 'marks the import as failed when something goes wrong' do
+ allow(importer).to receive(:schedule_first_tasks_page).and_raise('Stuff is broken')
+
+ importer.execute
+
+ expect(project.import_state).to be_failed
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
new file mode 100644
index 00000000000..2412cf76f79
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Issues::Importer do
+ set(:project) { create(:project) }
+
+ let(:response) do
+ Gitlab::PhabricatorImport::Conduit::TasksResponse.new(
+ Gitlab::PhabricatorImport::Conduit::Response
+ .new(JSON.parse(fixture_file('phabricator_responses/maniphest.search.json')))
+ )
+ end
+
+ subject(:importer) { described_class.new(project, nil) }
+
+ before do
+ client = instance_double(Gitlab::PhabricatorImport::Conduit::Maniphest)
+
+ allow(client).to receive(:tasks).and_return(response)
+ allow(importer).to receive(:client).and_return(client)
+ end
+
+ describe '#execute' do
+ it 'imports each task in the response' do
+ response.tasks.each do |task|
+ task_importer = instance_double(Gitlab::PhabricatorImport::Issues::TaskImporter)
+
+ expect(task_importer).to receive(:execute)
+ expect(Gitlab::PhabricatorImport::Issues::TaskImporter)
+ .to receive(:new).with(project, task)
+ .and_return(task_importer)
+ end
+
+ importer.execute
+ end
+
+ it 'schedules the next batch if there is one' do
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .to receive(:schedule).with(project.id, response.pagination.next_page)
+
+ importer.execute
+ end
+
+ it 'does not reschedule when there is no next page' do
+ allow(response.pagination).to receive(:has_next_page?).and_return(false)
+
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .not_to receive(:schedule)
+
+ importer.execute
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
new file mode 100644
index 00000000000..1625604e754
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Issues::TaskImporter do
+ set(:project) { create(:project) }
+ let(:task) do
+ Gitlab::PhabricatorImport::Representation::Task.new(
+ {
+ 'phid' => 'the-phid',
+ 'fields' => {
+ 'name' => 'Title',
+ 'description' => {
+ 'raw' => '# This is markdown\n it can contain more text.'
+ },
+ 'dateCreated' => '1518688921',
+ 'dateClosed' => '1518789995'
+ }
+ }
+ )
+ end
+
+ describe '#execute' do
+ it 'creates the issue with the expected attributes' do
+ issue = described_class.new(project, task).execute
+
+ expect(issue.project).to eq(project)
+ expect(issue).to be_persisted
+ expect(issue.author).to eq(User.ghost)
+ expect(issue.title).to eq('Title')
+ expect(issue.description).to eq('# This is markdown\n it can contain more text.')
+ expect(issue).to be_closed
+ expect(issue.created_at).to eq(Time.at(1518688921))
+ expect(issue.closed_at).to eq(Time.at(1518789995))
+ end
+
+ it 'does not recreate the issue when called multiple times' do
+ expect { described_class.new(project, task).execute }
+ .to change { project.issues.reload.size }.from(0).to(1)
+ expect { described_class.new(project, task).execute }
+ .not_to change { project.issues.reload.size }
+ end
+
+ it 'does not trigger a save when the object did not change' do
+ existing_issue = create(:issue,
+ task.issue_attributes.merge(author: User.ghost))
+ importer = described_class.new(project, task)
+ allow(importer).to receive(:issue).and_return(existing_issue)
+
+ expect(existing_issue).not_to receive(:save!)
+
+ importer.execute
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/project_creator_spec.rb b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
new file mode 100644
index 00000000000..e9455b866ac
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/project_creator_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::ProjectCreator do
+ let(:user) { create(:user) }
+ let(:params) do
+ { path: 'new-phab-import',
+ phabricator_server_url: 'http://phab.example.com',
+ api_token: 'the-token' }
+ end
+ subject(:creator) { described_class.new(user, params) }
+
+ describe '#execute' do
+ it 'creates a project correctly and schedule an import' do
+ expect_next_instance_of(Gitlab::PhabricatorImport::Importer) do |importer|
+ expect(importer).to receive(:execute)
+ end
+
+ project = creator.execute
+
+ expect(project).to be_persisted
+ expect(project).to be_import
+ expect(project.import_type).to eq('phabricator')
+ expect(project.import_data.credentials).to match(a_hash_including(api_token: 'the-token'))
+ expect(project.import_data.data).to match(a_hash_including('phabricator_url' => 'http://phab.example.com'))
+ expect(project.import_url).to eq(Project::UNKNOWN_IMPORT_URL)
+ expect(project.namespace).to eq(user.namespace)
+ end
+
+ context 'when import params are missing' do
+ let(:params) do
+ { path: 'new-phab-import',
+ phabricator_server_url: 'http://phab.example.com',
+ api_token: '' }
+ end
+
+ it 'returns nil' do
+ expect(creator.execute).to be_nil
+ end
+ end
+
+ context 'when import params are invalid' do
+ let(:params) do
+ { path: 'new-phab-import',
+ namespace_id: '-1',
+ phabricator_server_url: 'http://phab.example.com',
+ api_token: 'the-token' }
+ end
+
+ it 'returns an unpersisted project' do
+ project = creator.execute
+
+ expect(project).not_to be_persisted
+ expect(project).not_to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
new file mode 100644
index 00000000000..dfbd8c546eb
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Representation::Task do
+ subject(:task) do
+ described_class.new(
+ {
+ 'phid' => 'the-phid',
+ 'fields' => {
+ 'name' => 'Title'.ljust(257, '.'), # A string padded to 257 chars
+ 'description' => {
+ 'raw' => '# This is markdown\n it can contain more text.'
+ },
+ 'dateCreated' => '1518688921',
+ 'dateClosed' => '1518789995'
+ }
+ }
+ )
+ end
+
+ describe '#issue_attributes' do
+ it 'contains the expected values' do
+ expected_attributes = {
+ title: 'Title'.ljust(255, '.'),
+ description: '# This is markdown\n it can contain more text.',
+ state: :closed,
+ created_at: Time.at(1518688921),
+ closed_at: Time.at(1518789995)
+ }
+
+ expect(task.issue_attributes).to eq(expected_attributes)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
new file mode 100644
index 00000000000..a44947445c9
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
+ subject(:state) { described_class.new('weird-project-id') }
+ let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' }
+
+ describe '#add_job' do
+ it 'increments the counter for jobs' do
+ set_value(3)
+
+ expect { state.add_job }.to change { get_value }.from('3').to('4')
+ end
+ end
+
+ describe '#remove_job' do
+ it 'decrements the counter for jobs' do
+ set_value(3)
+
+ expect { state.remove_job }.to change { get_value }.from('3').to('2')
+ end
+ end
+
+ describe '#running_count' do
+ it 'reads the value' do
+ set_value(9)
+
+ expect(state.running_count).to eq(9)
+ end
+
+ it 'returns 0 when nothing was set' do
+ expect(state.running_count).to eq(0)
+ end
+ end
+
+ def set_value(value)
+ redis.with { |r| r.set(key, value) }
+ end
+
+ def get_value
+ redis.with { |r| r.get(key) }
+ end
+
+ def redis
+ Gitlab::Redis::SharedState
+ end
+end
diff --git a/spec/lib/gitlab/rack_timeout_observer_spec.rb b/spec/lib/gitlab/rack_timeout_observer_spec.rb
new file mode 100644
index 00000000000..3dc1a8b68fb
--- /dev/null
+++ b/spec/lib/gitlab/rack_timeout_observer_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::RackTimeoutObserver do
+ let(:counter) { Gitlab::Metrics::NullMetric.instance }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter)
+ .with(any_args)
+ .and_return(counter)
+ end
+
+ describe '#callback' do
+ context 'when request times out' do
+ let(:env) do
+ {
+ ::Rack::Timeout::ENV_INFO_KEY => double(state: :timed_out),
+ 'action_dispatch.request.parameters' => {
+ 'controller' => 'foo',
+ 'action' => 'bar'
+ }
+ }
+ end
+
+ subject { described_class.new }
+
+ it 'increments timeout counter' do
+ expect(counter)
+ .to receive(:increment)
+ .with({ controller: 'foo', action: 'bar', route: nil, state: :timed_out })
+
+ subject.callback.call(env)
+ end
+ end
+
+ context 'when request expires' do
+ let(:endpoint) { double }
+ let(:env) do
+ {
+ ::Rack::Timeout::ENV_INFO_KEY => double(state: :expired),
+ Grape::Env::API_ENDPOINT => endpoint
+ }
+ end
+
+ subject { described_class.new }
+
+ it 'increments timeout counter' do
+ allow(endpoint).to receive_message_chain('route.pattern.origin') { 'foobar' }
+ expect(counter)
+ .to receive(:increment)
+ .with({ controller: nil, action: nil, route: 'foobar', state: :expired })
+
+ subject.callback.call(env)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 312aa3be490..3d27156b356 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -256,4 +256,28 @@ describe Gitlab::SearchResults do
expect(results.objects('merge_requests')).not_to include merge_request
end
+
+ context 'milestones' do
+ it 'returns correct set of milestones' do
+ private_project_1 = create(:project, :private)
+ private_project_2 = create(:project, :private)
+ internal_project = create(:project, :internal)
+ public_project_1 = create(:project, :public)
+ public_project_2 = create(:project, :public, :issues_disabled, :merge_requests_disabled)
+ private_project_1.add_developer(user)
+ # milestones that should not be visible
+ create(:milestone, project: private_project_2, title: 'Private project without access milestone')
+ create(:milestone, project: public_project_2, title: 'Public project with milestones disabled milestone')
+ # milestones that should be visible
+ milestone_1 = create(:milestone, project: private_project_1, title: 'Private project with access milestone', state: 'closed')
+ milestone_2 = create(:milestone, project: internal_project, title: 'Internal project milestone')
+ milestone_3 = create(:milestone, project: public_project_1, title: 'Public project with milestones enabled milestone')
+ # Global search scope takes user authorized projects, internal projects and public projects.
+ limit_projects = ProjectsFinder.new(current_user: user).execute
+
+ milestones = described_class.new(user, limit_projects, 'milestone').objects('milestones')
+
+ expect(milestones).to match_array([milestone_1, milestone_2, milestone_3])
+ end
+ end
end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index e2f09de2808..bce2e754176 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -612,16 +612,6 @@ describe Gitlab::Shell do
FileUtils.rm_rf(created_path)
end
- it 'creates a repository' do
- expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_truthy
-
- expect(File.stat(created_path).mode & 0o777).to eq(0o770)
-
- hooks_path = File.join(created_path, 'hooks')
- expect(File.lstat(hooks_path)).to be_symlink
- expect(File.realpath(hooks_path)).to eq(gitlab_shell_hooks_path)
- end
-
it 'returns false when the command fails' do
FileUtils.mkdir_p(File.dirname(created_path))
# This file will block the creation of the repo's .git directory. That
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index fe46c67a920..5f0a7e925ca 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -15,6 +15,13 @@ describe Gitlab::Template::GitlabCiYmlTemplate do
expect(all).to include('Docker')
expect(all).to include('Ruby')
end
+
+ it 'ensure that the template name is used exactly once' do
+ all = subject.all.group_by(&:name)
+ duplicates = all.select { |_, templates| templates.length > 1 }
+
+ expect(duplicates).to be_empty
+ end
end
describe '.find' do
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 445a56ab0d8..253366e0789 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -2,6 +2,87 @@
require 'spec_helper'
describe Gitlab::UrlBlocker do
+ describe '#validate!' do
+ context 'when URI is nil' do
+ let(:import_url) { nil }
+
+ it 'returns no URI and hostname' do
+ uri, hostname = described_class.validate!(import_url)
+
+ expect(uri).to be(nil)
+ expect(hostname).to be(nil)
+ end
+ end
+
+ context 'when URI is internal' do
+ let(:import_url) { 'http://localhost' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url)
+
+ expect(uri).to eq(Addressable::URI.parse('http://[::1]'))
+ expect(hostname).to eq('localhost')
+ end
+ end
+
+ context 'when the URL hostname is a domain' do
+ let(:import_url) { 'https://example.org' }
+
+ it 'returns URI and hostname' do
+ uri, hostname = described_class.validate!(import_url)
+
+ expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
+ expect(hostname).to eq('example.org')
+ end
+ end
+
+ context 'when the URL hostname is an IP address' do
+ let(:import_url) { 'https://93.184.216.34' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url)
+
+ expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
+ expect(hostname).to be(nil)
+ end
+ end
+
+ context 'disabled DNS rebinding protection' do
+ context 'when URI is internal' do
+ let(:import_url) { 'http://localhost' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
+
+ expect(uri).to eq(Addressable::URI.parse('http://localhost'))
+ expect(hostname).to be(nil)
+ end
+ end
+
+ context 'when the URL hostname is a domain' do
+ let(:import_url) { 'https://example.org' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
+
+ expect(uri).to eq(Addressable::URI.parse('https://example.org'))
+ expect(hostname).to eq(nil)
+ end
+ end
+
+ context 'when the URL hostname is an IP address' do
+ let(:import_url) { 'https://93.184.216.34' }
+
+ it 'returns URI and no hostname' do
+ uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
+
+ expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
+ expect(hostname).to be(nil)
+ end
+ end
+ end
+ end
+
describe '#blocked_url?' do
let(:ports) { Project::VALID_IMPORT_PORTS }
@@ -208,7 +289,7 @@ describe Gitlab::UrlBlocker do
end
def stub_domain_resolv(domain, ip)
- address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)
+ address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false, ipv4?: false)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 9f495a5d50b..bbcb92608d8 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::UrlBuilder do
url = described_class.build(milestone)
- expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/milestones/#{milestone.iid}"
+ expect(url).to eq "#{Settings.gitlab['url']}/#{milestone.project.full_path}/-/milestones/#{milestone.iid}"
end
end
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index 5861e6955a6..7242255d535 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -115,6 +115,40 @@ describe Gitlab::UrlSanitizer do
end
end
+ describe '#user' do
+ context 'credentials in hash' do
+ it 'overrides URL-provided user' do
+ sanitizer = described_class.new('http://a:b@example.com', credentials: { user: 'c', password: 'd' })
+
+ expect(sanitizer.user).to eq('c')
+ end
+ end
+
+ context 'credentials in URL' do
+ where(:url, :user) do
+ 'http://foo:bar@example.com' | 'foo'
+ 'http://foo:bar:baz@example.com' | 'foo'
+ 'http://:bar@example.com' | nil
+ 'http://foo:@example.com' | 'foo'
+ 'http://foo@example.com' | 'foo'
+ 'http://:@example.com' | nil
+ 'http://@example.com' | nil
+ 'http://example.com' | nil
+
+ # Other invalid URLs
+ nil | nil
+ '' | nil
+ 'no' | nil
+ end
+
+ with_them do
+ subject { described_class.new(url).user }
+
+ it { is_expected.to eq(user) }
+ end
+ end
+ end
+
describe '#full_url' do
context 'credentials in hash' do
where(:credentials, :userinfo) do
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 767b5779a79..e075904b0cc 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -109,4 +109,34 @@ describe Gitlab do
expect(described_class.ee?).to eq(false)
end
end
+
+ describe '.http_proxy_env?' do
+ it 'returns true when lower case https' do
+ stub_env('https_proxy', 'https://my.proxy')
+
+ expect(described_class.http_proxy_env?).to eq(true)
+ end
+
+ it 'returns true when upper case https' do
+ stub_env('HTTPS_PROXY', 'https://my.proxy')
+
+ expect(described_class.http_proxy_env?).to eq(true)
+ end
+
+ it 'returns true when lower case http' do
+ stub_env('http_proxy', 'http://my.proxy')
+
+ expect(described_class.http_proxy_env?).to eq(true)
+ end
+
+ it 'returns true when upper case http' do
+ stub_env('HTTP_PROXY', 'http://my.proxy')
+
+ expect(described_class.http_proxy_env?).to eq(true)
+ end
+
+ it 'returns false when not set' do
+ expect(described_class.http_proxy_env?).to eq(false)
+ end
+ end
end
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index 77fea5b2d24..346455067a7 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Mattermost::Session, type: :request do
include ExclusiveLeaseHelpers
+ include StubRequests
let(:user) { create(:user) }
@@ -24,7 +25,7 @@ describe Mattermost::Session, type: :request do
let(:location) { 'http://location.tld' }
let(:cookie_header) {'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;'}
let!(:stub) do
- WebMock.stub_request(:get, "#{mattermost_url}/oauth/gitlab/login")
+ stub_full_request("#{mattermost_url}/oauth/gitlab/login")
.to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 302)
end
@@ -63,7 +64,7 @@ describe Mattermost::Session, type: :request do
end
before do
- WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete")
+ stub_full_request("#{mattermost_url}/signup/gitlab/complete")
.with(query: hash_including({ 'state' => state }))
.to_return do |request|
post "/oauth/token",
@@ -80,7 +81,7 @@ describe Mattermost::Session, type: :request do
end
end
- WebMock.stub_request(:post, "#{mattermost_url}/api/v4/users/logout")
+ stub_full_request("#{mattermost_url}/api/v4/users/logout", method: :post)
.to_return(headers: { Authorization: 'token thisworksnow' }, status: 200)
end
diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb
new file mode 100644
index 00000000000..3465c3a050b
--- /dev/null
+++ b/spec/lib/quality/test_level_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Quality::TestLevel do
+ describe '#pattern' do
+ context 'when level is unit' do
+ it 'returns a pattern' do
+ expect(subject.pattern(:unit))
+ .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb")
+ end
+ end
+
+ context 'when level is integration' do
+ it 'returns a pattern' do
+ expect(subject.pattern(:integration))
+ .to eq("spec/{controllers,mailers,requests}{,/**/}*_spec.rb")
+ end
+ end
+
+ context 'when level is system' do
+ it 'returns a pattern' do
+ expect(subject.pattern(:system))
+ .to eq("spec/{features}{,/**/}*_spec.rb")
+ end
+ end
+
+ context 'with a prefix' do
+ it 'returns a pattern' do
+ expect(described_class.new('ee/').pattern(:system))
+ .to eq("ee/spec/{features}{,/**/}*_spec.rb")
+ end
+ end
+
+ describe 'performance' do
+ it 'memoizes the pattern for a given level' do
+ expect(subject.pattern(:system).object_id).to eq(subject.pattern(:system).object_id)
+ end
+
+ it 'freezes the pattern for a given level' do
+ expect(subject.pattern(:system)).to be_frozen
+ end
+ end
+ end
+
+ describe '#regexp' do
+ context 'when level is unit' do
+ it 'returns a regexp' do
+ expect(subject.regexp(:unit))
+ .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)})
+ end
+ end
+
+ context 'when level is integration' do
+ it 'returns a regexp' do
+ expect(subject.regexp(:integration))
+ .to eq(%r{spec/(controllers|mailers|requests)})
+ end
+ end
+
+ context 'when level is system' do
+ it 'returns a regexp' do
+ expect(subject.regexp(:system))
+ .to eq(%r{spec/(features)})
+ end
+ end
+
+ context 'with a prefix' do
+ it 'returns a regexp' do
+ expect(described_class.new('ee/').regexp(:system))
+ .to eq(%r{ee/spec/(features)})
+ end
+ end
+
+ describe 'performance' do
+ it 'memoizes the regexp for a given level' do
+ expect(subject.regexp(:system).object_id).to eq(subject.regexp(:system).object_id)
+ end
+
+ it 'freezes the regexp for a given level' do
+ expect(subject.regexp(:system)).to be_frozen
+ end
+ end
+ end
+
+ describe '#level_for' do
+ it 'returns the correct level for a unit test' do
+ expect(subject.level_for('spec/models/abuse_report_spec.rb')).to eq(:unit)
+ end
+
+ it 'returns the correct level for an integration test' do
+ expect(subject.level_for('spec/mailers/abuse_report_mailer_spec.rb')).to eq(:integration)
+ end
+
+ it 'returns the correct level for a system test' do
+ expect(subject.level_for('spec/features/abuse_report_spec.rb')).to eq(:system)
+ end
+
+ it 'raises an error for an unknown level' do
+ expect { subject.level_for('spec/unknown/foo_spec.rb') }
+ .to raise_error(described_class::UnknownTestLevelError,
+ %r{Test level for spec/unknown/foo_spec.rb couldn't be set. Please rename the file properly or change the test level detection regexes in .+/lib/quality/test_level.rb.})
+ end
+ end
+end
diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb
index 050af587061..2f594dbf9d1 100644
--- a/spec/mailers/emails/pages_domains_spec.rb
+++ b/spec/mailers/emails/pages_domains_spec.rb
@@ -5,11 +5,13 @@ describe Emails::PagesDomains do
include EmailSpec::Matchers
include_context 'gitlab email notification'
- set(:project) { create(:project) }
set(:domain) { create(:pages_domain, project: project) }
- set(:user) { project.owner }
+ set(:user) { project.creator }
shared_examples 'a pages domain email' do
+ let(:test_recipient) { user }
+
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 8f348b1b053..cbbb22ad78c 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -45,6 +45,10 @@ describe Notify do
context 'for a project' do
shared_examples 'an assignee email' do
+ let(:test_recipient) { assignee }
+
+ it_behaves_like 'an email sent to a user'
+
it 'is sent to the assignee as the author' do
sender = subject.header[:from].addrs.first
@@ -618,8 +622,10 @@ describe Notify do
end
describe 'project was moved' do
+ let(:test_recipient) { user }
subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
@@ -773,7 +779,7 @@ describe Notify do
invitee
end
- subject { described_class.member_invite_declined_email('project', project.id, project_member.invite_email, maintainer.id) }
+ subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, maintainer.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
@@ -1083,8 +1089,6 @@ describe Notify do
end
context 'for a group' do
- set(:group) { create(:group) }
-
describe 'group access requested' do
let(:group) { create(:group, :public, :access_requestable) }
let(:group_member) do
diff --git a/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
new file mode 100644
index 00000000000..93e7e9304b1
--- /dev/null
+++ b/spec/migrations/change_packages_size_defaults_in_project_statistics_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190516155724_change_packages_size_defaults_in_project_statistics.rb')
+
+describe ChangePackagesSizeDefaultsInProjectStatistics, :migration do
+ let(:project_statistics) { table(:project_statistics) }
+ let(:projects) { table(:projects) }
+
+ it 'removes null packages_size' do
+ stats_to_migrate = 10
+
+ stats_to_migrate.times do |i|
+ p = projects.create!(name: "project #{i}", namespace_id: 1)
+ project_statistics.create!(project_id: p.id, namespace_id: p.namespace_id)
+ end
+
+ expect { migrate! }
+ .to change { ProjectStatistics.where(packages_size: nil).count }
+ .from(stats_to_migrate)
+ .to(0)
+ end
+
+ it 'defaults packages_size to 0' do
+ project = projects.create!(name: 'a new project', namespace_id: 2)
+ stat = project_statistics.create!(project_id: project.id, namespace_id: project.namespace_id)
+
+ expect(stat.packages_size).to be_nil
+
+ migrate!
+
+ stat.reload
+ expect(stat.packages_size).to eq(0)
+ end
+end
diff --git a/spec/migrations/enqueue_reset_merge_status_spec.rb b/spec/migrations/enqueue_reset_merge_status_spec.rb
new file mode 100644
index 00000000000..a6dd2e08079
--- /dev/null
+++ b/spec/migrations/enqueue_reset_merge_status_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190528180441_enqueue_reset_merge_status.rb')
+
+describe EnqueueResetMergeStatus, :migration, :sidekiq do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+
+ def create_merge_request(id, extra_params = {})
+ params = {
+ id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}"
+ }.merge(extra_params)
+
+ merge_requests.create!(params)
+ end
+
+ it 'correctly schedules background migrations' do
+ create_merge_request(1, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(2, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(3, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(4, state: 'merged', merge_status: 'can_be_merged')
+ create_merge_request(5, state: 'opened', merge_status: 'unchecked')
+
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(5.minutes, 1, 2)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(10.minutes, 3, 4)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(15.minutes, 5, 5)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(3)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb
index afcaefa0591..abf39317188 100644
--- a/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb
+++ b/spec/migrations/enqueue_verify_pages_domain_workers_spec.rb
@@ -8,9 +8,13 @@ describe EnqueueVerifyPagesDomainWorkers, :sidekiq, :migration do
end
end
+ let(:domains_table) { table(:pages_domains) }
+
describe '#up' do
it 'enqueues a verification worker for every domain' do
- domains = 1.upto(3).map { |i| PagesDomain.create!(domain: "my#{i}.domain.com") }
+ domains = Array.new(3) do |i|
+ domains_table.create!(domain: "my#{i}.domain.com", verification_code: "123#{i}")
+ end
expect { migrate! }.to change(PagesDomainVerificationWorker.jobs, :size).by(3)
diff --git a/spec/migrations/generate_lets_encrypt_private_key_spec.rb b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
new file mode 100644
index 00000000000..773bf5222f0
--- /dev/null
+++ b/spec/migrations/generate_lets_encrypt_private_key_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190524062810_generate_lets_encrypt_private_key.rb')
+
+describe GenerateLetsEncryptPrivateKey, :migration do
+ describe '#up' do
+ it 'does not fail' do
+ expect do
+ described_class.new.up
+ end.not_to raise_error
+ end
+ end
+end
diff --git a/spec/migrations/generate_missing_routes_spec.rb b/spec/migrations/generate_missing_routes_spec.rb
index 32515d353b0..30ad135d4df 100644
--- a/spec/migrations/generate_missing_routes_spec.rb
+++ b/spec/migrations/generate_missing_routes_spec.rb
@@ -8,7 +8,7 @@ describe GenerateMissingRoutes, :migration do
let(:routes) { table(:routes) }
it 'creates routes for projects without a route' do
- namespace = namespaces.create!(name: 'GitLab', path: 'gitlab')
+ namespace = namespaces.create!(name: 'GitLab', path: 'gitlab', type: 'Group')
routes.create!(
path: 'gitlab',
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index 79e21514506..bc826d91471 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -45,10 +45,6 @@ describe MigrateOldArtifacts, :migration, schema: 20170918072948 do
expect(build_with_legacy_artifacts.artifacts?).to be_falsey
end
- it "legacy artifacts are set" do
- expect(build_with_legacy_artifacts.legacy_artifacts_file_identifier).not_to be_nil
- end
-
describe '#min_id' do
subject { migration.send(:min_id) }
diff --git a/spec/migrations/remove_orphaned_label_links_spec.rb b/spec/migrations/remove_orphaned_label_links_spec.rb
index 13b8919343e..e8c44c141c3 100644
--- a/spec/migrations/remove_orphaned_label_links_spec.rb
+++ b/spec/migrations/remove_orphaned_label_links_spec.rb
@@ -10,6 +10,12 @@ describe RemoveOrphanedLabelLinks, :migration do
let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
let(:label) { create_label }
+ before do
+ # This migration was created before we introduced ProjectCiCdSetting#default_git_depth
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil)
+ allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0)
+ end
+
context 'add foreign key on label_id' do
let!(:label_link_with_label) { create_label_link(label_id: label.id) }
let!(:label_link_without_label) { create_label_link(label_id: nil) }
diff --git a/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
new file mode 100644
index 00000000000..54f3e264df0
--- /dev/null
+++ b/spec/migrations/schedule_fill_valid_time_for_pages_domain_certificates_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190524073827_schedule_fill_valid_time_for_pages_domain_certificates.rb')
+
+describe ScheduleFillValidTimeForPagesDomainCertificates, :migration, :sidekiq do
+ let(:migration_class) { described_class::MIGRATION }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ let(:domains_table) { table(:pages_domains) }
+
+ let(:certificate) do
+ File.read('spec/fixtures/passphrase_x509_certificate.crt')
+ end
+
+ before do
+ domains_table.create!(domain: "domain1.example.com", verification_code: "123")
+ domains_table.create!(domain: "domain2.example.com", verification_code: "123", certificate: '')
+ domains_table.create!(domain: "domain3.example.com", verification_code: "123", certificate: certificate)
+ domains_table.create!(domain: "domain4.example.com", verification_code: "123", certificate: certificate)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ first_id = domains_table.find_by_domain("domain3.example.com").id
+ last_id = domains_table.find_by_domain("domain4.example.com").id
+
+ expect(migration_name).to be_scheduled_delayed_migration(5.minutes, first_id, last_id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+
+ it 'sets certificate valid_not_before/not_after' do
+ perform_enqueued_jobs do
+ migrate!
+
+ domain = domains_table.find_by_domain("domain3.example.com")
+ expect(domain.certificate_valid_not_before)
+ .to eq(Time.parse("2018-03-23 14:02:08 UTC"))
+ expect(domain.certificate_valid_not_after)
+ .to eq(Time.parse("2019-03-23 14:02:08 UTC"))
+ end
+ end
+end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 3ab013ddc0e..4d53e4aad8a 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -88,13 +88,6 @@ describe BroadcastMessage do
expect(Rails.cache).not_to receive(:delete).with(described_class::CACHE_KEY)
expect(described_class.current.length).to eq(0)
end
-
- it 'clears the legacy cache key' do
- create(:broadcast_message, :future)
-
- expect(Rails.cache).to receive(:delete).with(described_class::LEGACY_CACHE_KEY)
- expect(described_class.current.length).to eq(0)
- end
end
describe '#attributes' do
@@ -164,7 +157,6 @@ describe BroadcastMessage do
message = create(:broadcast_message)
expect(Rails.cache).to receive(:delete).with(described_class::CACHE_KEY)
- expect(Rails.cache).to receive(:delete).with(described_class::LEGACY_CACHE_KEY)
message.flush_redis_cache
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 5f2e8aa0baa..d98db024f73 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -30,12 +30,6 @@ describe Ci::Build do
it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) }
it { is_expected.to include_module(Ci::PipelineDelegator) }
- it { is_expected.to be_a(ArtifactMigratable) }
-
- it_behaves_like 'UpdateProjectStatistics' do
- subject { FactoryBot.build(:ci_build, pipeline: pipeline, artifacts_size: 23) }
- end
-
describe 'associations' do
it 'has a bidirectional relationship with projects' do
expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds)
@@ -116,24 +110,6 @@ describe Ci::Build do
end
end
- context 'when job has a legacy archive' do
- let!(:job) { create(:ci_build, :legacy_artifacts) }
-
- it 'returns the job' do
- is_expected.to include(job)
- end
-
- context 'when ci_enable_legacy_artifacts feature flag is disabled' do
- before do
- stub_feature_flags(ci_enable_legacy_artifacts: false)
- end
-
- it 'does not return the job' do
- is_expected.not_to include(job)
- end
- end
- end
-
context 'when job has a job artifact archive' do
let!(:job) { create(:ci_build, :artifacts) }
@@ -464,51 +440,11 @@ describe Ci::Build do
end
end
end
-
- context 'when legacy artifacts are used' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
-
- subject { build.artifacts? }
-
- context 'is expired' do
- let(:build) { create(:ci_build, :legacy_artifacts, :expired) }
-
- it { is_expected.to be_falsy }
- end
-
- context 'artifacts archive does not exist' do
- let(:build) { create(:ci_build) }
-
- it { is_expected.to be_falsy }
- end
-
- context 'artifacts archive exists' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
-
- it { is_expected.to be_truthy }
-
- context 'when ci_enable_legacy_artifacts feature flag is disabled' do
- before do
- stub_feature_flags(ci_enable_legacy_artifacts: false)
- end
-
- it { is_expected.to be_falsy }
- end
- end
- end
end
describe '#browsable_artifacts?' do
subject { build.browsable_artifacts? }
- context 'artifacts metadata does not exist' do
- before do
- build.update(legacy_artifacts_metadata: nil)
- end
-
- it { is_expected.to be_falsy }
- end
-
context 'artifacts metadata does exists' do
let(:build) { create(:ci_build, :artifacts) }
@@ -764,12 +700,6 @@ describe Ci::Build do
it { is_expected.to be_truthy }
end
-
- context 'when build does not have job artifacts' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
-
- it { is_expected.to be_falsy }
- end
end
describe '#has_old_trace?' do
@@ -1096,11 +1026,11 @@ describe Ci::Build do
describe 'erasable build' do
shared_examples 'erasable' do
it 'removes artifact file' do
- expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_file.present?).to be_falsy
end
it 'removes artifact metadata file' do
- expect(build.artifacts_metadata.exists?).to be_falsy
+ expect(build.artifacts_metadata.present?).to be_falsy
end
it 'removes all job_artifacts' do
@@ -1192,7 +1122,7 @@ describe Ci::Build do
let!(:build) { create(:ci_build, :success, :artifacts) }
before do
- build.remove_artifacts_metadata!
+ build.erase_erasable_artifacts!
end
describe '#erase' do
@@ -1203,76 +1133,6 @@ describe Ci::Build do
end
end
end
-
- context 'old artifacts' do
- context 'build is erasable' do
- context 'new artifacts' do
- let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) }
-
- describe '#erase' do
- before do
- build.erase(erased_by: erased_by)
- end
-
- context 'erased by user' do
- let!(:erased_by) { create(:user, username: 'eraser') }
-
- include_examples 'erasable'
-
- it 'records user who erased a build' do
- expect(build.erased_by).to eq erased_by
- end
- end
-
- context 'erased by system' do
- let(:erased_by) { nil }
-
- include_examples 'erasable'
-
- it 'does not set user who erased a build' do
- expect(build.erased_by).to be_nil
- end
- end
- end
-
- describe '#erasable?' do
- subject { build.erasable? }
- it { is_expected.to be_truthy }
- end
-
- describe '#erased?' do
- let!(:build) { create(:ci_build, :trace_artifact, :success, :legacy_artifacts) }
- subject { build.erased? }
-
- context 'job has not been erased' do
- it { is_expected.to be_falsey }
- end
-
- context 'job has been erased' do
- before do
- build.erase
- end
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'metadata and build trace are not available' do
- let!(:build) { create(:ci_build, :success, :legacy_artifacts) }
-
- before do
- build.remove_artifacts_metadata!
- end
-
- describe '#erase' do
- it 'does not raise error' do
- expect { build.erase }.not_to raise_error
- end
- end
- end
- end
- end
- end
end
describe '#erase_erasable_artifacts!' do
@@ -1434,7 +1294,7 @@ describe Ci::Build do
build.cancel!
end
- it { is_expected.not_to be_retryable }
+ it { is_expected.to be_retryable }
end
end
@@ -1964,7 +1824,7 @@ describe Ci::Build do
context 'when build has been canceled' do
subject { build_stubbed(:ci_build, :manual, status: :canceled) }
- it { is_expected.not_to be_playable }
+ it { is_expected.to be_playable }
end
context 'when build is successful' do
@@ -2604,30 +2464,6 @@ describe Ci::Build do
it { is_expected.to include(ci_config_path) }
end
- context 'when using auto devops' do
- context 'and is enabled' do
- before do
- project.create_auto_devops!(enabled: true, domain: 'example.com')
- end
-
- it "includes AUTO_DEVOPS_DOMAIN" do
- is_expected.to include(
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false })
- end
- end
-
- context 'and is disabled' do
- before do
- project.create_auto_devops!(enabled: false, domain: 'example.com')
- end
-
- it "includes AUTO_DEVOPS_DOMAIN" do
- is_expected.not_to include(
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true, masked: false })
- end
- end
- end
-
context 'when pipeline variable overrides build variable' do
before do
build.yaml_variables = [{ key: 'MYVAR', value: 'myvar', public: true }]
@@ -3514,6 +3350,18 @@ describe Ci::Build do
end
end
+ describe '#report_artifacts' do
+ subject { build.report_artifacts }
+
+ context 'when the build has reports' do
+ let!(:report) { create(:ci_job_artifact, :codequality, job: build) }
+
+ it 'returns the artifacts with reports' do
+ expect(subject).to contain_exactly(report)
+ end
+ end
+ end
+
describe '#artifacts_metadata_entry' do
set(:build) { create(:ci_build, project: project) }
let(:path) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' }
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 5964f66c398..1ba66565e03 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -5,10 +5,6 @@ require 'spec_helper'
describe Ci::JobArtifact do
let(:artifact) { create(:ci_job_artifact, :archive) }
- it_behaves_like 'UpdateProjectStatistics' do
- subject { build(:ci_job_artifact, :archive, size: 106365) }
- end
-
describe "Associations" do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:job) }
@@ -23,6 +19,25 @@ describe Ci::JobArtifact do
it_behaves_like 'having unique enum values'
+ it_behaves_like 'UpdateProjectStatistics' do
+ subject { build(:ci_job_artifact, :archive, size: 106365) }
+ end
+
+ describe '.with_reports' do
+ let!(:artifact) { create(:ci_job_artifact, :archive) }
+
+ subject { described_class.with_reports }
+
+ it { is_expected.to be_empty }
+
+ context 'when there are reports' do
+ let!(:metrics_report) { create(:ci_job_artifact, :junit) }
+ let!(:codequality_report) { create(:ci_job_artifact, :codequality) }
+
+ it { is_expected.to eq([metrics_report, codequality_report]) }
+ end
+ end
+
describe '.test_reports' do
subject { described_class.test_reports }
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 42d4769a921..227870eb27f 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -48,32 +48,116 @@ describe Ci::PipelineSchedule do
end
end
+ describe '.runnable_schedules' do
+ subject { described_class.runnable_schedules }
+
+ let!(:pipeline_schedule) do
+ Timecop.freeze(1.day.ago) do
+ create(:ci_pipeline_schedule, :hourly)
+ end
+ end
+
+ it 'returns the runnable schedule' do
+ is_expected.to eq([pipeline_schedule])
+ end
+
+ context 'when there are no runnable schedules' do
+ let!(:pipeline_schedule) { }
+
+ it 'returns an empty array' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.preloaded' do
+ subject { described_class.preloaded }
+
+ before do
+ create_list(:ci_pipeline_schedule, 3)
+ end
+
+ it 'preloads the associations' do
+ subject
+
+ query = ActiveRecord::QueryRecorder.new { subject.each(&:project) }
+
+ expect(query.count).to eq(2)
+ end
+ end
+
describe '#set_next_run_at' do
- let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) }
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) }
+ let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) }
+
+ let(:expected_next_run_at) do
+ Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
+ .next_time_from(ideal_next_run_at)
+ end
+
+ let(:cron_worker_next_run_at) do
+ Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
+ .next_time_from(Time.zone.now)
+ end
context 'when creates new pipeline schedule' do
- let(:expected_next_run_at) do
- Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone)
- .next_time_from(Time.now)
+ it 'updates next_run_at automatically' do
+ expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at)
end
+ end
- it 'updates next_run_at automatically' do
- expect(described_class.last.next_run_at).to eq(expected_next_run_at)
+ context 'when PipelineScheduleWorker runs at a specific interval' do
+ before do
+ allow(Settings).to receive(:cron_jobs) do
+ {
+ 'pipeline_schedule_worker' => {
+ 'cron' => '0 1 2 3 *'
+ }
+ }
+ end
+ end
+
+ it "updates next_run_at to the sidekiq worker's execution time" do
+ expect(pipeline_schedule.next_run_at.min).to eq(0)
+ expect(pipeline_schedule.next_run_at.hour).to eq(1)
+ expect(pipeline_schedule.next_run_at.day).to eq(2)
+ expect(pipeline_schedule.next_run_at.month).to eq(3)
end
end
- context 'when updates cron of exsisted pipeline schedule' do
- let(:new_cron) { '0 0 1 1 *' }
+ context 'when pipeline schedule runs every minute' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, :every_minute) }
- let(:expected_next_run_at) do
- Gitlab::Ci::CronParser.new(new_cron, pipeline_schedule.cron_timezone)
- .next_time_from(Time.now)
+ it "updates next_run_at to the sidekiq worker's execution time", :quarantine do
+ expect(pipeline_schedule.next_run_at).to eq(cron_worker_next_run_at)
+ end
+ end
+
+ context 'when there are two different pipeline schedules in different time zones' do
+ let(:pipeline_schedule_1) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'Eastern Time (US & Canada)') }
+ let(:pipeline_schedule_2) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'UTC') }
+
+ it 'sets different next_run_at' do
+ expect(pipeline_schedule_1.next_run_at).not_to eq(pipeline_schedule_2.next_run_at)
+ end
+ end
+
+ context 'when there are two different pipeline schedules in the same time zones' do
+ let(:pipeline_schedule_1) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'UTC') }
+ let(:pipeline_schedule_2) { create(:ci_pipeline_schedule, :weekly, cron_timezone: 'UTC') }
+
+ it 'sets the sames next_run_at' do
+ expect(pipeline_schedule_1.next_run_at).to eq(pipeline_schedule_2.next_run_at)
end
+ end
+
+ context 'when updates cron of exsisted pipeline schedule' do
+ let(:new_cron) { '0 0 1 1 *' }
it 'updates next_run_at automatically' do
pipeline_schedule.update!(cron: new_cron)
- expect(described_class.last.next_run_at).to eq(expected_next_run_at)
+ expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at)
end
end
end
@@ -83,10 +167,11 @@ describe Ci::PipelineSchedule do
context 'when reschedules after 10 days from now' do
let(:future_time) { 10.days.from_now }
+ let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) }
let(:expected_next_run_at) do
- Gitlab::Ci::CronParser.new(pipeline_schedule.cron, pipeline_schedule.cron_timezone)
- .next_time_from(future_time)
+ Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
+ .next_time_from(ideal_next_run_at)
end
it 'points to proper next_run_at' do
@@ -99,38 +184,6 @@ describe Ci::PipelineSchedule do
end
end
- describe '#real_next_run' do
- subject do
- described_class.last.real_next_run(worker_cron: worker_cron,
- worker_time_zone: worker_time_zone)
- end
-
- context 'when GitLab time_zone is UTC' do
- before do
- allow(Time).to receive(:zone)
- .and_return(ActiveSupport::TimeZone[worker_time_zone])
- end
-
- let(:worker_time_zone) { 'UTC' }
-
- context 'when cron_timezone is Eastern Time (US & Canada)' do
- before do
- create(:ci_pipeline_schedule, :nightly,
- cron_timezone: 'Eastern Time (US & Canada)')
- end
-
- let(:worker_cron) { '0 1 2 3 *' }
-
- it 'returns the next time worker executes' do
- expect(subject.min).to eq(0)
- expect(subject.hour).to eq(1)
- expect(subject.day).to eq(2)
- expect(subject.month).to eq(3)
- end
- end
- end
- end
-
describe '#job_variables' do
let!(:pipeline_schedule) { create(:ci_pipeline_schedule) }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index a0319b3eb0a..a8701f0efa4 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1381,6 +1381,40 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe 'auto merge' do
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, :running, project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+ end
+
+ before do
+ merge_request.update_head_pipeline
+ end
+
+ %w[succeed! drop! cancel! skip!].each do |action|
+ context "when the pipeline recieved #{action} event" do
+ it 'performs AutoMergeProcessWorker' do
+ expect(AutoMergeProcessWorker).to receive(:perform_async).with(merge_request.id)
+
+ pipeline.public_send(action)
+ end
+ end
+ end
+
+ context 'when auto merge is not enabled in the merge request' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'performs AutoMergeProcessWorker' do
+ expect(AutoMergeProcessWorker).not_to receive(:perform_async)
+
+ pipeline.succeed!
+ end
+ end
+ end
+
def create_build(name, *traits, queued_at: current, started_from: 0, **opts)
create(:ci_build, *traits,
name: name,
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index fc9ebed863e..43fa1010b2b 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -96,6 +96,8 @@ describe Clusters::Applications::Jupyter do
expect(values).to match(/clientId: '?#{application.oauth_application.uid}/)
expect(values).to match(/callbackUrl: '?#{application.callback_url}/)
expect(values).to include("gitlabProjectIdWhitelist:\n - #{application.cluster.project.id}")
+ expect(values).to include("c.GitLabOAuthenticator.scope = ['api read_repository write_repository']")
+ expect(values).to match(/GITLAB_HOST: '?#{Gitlab.config.gitlab.host}/)
end
context 'when cluster belongs to a project' do
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index d5974f47190..b38cf96de7e 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -3,9 +3,6 @@
require 'rails_helper'
describe Clusters::Applications::Knative do
- include KubernetesHelpers
- include ReactiveCachingHelpers
-
let(:knative) { create(:clusters_applications_knative) }
include_examples 'cluster application core specs', :clusters_applications_knative
@@ -146,77 +143,4 @@ describe Clusters::Applications::Knative do
describe 'validations' do
it { is_expected.to validate_presence_of(:hostname) }
end
-
- describe '#service_pod_details' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:service) { cluster.platform_kubernetes }
- let(:knative) { create(:clusters_applications_knative, cluster: cluster) }
-
- let(:namespace) do
- create(:cluster_kubernetes_namespace,
- cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
- end
-
- before do
- stub_kubeclient_discover(service.api_url)
- stub_kubeclient_knative_services
- stub_kubeclient_service_pods
- stub_reactive_cache(knative,
- {
- services: kube_response(kube_knative_services_body),
- pods: kube_response(kube_knative_pods_body(cluster.cluster_project.project.name, namespace.namespace))
- })
- synchronous_reactive_cache(knative)
- end
-
- it 'is able k8s core for pod details' do
- expect(knative.service_pod_details(namespace.namespace, cluster.cluster_project.project.name)).not_to be_nil
- end
- end
-
- describe '#services' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:service) { cluster.platform_kubernetes }
- let(:knative) { create(:clusters_applications_knative, cluster: cluster) }
-
- let(:namespace) do
- create(:cluster_kubernetes_namespace,
- cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
- end
-
- subject { knative.services }
-
- before do
- stub_kubeclient_discover(service.api_url)
- stub_kubeclient_knative_services
- stub_kubeclient_service_pods
- end
-
- it 'has an unintialized cache' do
- is_expected.to be_nil
- end
-
- context 'when using synchronous reactive cache' do
- before do
- stub_reactive_cache(knative,
- {
- services: kube_response(kube_knative_services_body),
- pods: kube_response(kube_knative_pods_body(cluster.cluster_project.project.name, namespace.namespace))
- })
- synchronous_reactive_cache(knative)
- end
-
- it 'has cached services' do
- is_expected.not_to be_nil
- end
-
- it 'matches our namespace' do
- expect(knative.services_for(ns: namespace)).not_to be_nil
- end
- end
- end
end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 3ee8c340bfe..f206bb41f45 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -2,7 +2,10 @@
require 'spec_helper'
-describe Clusters::Cluster do
+describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
+ include ReactiveCachingHelpers
+ include KubernetesHelpers
+
it_behaves_like 'having unique enum values'
subject { build(:cluster) }
@@ -23,7 +26,6 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
- it { is_expected.to delegate_method(:status_name).to(:provider) }
it { is_expected.to delegate_method(:on_creation?).to(:provider) }
it { is_expected.to delegate_method(:active?).to(:platform_kubernetes).with_prefix }
it { is_expected.to delegate_method(:rbac?).to(:platform_kubernetes).with_prefix }
@@ -36,6 +38,11 @@ describe Clusters::Cluster do
it { is_expected.to respond_to :project }
+ it do
+ expect(subject.knative_services_finder(subject.project))
+ .to be_instance_of(Clusters::KnativeServicesFinder)
+ end
+
describe '.enabled' do
subject { described_class.enabled }
@@ -501,28 +508,6 @@ describe Clusters::Cluster do
end
end
- describe '#created?' do
- let(:cluster) { create(:cluster, :provided_by_gcp) }
-
- subject { cluster.created? }
-
- context 'when status_name is :created' do
- before do
- allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:created)
- end
-
- it { is_expected.to eq(true) }
- end
-
- context 'when status_name is not :created' do
- before do
- allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:creating)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
describe '#allow_user_defined_namespace?' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
@@ -557,62 +542,15 @@ describe Clusters::Cluster do
end
context 'with no domain on cluster' do
- context 'with a project cluster' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:project) { cluster.project }
-
- context 'with domain set at instance level' do
- before do
- stub_application_setting(auto_devops_domain: 'global_domain.com')
-
- it { is_expected.to eq('global_domain.com') }
- end
- end
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:project) { cluster.project }
- context 'with domain set on ProjectAutoDevops' do
- before do
- auto_devops = project.build_auto_devops(domain: 'legacy-ado-domain.com')
- auto_devops.save
- end
-
- it { is_expected.to eq('legacy-ado-domain.com') }
+ context 'with domain set at instance level' do
+ before do
+ stub_application_setting(auto_devops_domain: 'global_domain.com')
end
- context 'with domain set as environment variable on project' do
- before do
- variable = project.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'project-ado-domain.com')
- variable.save
- end
-
- it { is_expected.to eq('project-ado-domain.com') }
- end
-
- context 'with domain set as environment variable on the group project' do
- let(:group) { create(:group) }
-
- before do
- project.update(parent_id: group.id)
- variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com')
- variable.save
- end
-
- it { is_expected.to eq('group-ado-domain.com') }
- end
- end
-
- context 'with a group cluster' do
- let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
-
- context 'with domain set as environment variable for the group' do
- let(:group) { cluster.group }
-
- before do
- variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com')
- variable.save
- end
-
- it { is_expected.to eq('group-ado-domain.com') }
- end
+ it { is_expected.to eq('global_domain.com') }
end
end
end
@@ -664,4 +602,139 @@ describe Clusters::Cluster do
it { is_expected.to be_truthy }
end
end
+
+ describe '#status_name' do
+ subject { cluster.status_name }
+
+ context 'the cluster has a provider' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ before do
+ cluster.provider.make_errored!
+ end
+
+ it { is_expected.to eq :errored }
+ end
+
+ context 'there is a cached connection status' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ before do
+ allow(cluster).to receive(:connection_status).and_return(:connected)
+ end
+
+ it { is_expected.to eq :connected }
+ end
+
+ context 'there is no connection status in the cache' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ before do
+ allow(cluster).to receive(:connection_status).and_return(nil)
+ end
+
+ it { is_expected.to eq :created }
+ end
+ end
+
+ describe '#connection_status' do
+ let(:cluster) { create(:cluster) }
+ let(:status) { :connected }
+
+ subject { cluster.connection_status }
+
+ it { is_expected.to be_nil }
+
+ context 'with a cached status' do
+ before do
+ stub_reactive_cache(cluster, connection_status: status)
+ end
+
+ it { is_expected.to eq(status) }
+ end
+ end
+
+ describe '#calculate_reactive_cache' do
+ subject { cluster.calculate_reactive_cache }
+
+ context 'cluster is disabled' do
+ let(:cluster) { create(:cluster, :disabled) }
+
+ it 'does not populate the cache' do
+ expect(cluster).not_to receive(:retrieve_connection_status)
+
+ is_expected.to be_nil
+ end
+ end
+
+ context 'cluster is enabled' do
+ let(:cluster) { create(:cluster, :provided_by_user, :group) }
+
+ context 'connection to the cluster is successful' do
+ before do
+ stub_kubeclient_discover(cluster.platform.api_url)
+ end
+
+ it { is_expected.to eq(connection_status: :connected) }
+ end
+
+ context 'cluster cannot be reached' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(SocketError)
+ end
+
+ it { is_expected.to eq(connection_status: :unreachable) }
+ end
+
+ context 'cluster cannot be authenticated to' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(OpenSSL::X509::CertificateError.new("Certificate error"))
+ end
+
+ it { is_expected.to eq(connection_status: :authentication_failure) }
+ end
+
+ describe 'Kubeclient::HttpError' do
+ let(:error_code) { 403 }
+ let(:error_message) { "Forbidden" }
+
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(Kubeclient::HttpError.new(error_code, error_message, nil))
+ end
+
+ it { is_expected.to eq(connection_status: :authentication_failure) }
+
+ context 'generic timeout' do
+ let(:error_message) { 'Timed out connecting to server'}
+
+ it { is_expected.to eq(connection_status: :unreachable) }
+ end
+
+ context 'gateway timeout' do
+ let(:error_message) { '504 Gateway Timeout for GET https://kubernetes.example.com/api/v1'}
+
+ it { is_expected.to eq(connection_status: :unreachable) }
+ end
+ end
+
+ context 'an uncategorised error is raised' do
+ before do
+ allow(cluster.kubeclient.core_client).to receive(:discover)
+ .and_raise(StandardError)
+ end
+
+ it { is_expected.to eq(connection_status: :unknown_failure) }
+
+ it 'notifies Sentry' do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception)
+ .with(instance_of(StandardError), hash_including(extra: { cluster_id: cluster.id }))
+
+ subject
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 78637ff10c6..0e5fb2b5153 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -2,383 +2,213 @@
require 'spec_helper'
-describe CacheMarkdownField do
- # The minimum necessary ActiveModel to test this concern
- class ThingWithMarkdownFields
- include ActiveModel::Model
- include ActiveModel::Dirty
-
- include ActiveModel::Serialization
-
- class_attribute :attribute_names
- self.attribute_names = []
-
- def attributes
- attribute_names.each_with_object({}) do |name, hsh|
- hsh[name.to_s] = send(name)
- end
+describe CacheMarkdownField, :clean_gitlab_redis_cache do
+ let(:ar_class) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'issues'
+ include CacheMarkdownField
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :description
end
+ end
- extend ActiveModel::Callbacks
- define_model_callbacks :create, :update
-
- include CacheMarkdownField
- cache_markdown_field :foo
- cache_markdown_field :baz, pipeline: :single_line
- cache_markdown_field :zoo, whitelisted: true
+ let(:other_class) do
+ Class.new do
+ include CacheMarkdownField
- def self.add_attr(name)
- self.attribute_names += [name]
- define_attribute_methods(name)
- attr_reader(name)
- define_method("#{name}=") do |value|
- write_attribute(name, value)
+ def initialize(args = {})
+ @title, @description, @cached_markdown_version = args[:title], args[:description], args[:cached_markdown_version]
+ @title_html, @description_html = args[:title_html], args[:description_html]
+ @author, @project = args[:author], args[:project]
end
- end
- add_attr :cached_markdown_version
+ attr_accessor :title, :description, :cached_markdown_version
- [:foo, :foo_html, :bar, :baz, :baz_html, :zoo, :zoo_html].each do |name|
- add_attr(name)
- end
-
- def initialize(*)
- super
-
- # Pretend new is load
- clear_changes_information
- end
-
- def read_attribute(name)
- instance_variable_get("@#{name}")
- end
-
- def write_attribute(name, value)
- send("#{name}_will_change!") unless value == read_attribute(name)
- instance_variable_set("@#{name}", value)
- end
+ cache_markdown_field :title, pipeline: :single_line
+ cache_markdown_field :description
- def save
- run_callbacks :update do
- changes_applied
+ def cache_key
+ "cache-key"
end
end
-
- def has_attribute?(attr_name)
- attribute_names.include?(attr_name)
- end
- end
-
- def thing_subclass(new_attr)
- Class.new(ThingWithMarkdownFields) { add_attr(new_attr) }
end
let(:markdown) { '`Foo`' }
- let(:html) { '<p dir="auto"><code>Foo</code></p>' }
+ let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' }
let(:updated_markdown) { '`Bar`' }
- let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' }
-
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
- let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 }
-
- before do
- stub_commonmark_sourcepos_disabled
- end
+ let(:updated_html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Bar</code></p>' }
- describe '.attributes' do
- it 'excludes cache attributes that is blacklisted by default' do
- expect(thing.attributes.keys.sort).to eq(%w[bar baz cached_markdown_version foo zoo zoo_html])
- end
- end
-
- context 'an unchanged markdown field' do
- before do
- thing.foo = thing.foo
- thing.save
- end
+ let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
- it { expect(thing.foo).to eq(markdown) }
- it { expect(thing.foo_html).to eq(html) }
- it { expect(thing.foo_html_changed?).not_to be_truthy }
- it { expect(thing.cached_markdown_version).to eq(cache_version) }
+ def thing_subclass(klass, extra_attribute)
+ Class.new(klass) { attr_accessor(extra_attribute) }
end
- context 'a changed markdown field' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) }
+ shared_examples 'a class with cached markdown fields' do
+ describe '#cached_html_up_to_date?' do
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
- before do
- thing.foo = updated_markdown
- thing.save
- end
+ subject { thing.cached_html_up_to_date?(:title) }
- it { expect(thing.foo_html).to eq(updated_html) }
- it { expect(thing.cached_markdown_version).to eq(cache_version) }
- end
+ it 'returns false when the version is absent' do
+ thing.cached_markdown_version = nil
- context 'when a markdown field is set repeatedly to an empty string' do
- it do
- expect(thing).to receive(:refresh_markdown_cache).once
- thing.foo = ''
- thing.save
- thing.foo = ''
- thing.save
- end
- end
-
- context 'when a markdown field is set repeatedly to a string which renders as empty html' do
- it do
- expect(thing).to receive(:refresh_markdown_cache).once
- thing.foo = '[//]: # (This is also a comment.)'
- thing.save
- thing.foo = '[//]: # (This is also a comment.)'
- thing.save
- end
- end
-
- context 'when a markdown field and html field are both changed' do
- it do
- expect(thing).not_to receive(:refresh_markdown_cache)
- thing.foo = '_look over there!_'
- thing.foo_html = '<em>look over there!</em>'
- thing.save
- end
- end
-
- context 'a non-markdown field changed' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) }
-
- before do
- thing.bar = 'OK'
- thing.save
- end
-
- it { expect(thing.bar).to eq('OK') }
- it { expect(thing.foo).to eq(markdown) }
- it { expect(thing.foo_html).to eq(html) }
- it { expect(thing.cached_markdown_version).to eq(cache_version) }
- end
-
- context 'version is out of date' do
- let(:thing) { ThingWithMarkdownFields.new(foo: updated_markdown, foo_html: html, cached_markdown_version: nil) }
-
- before do
- thing.save
- end
-
- it { expect(thing.foo_html).to eq(updated_html) }
- it { expect(thing.cached_markdown_version).to eq(cache_version) }
- end
-
- describe '#cached_html_up_to_date?' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
-
- subject { thing.cached_html_up_to_date?(:foo) }
-
- it 'returns false when the version is absent' do
- thing.cached_markdown_version = nil
-
- is_expected.to be_falsy
- end
-
- it 'returns false when the cached version is too old' do
- thing.cached_markdown_version = cache_version - 1
-
- is_expected.to be_falsy
- end
-
- it 'returns false when the cached version is in future' do
- thing.cached_markdown_version = cache_version + 1
-
- is_expected.to be_falsy
- end
-
- it 'returns false when the local version was bumped' do
- allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
- thing.cached_markdown_version = cache_version
-
- is_expected.to be_falsy
- end
+ is_expected.to be_falsy
+ end
- it 'returns true when the local version is default' do
- thing.cached_markdown_version = cache_version
+ it 'returns false when the version is too early' do
+ thing.cached_markdown_version -= 1
- is_expected.to be_truthy
- end
+ is_expected.to be_falsy
+ end
- it 'returns true when the cached version is just right' do
- allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
- thing.cached_markdown_version = cache_version + 2
+ it 'returns false when the version is too late' do
+ thing.cached_markdown_version += 1
- is_expected.to be_truthy
- end
+ is_expected.to be_falsy
+ end
- it 'returns false if markdown has been changed but html has not' do
- thing.foo = updated_html
+ it 'returns false when the local version was bumped' do
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
+ thing.cached_markdown_version = cache_version
- is_expected.to be_falsy
- end
+ is_expected.to be_falsy
+ end
- it 'returns true if markdown has not been changed but html has' do
- thing.foo_html = updated_html
+ it 'returns true when the local version is default' do
+ thing.cached_markdown_version = cache_version
- is_expected.to be_truthy
- end
+ is_expected.to be_truthy
+ end
- it 'returns true if markdown and html have both been changed' do
- thing.foo = updated_markdown
- thing.foo_html = updated_html
+ it 'returns true when the cached version is just right' do
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
+ thing.cached_markdown_version = cache_version + 2
- is_expected.to be_truthy
+ is_expected.to be_truthy
+ end
end
- it 'returns false if the markdown field is set but the html is not' do
- thing.foo_html = nil
+ describe '#latest_cached_markdown_version' do
+ let(:thing) { klass.new }
+ subject { thing.latest_cached_markdown_version }
- is_expected.to be_falsy
+ it 'returns default version' do
+ thing.cached_markdown_version = nil
+ is_expected.to eq(cache_version)
+ end
end
- end
-
- describe '#latest_cached_markdown_version' do
- subject { thing.latest_cached_markdown_version }
- it 'returns default version' do
- thing.cached_markdown_version = nil
- is_expected.to eq(cache_version)
- end
- end
+ describe '#refresh_markdown_cache' do
+ let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) }
- describe '#refresh_markdown_cache' do
- before do
- thing.foo = updated_markdown
- end
+ before do
+ thing.description = updated_markdown
+ end
- it 'fills all html fields' do
- thing.refresh_markdown_cache
+ it 'fills all html fields' do
+ thing.refresh_markdown_cache
- expect(thing.foo_html).to eq(updated_html)
- expect(thing.foo_html_changed?).to be_truthy
- expect(thing.baz_html_changed?).to be_truthy
- end
+ expect(thing.description_html).to eq(updated_html)
+ end
- it 'does not save the result' do
- expect(thing).not_to receive(:update_columns)
+ it 'does not save the result' do
+ expect(thing).not_to receive(:save_markdown)
- thing.refresh_markdown_cache
- end
+ thing.refresh_markdown_cache
+ end
- it 'updates the markdown cache version' do
- thing.cached_markdown_version = nil
- thing.refresh_markdown_cache
+ it 'updates the markdown cache version' do
+ thing.cached_markdown_version = nil
+ thing.refresh_markdown_cache
- expect(thing.cached_markdown_version).to eq(cache_version)
+ expect(thing.cached_markdown_version).to eq(cache_version)
+ end
end
- end
-
- describe '#refresh_markdown_cache!' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
- before do
- thing.foo = updated_markdown
- end
+ describe '#refresh_markdown_cache!' do
+ let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) }
- it 'fills all html fields' do
- thing.refresh_markdown_cache!
+ before do
+ thing.description = updated_markdown
+ end
- expect(thing.foo_html).to eq(updated_html)
- expect(thing.foo_html_changed?).to be_truthy
- expect(thing.baz_html_changed?).to be_truthy
- end
+ it 'fills all html fields' do
+ thing.refresh_markdown_cache!
- it 'skips saving if not persisted' do
- expect(thing).to receive(:persisted?).and_return(false)
- expect(thing).not_to receive(:update_columns)
+ expect(thing.description_html).to eq(updated_html)
+ end
- thing.refresh_markdown_cache!
- end
+ it 'saves the changes' do
+ expect(thing)
+ .to receive(:save_markdown)
+ .with("description_html" => updated_html, "title_html" => "", "cached_markdown_version" => cache_version)
- it 'saves the changes using #update_columns' do
- expect(thing).to receive(:persisted?).and_return(true)
- expect(thing).to receive(:update_columns)
- .with(
- "foo_html" => updated_html,
- "baz_html" => "",
- "zoo_html" => "",
- "cached_markdown_version" => cache_version
- )
-
- thing.refresh_markdown_cache!
+ thing.refresh_markdown_cache!
+ end
end
- end
- describe '#banzai_render_context' do
- subject(:context) { thing.banzai_render_context(:foo) }
+ describe '#banzai_render_context' do
+ let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ subject(:context) { thing.banzai_render_context(:title) }
- it 'sets project to nil if the object lacks a project' do
- is_expected.to have_key(:project)
- expect(context[:project]).to be_nil
- end
+ it 'sets project to nil if the object lacks a project' do
+ is_expected.to have_key(:project)
+ expect(context[:project]).to be_nil
+ end
- it 'excludes author if the object lacks an author' do
- is_expected.not_to have_key(:author)
- end
+ it 'excludes author if the object lacks an author' do
+ is_expected.not_to have_key(:author)
+ end
- it 'raises if the context for an unrecognised field is requested' do
- expect { thing.banzai_render_context(:not_found) }.to raise_error(ArgumentError)
- end
+ it 'raises if the context for an unrecognised field is requested' do
+ expect { thing.banzai_render_context(:not_found) }.to raise_error(ArgumentError)
+ end
- it 'includes the pipeline' do
- baz = thing.banzai_render_context(:baz)
+ it 'includes the pipeline' do
+ title_context = thing.banzai_render_context(:title)
- expect(baz[:pipeline]).to eq(:single_line)
- end
+ expect(title_context[:pipeline]).to eq(:single_line)
+ end
- it 'returns copies of the context template' do
- template = thing.cached_markdown_fields[:baz]
- copy = thing.banzai_render_context(:baz)
+ it 'returns copies of the context template' do
+ template = thing.cached_markdown_fields[:description]
+ copy = thing.banzai_render_context(:description)
- expect(copy).not_to be(template)
- end
+ expect(copy).not_to be(template)
+ end
- context 'with a project' do
- let(:project) { create(:project, group: create(:group)) }
- let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: project) }
+ context 'with a project' do
+ let(:project) { build(:project, group: create(:group)) }
+ let(:thing) { thing_subclass(klass, :project).new(title: markdown, title_html: html, project: project) }
- it 'sets the project in the context' do
- is_expected.to have_key(:project)
- expect(context[:project]).to eq(project)
+ it 'sets the project in the context' do
+ is_expected.to have_key(:project)
+ expect(context[:project]).to eq(project)
+ end
end
- it 'invalidates the cache when project changes' do
- thing.project = :new_project
- allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
-
- thing.save
+ context 'with an author' do
+ let(:thing) { thing_subclass(klass, :author).new(title: markdown, title_html: html, author: :author_value) }
- expect(thing.foo_html).to eq(updated_html)
- expect(thing.baz_html).to eq(updated_html)
- expect(thing.cached_markdown_version).to eq(cache_version)
+ it 'sets the author in the context' do
+ is_expected.to have_key(:author)
+ expect(context[:author]).to eq(:author_value)
+ end
end
end
+ end
- context 'with an author' do
- let(:thing) { thing_subclass(:author).new(foo: markdown, foo_html: html, author: :author_value) }
-
- it 'sets the author in the context' do
- is_expected.to have_key(:author)
- expect(context[:author]).to eq(:author_value)
- end
+ context 'for Active record classes' do
+ let(:klass) { ar_class }
- it 'invalidates the cache when author changes' do
- thing.author = :new_author
- allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
+ it_behaves_like 'a class with cached markdown fields'
+ end
- thing.save
+ context 'for other classes' do
+ let(:klass) { other_class }
- expect(thing.foo_html).to eq(updated_html)
- expect(thing.baz_html).to eq(updated_html)
- expect(thing.cached_markdown_version).to eq(cache_version)
- end
- end
+ it_behaves_like 'a class with cached markdown fields'
end
end
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 22d4b2cc517..7e9a8306612 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -194,58 +194,6 @@ describe Milestone, 'Milestoneish' do
end
end
- describe '#closed_items_count' do
- it 'does not count confidential issues for non project members' do
- expect(milestone.closed_items_count(non_member)).to eq 2
- end
-
- it 'does not count confidential issues for project members with guest role' do
- expect(milestone.closed_items_count(guest)).to eq 2
- end
-
- it 'counts confidential issues for author' do
- expect(milestone.closed_items_count(author)).to eq 4
- end
-
- it 'counts confidential issues for assignee' do
- expect(milestone.closed_items_count(assignee)).to eq 4
- end
-
- it 'counts confidential issues for project members' do
- expect(milestone.closed_items_count(member)).to eq 6
- end
-
- it 'counts all issues for admin' do
- expect(milestone.closed_items_count(admin)).to eq 6
- end
- end
-
- describe '#total_items_count' do
- it 'does not count confidential issues for non project members' do
- expect(milestone.total_items_count(non_member)).to eq 4
- end
-
- it 'does not count confidential issues for project members with guest role' do
- expect(milestone.total_items_count(guest)).to eq 4
- end
-
- it 'counts confidential issues for author' do
- expect(milestone.total_items_count(author)).to eq 7
- end
-
- it 'counts confidential issues for assignee' do
- expect(milestone.total_items_count(assignee)).to eq 7
- end
-
- it 'counts confidential issues for project members' do
- expect(milestone.total_items_count(member)).to eq 10
- end
-
- it 'counts all issues for admin' do
- expect(milestone.total_items_count(admin)).to eq 10
- end
- end
-
describe '#complete?' do
it 'returns false when has items opened' do
expect(milestone.complete?(non_member)).to eq false
@@ -260,28 +208,42 @@ describe Milestone, 'Milestoneish' do
end
describe '#percent_complete' do
+ context 'division by zero' do
+ let(:new_milestone) { build_stubbed(:milestone) }
+
+ it { expect(new_milestone.percent_complete(admin)).to eq(0) }
+ end
+ end
+
+ describe '#count_issues_by_state' do
it 'does not count confidential issues for non project members' do
- expect(milestone.percent_complete(non_member)).to eq 50
+ expect(milestone.closed_issues_count(non_member)).to eq 2
+ expect(milestone.total_issues_count(non_member)).to eq 3
end
it 'does not count confidential issues for project members with guest role' do
- expect(milestone.percent_complete(guest)).to eq 50
+ expect(milestone.closed_issues_count(guest)).to eq 2
+ expect(milestone.total_issues_count(guest)).to eq 3
end
it 'counts confidential issues for author' do
- expect(milestone.percent_complete(author)).to eq 57
+ expect(milestone.closed_issues_count(author)).to eq 4
+ expect(milestone.total_issues_count(author)).to eq 6
end
it 'counts confidential issues for assignee' do
- expect(milestone.percent_complete(assignee)).to eq 57
+ expect(milestone.closed_issues_count(assignee)).to eq 4
+ expect(milestone.total_issues_count(assignee)).to eq 6
end
it 'counts confidential issues for project members' do
- expect(milestone.percent_complete(member)).to eq 60
+ expect(milestone.closed_issues_count(member)).to eq 6
+ expect(milestone.total_issues_count(member)).to eq 9
end
it 'counts confidential issues for admin' do
- expect(milestone.percent_complete(admin)).to eq 60
+ expect(milestone.closed_issues_count(admin)).to eq 6
+ expect(milestone.total_issues_count(admin)).to eq 9
end
end
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index ee613b199ad..e17b98536fa 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -260,4 +260,16 @@ describe Noteable do
end
end
end
+
+ describe '.replyable_types' do
+ it 'exposes the replyable types' do
+ expect(described_class.replyable_types).to include('Issue', 'MergeRequest')
+ end
+ end
+
+ describe '.resolvable_types' do
+ it 'exposes the replyable types' do
+ expect(described_class.resolvable_types).to include('MergeRequest')
+ end
+ end
end
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 53df9e0bc05..7faa196623f 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -232,4 +232,17 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
end
end
end
+
+ describe 'default options' do
+ let(:cached_class) { Class.new { include ReactiveCaching } }
+
+ subject { cached_class.new }
+
+ it { expect(subject.reactive_cache_lease_timeout).to be_a(ActiveSupport::Duration) }
+ it { expect(subject.reactive_cache_refresh_interval).to be_a(ActiveSupport::Duration) }
+ it { expect(subject.reactive_cache_lifetime).to be_a(ActiveSupport::Duration) }
+
+ it { expect(subject.reactive_cache_key).to respond_to(:call) }
+ it { expect(subject.reactive_cache_worker_finder).to respond_to(:call) }
+ end
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index fa19cb47a0d..d9e1fe4b165 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -321,6 +321,14 @@ describe DiffNote do
end
describe '#supports_suggestion?' do
+ context 'when noteable does not exist' do
+ it 'returns false' do
+ allow(subject).to receive(:noteable) { nil }
+
+ expect(subject.supports_suggestion?).to be(false)
+ end
+ end
+
context 'when noteable does not support suggestions' do
it 'returns false' do
allow(subject.noteable).to receive(:supports_suggestion?) { false }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index cc777cbf749..a5c7e9db2a1 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -93,6 +93,21 @@ describe Issue do
end
end
+ describe '#sort' do
+ let(:project) { create(:project) }
+
+ context "by relative_position" do
+ let!(:issue) { create(:issue, project: project) }
+ let!(:issue2) { create(:issue, project: project, relative_position: 2) }
+ let!(:issue3) { create(:issue, project: project, relative_position: 1) }
+
+ it "sorts asc with nulls at the end" do
+ issues = project.issues.sort_by_attribute('relative_position')
+ expect(issues).to eq([issue3, issue2, issue])
+ end
+ end
+ end
+
describe '#card_attributes' do
it 'includes the author name' do
allow(subject).to receive(:author).and_return(double(name: 'Robert'))
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c72b6e9033d..c6251326c22 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -173,6 +173,42 @@ describe MergeRequest do
end
end
+ context 'for branch' do
+ before do
+ stub_feature_flags(stricter_mr_branch_name: false)
+ end
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:branch_name, :valid) do
+ 'foo' | true
+ 'foo:bar' | false
+ '+foo:bar' | false
+ 'foo bar' | false
+ '-foo' | false
+ 'HEAD' | true
+ 'refs/heads/master' | true
+ end
+
+ with_them do
+ it "validates source_branch" do
+ subject = build(:merge_request, source_branch: branch_name, target_branch: 'master')
+
+ subject.valid?
+
+ expect(subject.errors.added?(:source_branch)).to eq(!valid)
+ end
+
+ it "validates target_branch" do
+ subject = build(:merge_request, source_branch: 'master', target_branch: branch_name)
+
+ subject.valid?
+
+ expect(subject.errors.added?(:target_branch)).to eq(!valid)
+ end
+ end
+ end
+
context 'for forks' do
let(:project) { create(:project) }
let(:fork1) { fork_project(project) }
@@ -1038,19 +1074,17 @@ describe MergeRequest do
end
end
- describe "#reset_merge_when_pipeline_succeeds" do
- let(:merge_if_green) do
- create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user),
- merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" }
- end
+ describe "#auto_merge_strategy" do
+ subject { merge_request.auto_merge_strategy }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it { is_expected.to eq('merge_when_pipeline_succeeds') }
- it "sets the item to false" do
- merge_if_green.reset_merge_when_pipeline_succeeds
- merge_if_green.reload
+ context 'when auto merge is disabled' do
+ let(:merge_request) { create(:merge_request) }
- expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey
- expect(merge_if_green.merge_params["should_remove_source_branch"]).to be_nil
- expect(merge_if_green.merge_params["commit_message"]).to be_nil
+ it { is_expected.to be_nil }
end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index b82368318f2..3704a2d468d 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -31,12 +31,28 @@ describe Milestone do
end
describe 'start_date' do
- it 'adds an error when start_date is greated then due_date' do
+ it 'adds an error when start_date is greater then due_date' do
milestone = build(:milestone, start_date: Date.tomorrow, due_date: Date.yesterday)
expect(milestone).not_to be_valid
expect(milestone.errors[:due_date]).to include("must be greater than start date")
end
+
+ it 'adds an error when start_date is greater than 9999-12-31' do
+ milestone = build(:milestone, start_date: Date.new(10000, 1, 1))
+
+ expect(milestone).not_to be_valid
+ expect(milestone.errors[:start_date]).to include("date must not be after 9999-12-31")
+ end
+ end
+
+ describe 'due_date' do
+ it 'adds an error when due_date is greater than 9999-12-31' do
+ milestone = build(:milestone, due_date: Date.new(10000, 1, 1))
+
+ expect(milestone).not_to be_valid
+ expect(milestone.errors[:due_date]).to include("date must not be after 9999-12-31")
+ end
end
end
@@ -166,32 +182,10 @@ describe Milestone do
end
end
- describe '#percent_complete' do
- before do
- allow(milestone).to receive_messages(
- closed_items_count: 3,
- total_items_count: 4
- )
- end
-
- it { expect(milestone.percent_complete(user)).to eq(75) }
- end
-
describe '#can_be_closed?' do
it { expect(milestone.can_be_closed?).to be_truthy }
end
- describe '#total_items_count' do
- before do
- create :closed_issue, milestone: milestone, project: project
- create :merge_request, milestone: milestone, source_project: project
- end
-
- it 'returns total count of issues and merge requests assigned to milestone' do
- expect(milestone.total_items_count(user)).to eq 2
- end
- end
-
describe '#can_be_closed?' do
before do
milestone = create :milestone, project: project
@@ -381,21 +375,6 @@ describe Milestone do
expect(milestone_ids).to be_empty
end
end
-
- context 'when there is a milestone with a date after 294276 AD', :postgresql do
- before do
- past_milestone_project_1.update!(due_date: Date.new(294277, 1, 1))
- end
-
- it 'returns the next upcoming open milestone ID for each project and group' do
- expect(milestone_ids).to contain_exactly(
- current_milestone_project_1.id,
- current_milestone_project_2.id,
- current_milestone_group_1.id,
- current_milestone_group_2.id
- )
- end
- end
end
describe '#to_reference' do
@@ -519,4 +498,20 @@ describe Milestone do
end
end
end
+
+ describe '.reference_pattern' do
+ subject { described_class.reference_pattern }
+
+ it { is_expected.to match('gitlab-org/gitlab-ce%123') }
+ it { is_expected.to match('gitlab-org/gitlab-ce%"my-milestone"') }
+ end
+
+ describe '.link_reference_pattern' do
+ subject { described_class.link_reference_pattern }
+
+ it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/milestones/123") }
+ it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/-/milestones/123") }
+ it { is_expected.not_to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-ce/issues/123") }
+ it { is_expected.not_to match("gitlab-org/gitlab-ce/milestones/123") }
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index bfde367c47f..d80183af33e 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -147,6 +147,7 @@ describe Namespace do
namespace: namespace,
statistics: build(:project_statistics,
repository_size: 101,
+ wiki_size: 505,
lfs_objects_size: 202,
build_artifacts_size: 303,
packages_size: 404))
@@ -157,6 +158,7 @@ describe Namespace do
namespace: namespace,
statistics: build(:project_statistics,
repository_size: 10,
+ wiki_size: 50,
lfs_objects_size: 20,
build_artifacts_size: 30,
packages_size: 40))
@@ -167,8 +169,9 @@ describe Namespace do
project2
statistics = described_class.with_statistics.find(namespace.id)
- expect(statistics.storage_size).to eq 1110
+ expect(statistics.storage_size).to eq 1665
expect(statistics.repository_size).to eq 111
+ expect(statistics.wiki_size).to eq 555
expect(statistics.lfs_objects_size).to eq 222
expect(statistics.build_artifacts_size).to eq 333
expect(statistics.packages_size).to eq 444
@@ -179,6 +182,7 @@ describe Namespace do
expect(statistics.storage_size).to eq 0
expect(statistics.repository_size).to eq 0
+ expect(statistics.wiki_size).to eq 0
expect(statistics.lfs_objects_size).to eq 0
expect(statistics.build_artifacts_size).to eq 0
expect(statistics.packages_size).to eq 0
diff --git a/spec/models/pages_domain_acme_order_spec.rb b/spec/models/pages_domain_acme_order_spec.rb
new file mode 100644
index 00000000000..4ffb4fc7389
--- /dev/null
+++ b/spec/models/pages_domain_acme_order_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomainAcmeOrder do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '.expired' do
+ let!(:not_expired_order) { create(:pages_domain_acme_order) }
+ let!(:expired_order) { create(:pages_domain_acme_order, :expired) }
+
+ it 'returns only expired orders' do
+ expect(described_class.count).to eq(2)
+ expect(described_class.expired).to eq([expired_order])
+ end
+ end
+
+ describe '.find_by_domain_and_token' do
+ let!(:domain) { create(:pages_domain, domain: 'test.com') }
+ let!(:acme_order) { create(:pages_domain_acme_order, challenge_token: 'righttoken', pages_domain: domain) }
+
+ where(:domain_name, :challenge_token, :present) do
+ 'test.com' | 'righttoken' | true
+ 'test.com' | 'wrongtoken' | false
+ 'test.org' | 'righttoken' | false
+ end
+
+ with_them do
+ subject { described_class.find_by_domain_and_token(domain_name, challenge_token).present? }
+
+ it { is_expected.to eq(present) }
+ end
+ end
+
+ subject { create(:pages_domain_acme_order) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:pages_domain) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:pages_domain) }
+ it { is_expected.to validate_presence_of(:expires_at) }
+ it { is_expected.to validate_presence_of(:url) }
+ it { is_expected.to validate_presence_of(:challenge_token) }
+ it { is_expected.to validate_presence_of(:challenge_file_content) }
+ it { is_expected.to validate_presence_of(:private_key) }
+ end
+end
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index ec4d4517f82..fdc81359d34 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -81,6 +81,17 @@ describe PagesDomain do
end
end
+ describe 'when certificate is specified' do
+ let(:domain) { build(:pages_domain) }
+
+ it 'saves validity time' do
+ domain.save
+
+ expect(domain.certificate_valid_not_before).to be_like_time(Time.parse("2016-02-12 14:32:00 UTC"))
+ expect(domain.certificate_valid_not_after).to be_like_time(Time.parse("2020-04-12 14:32:00 UTC"))
+ end
+ end
+
describe 'validate certificate' do
subject { domain }
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index b81e5610e2c..7bdd2367a68 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -14,65 +14,9 @@ describe ProjectAutoDevops do
it { is_expected.to respond_to(:created_at) }
it { is_expected.to respond_to(:updated_at) }
- describe '#has_domain?' do
- context 'when domain is defined' do
- let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: 'domain.com') }
-
- it { expect(auto_devops).to have_domain }
- end
-
- context 'when domain is empty' do
- let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: '') }
-
- context 'when there is an instance domain specified' do
- before do
- allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com')
- end
-
- it { expect(auto_devops).to have_domain }
- end
-
- context 'when there is no instance domain specified' do
- before do
- allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil)
- end
-
- it { expect(auto_devops).not_to have_domain }
- end
- end
- end
-
describe '#predefined_variables' do
let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) }
- context 'when domain is defined' do
- let(:domain) { 'example.com' }
-
- it 'returns AUTO_DEVOPS_DOMAIN' do
- expect(auto_devops.predefined_variables).to include(domain_variable)
- end
- end
-
- context 'when domain is not defined' do
- let(:domain) { nil }
-
- context 'when there is an instance domain specified' do
- before do
- allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com')
- end
-
- it { expect(auto_devops.predefined_variables).to include(domain_variable) }
- end
-
- context 'when there is no instance domain specified' do
- before do
- allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil)
- end
-
- it { expect(auto_devops.predefined_variables).not_to include(domain_variable) }
- end
- end
-
context 'when deploy_strategy is manual' do
let(:auto_devops) { build_stubbed(:project_auto_devops, :manual_deployment, project: project) }
let(:expected_variables) do
@@ -105,10 +49,6 @@ describe ProjectAutoDevops do
.not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED")
end
end
-
- def domain_variable
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }
- end
end
describe '#create_gitlab_deploy_token' do
diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb
index 4aa62028169..eb3a7e527c9 100644
--- a/spec/models/project_ci_cd_setting_spec.rb
+++ b/spec/models/project_ci_cd_setting_spec.rb
@@ -21,4 +21,44 @@ describe ProjectCiCdSetting do
2.times { described_class.available? }
end
end
+
+ describe 'validations' do
+ it 'validates default_git_depth is between 0 and 1000 or nil' do
+ expect(subject).to validate_numericality_of(:default_git_depth)
+ .only_integer
+ .is_greater_than_or_equal_to(0)
+ .is_less_than_or_equal_to(1000)
+ .allow_nil
+ end
+ end
+
+ describe '#default_git_depth' do
+ let(:default_value) { described_class::DEFAULT_GIT_DEPTH }
+
+ it 'sets default value for new records' do
+ project = create(:project)
+
+ expect(project.ci_cd_settings.default_git_depth).to eq(default_value)
+ end
+
+ it 'does not set default value if present' do
+ project = build(:project)
+ project.build_ci_cd_settings(default_git_depth: 0)
+ project.save!
+
+ expect(project.reload.ci_cd_settings.default_git_depth).to eq(0)
+ end
+
+ context 'when feature flag :ci_set_project_default_git_depth is disabled' do
+ let(:project) { create(:project) }
+
+ before do
+ stub_feature_flags(ci_set_project_default_git_depth: { enabled: false } )
+ end
+
+ it 'does not set default value for new records' do
+ expect(project.ci_cd_settings.default_git_depth).to eq(nil)
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index 7742e33e901..2c86c0ec7be 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe AssemblaService do
+ include StubRequests
+
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -23,12 +25,12 @@ describe AssemblaService do
)
@sample_data = Gitlab::DataBuilder::Push.build_sample(project, user)
@api_url = 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret'
- WebMock.stub_request(:post, @api_url)
+ stub_full_request(@api_url, method: :post)
end
it "calls Assembla API" do
@assembla_service.execute(@sample_data)
- expect(WebMock).to have_requested(:post, @api_url).with(
+ expect(WebMock).to have_requested(:post, stubbed_hostname(@api_url)).with(
body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/
).once
end
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 08c510f09df..65d227a17f9 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe BambooService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
+ include StubRequests
let(:bamboo_url) { 'http://gitlab.com/bamboo' }
@@ -257,7 +258,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do
end
def stub_bamboo_request(url, status, body)
- WebMock.stub_request(:get, url).to_return(
+ stub_full_request(url).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 091d4d8f695..ca196069055 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe BuildkiteService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
+ include StubRequests
let(:project) { create(:project) }
@@ -110,10 +111,9 @@ describe BuildkiteService, :use_clean_rails_memory_store_caching do
body ||= %q({"status":"success"})
buildkite_full_url = 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123'
- WebMock.stub_request(:get, buildkite_full_url).to_return(
- status: status,
- headers: { 'Content-Type' => 'application/json' },
- body: body
- )
+ stub_full_request(buildkite_full_url)
+ .to_return(status: status,
+ headers: { 'Content-Type' => 'application/json' },
+ body: body)
end
end
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index bf4c52fc7ab..0d3dd89e93b 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe CampfireService do
+ include StubRequests
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -49,39 +51,37 @@ describe CampfireService do
it "calls Campfire API to get a list of rooms and speak in a room" do
# make sure a valid list of rooms is returned
body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms.json')
- WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return(
+
+ stub_full_request(@rooms_url).with(basic_auth: @auth).to_return(
body: body,
status: 200,
headers: @headers
)
+
# stub the speak request with the room id found in the previous request's response
speak_url = 'https://project-name.campfirenow.com/room/123/speak.json'
- WebMock.stub_request(:post, speak_url).with(basic_auth: @auth)
+ stub_full_request(speak_url, method: :post).with(basic_auth: @auth)
@campfire_service.execute(@sample_data)
- expect(WebMock).to have_requested(:get, @rooms_url).once
- expect(WebMock).to have_requested(:post, speak_url).with(
- body: /#{project.path}.*#{@sample_data[:before]}.*#{@sample_data[:after]}/
- ).once
+ expect(WebMock).to have_requested(:get, stubbed_hostname(@rooms_url)).once
+ expect(WebMock).to have_requested(:post, stubbed_hostname(speak_url))
+ .with(body: /#{project.path}.*#{@sample_data[:before]}.*#{@sample_data[:after]}/).once
end
it "calls Campfire API to get a list of rooms but shouldn't speak in a room" do
# return a list of rooms that do not contain a room named 'test-room'
body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms2.json')
- WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return(
+ stub_full_request(@rooms_url).with(basic_auth: @auth).to_return(
body: body,
status: 200,
headers: @headers
)
- # we want to make sure no request is sent to the /speak endpoint, here is a basic
- # regexp that matches this endpoint
- speak_url = 'https://verySecret:X@project-name.campfirenow.com/room/.*/speak.json'
@campfire_service.execute(@sample_data)
- expect(WebMock).to have_requested(:get, @rooms_url).once
- expect(WebMock).not_to have_requested(:post, /#{speak_url}/)
+ expect(WebMock).to have_requested(:get, 'https://8.8.8.9/rooms.json').once
+ expect(WebMock).not_to have_requested(:post, '*/room/.*/speak.json')
end
end
end
diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb
index ca17e7453b8..b85565e0c25 100644
--- a/spec/models/project_services/pipelines_email_service_spec.rb
+++ b/spec/models/project_services/pipelines_email_service_spec.rb
@@ -4,7 +4,11 @@ require 'spec_helper'
describe PipelinesEmailService, :mailer do
let(:pipeline) do
- create(:ci_pipeline, project: project, sha: project.commit('master').sha)
+ create(:ci_pipeline, :failed,
+ project: project,
+ sha: project.commit('master').sha,
+ ref: project.default_branch
+ )
end
let(:project) { create(:project, :repository) }
@@ -84,12 +88,7 @@ describe PipelinesEmailService, :mailer do
subject.test(data)
end
- context 'when pipeline is failed' do
- before do
- data[:object_attributes][:status] = 'failed'
- pipeline.update(status: 'failed')
- end
-
+ context 'when pipeline is failed and on default branch' do
it_behaves_like 'sending email'
end
@@ -101,6 +100,25 @@ describe PipelinesEmailService, :mailer do
it_behaves_like 'sending email'
end
+
+ context 'when pipeline is failed and on a non-default branch' do
+ before do
+ data[:object_attributes][:ref] = 'not-the-default-branch'
+ pipeline.update(ref: 'not-the-default-branch')
+ end
+
+ context 'with notify_only_default branch on' do
+ before do
+ subject.notify_only_default_branch = true
+ end
+
+ it_behaves_like 'sending email'
+ end
+
+ context 'with notify_only_default_branch off' do
+ it_behaves_like 'sending email'
+ end
+ end
end
describe '#execute' do
@@ -110,11 +128,6 @@ describe PipelinesEmailService, :mailer do
context 'with recipients' do
context 'with failed pipeline' do
- before do
- data[:object_attributes][:status] = 'failed'
- pipeline.update(status: 'failed')
- end
-
it_behaves_like 'sending email'
end
@@ -133,11 +146,6 @@ describe PipelinesEmailService, :mailer do
end
context 'with failed pipeline' do
- before do
- data[:object_attributes][:status] = 'failed'
- pipeline.update(status: 'failed')
- end
-
it_behaves_like 'sending email'
end
@@ -150,6 +158,40 @@ describe PipelinesEmailService, :mailer do
it_behaves_like 'not sending email'
end
end
+
+ context 'with notify_only_default_branch off' do
+ context 'with default branch' do
+ it_behaves_like 'sending email'
+ end
+
+ context 'with non default branch' do
+ before do
+ data[:object_attributes][:ref] = 'not-the-default-branch'
+ pipeline.update(ref: 'not-the-default-branch')
+ end
+
+ it_behaves_like 'sending email'
+ end
+ end
+
+ context 'with notify_only_default_branch on' do
+ before do
+ subject.notify_only_default_branch = true
+ end
+
+ context 'with default branch' do
+ it_behaves_like 'sending email'
+ end
+
+ context 'with non default branch' do
+ before do
+ data[:object_attributes][:ref] = 'not-the-default-branch'
+ pipeline.update(ref: 'not-the-default-branch')
+ end
+
+ it_behaves_like 'not sending email'
+ end
+ end
end
context 'with empty recipients list' do
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index 773b8b7890f..dde46c82df6 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe PivotaltrackerService do
+ include StubRequests
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -53,12 +55,12 @@ describe PivotaltrackerService do
end
before do
- WebMock.stub_request(:post, url)
+ stub_full_request(url, method: :post)
end
it 'posts correct message' do
service.execute(push_data)
- expect(WebMock).to have_requested(:post, url).with(
+ expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with(
body: {
'source_commit' => {
'commit_id' => '21c12ea',
@@ -85,14 +87,14 @@ describe PivotaltrackerService do
service.execute(push_data(branch: 'master'))
service.execute(push_data(branch: 'v10'))
- expect(WebMock).to have_requested(:post, url).twice
+ expect(WebMock).to have_requested(:post, stubbed_hostname(url)).twice
end
it 'does not post message if branch is not in the list' do
service.execute(push_data(branch: 'mas'))
service.execute(push_data(branch: 'v11'))
- expect(WebMock).not_to have_requested(:post, url)
+ expect(WebMock).not_to have_requested(:post, stubbed_hostname(url))
end
end
end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index d2a45f48705..380f02739bc 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe PushoverService do
+ include StubRequests
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -57,13 +59,13 @@ describe PushoverService do
sound: sound
)
- WebMock.stub_request(:post, api_url)
+ stub_full_request(api_url, method: :post, ip_address: '8.8.8.8')
end
it 'calls Pushover API' do
pushover.execute(sample_data)
- expect(WebMock).to have_requested(:post, api_url).once
+ expect(WebMock).to have_requested(:post, 'https://8.8.8.8/1/messages.json').once
end
end
end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 96dccae733b..1c434b25205 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe TeamcityService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
+ include StubRequests
let(:teamcity_url) { 'http://gitlab.com/teamcity' }
@@ -212,7 +213,7 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do
body ||= %Q({"build":{"status":"#{build_status}","id":"666"}})
- WebMock.stub_request(:get, teamcity_full_url).with(basic_auth: auth).to_return(
+ stub_full_request(teamcity_full_url).with(basic_auth: auth).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 425096d7e80..aad08b9d4aa 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1147,7 +1147,7 @@ describe Project do
allow(project).to receive(:avatar_in_git) { true }
end
- let(:avatar_path) { "/#{project.full_path}/avatar" }
+ let(:avatar_path) { "/#{project.full_path}/-/avatar" }
it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
end
@@ -3170,6 +3170,23 @@ describe Project do
end
end
+ describe '.ids_with_milestone_available_for' do
+ let!(:user) { create(:user) }
+
+ it 'returns project ids with milestones available for user' do
+ project_1 = create(:project, :public, :merge_requests_disabled, :issues_disabled)
+ project_2 = create(:project, :public, :merge_requests_disabled)
+ project_3 = create(:project, :public, :issues_disabled)
+ project_4 = create(:project, :public)
+ project_4.project_feature.update(issues_access_level: ProjectFeature::PRIVATE, merge_requests_access_level: ProjectFeature::PRIVATE )
+
+ project_ids = described_class.ids_with_milestone_available_for(user).pluck(:id)
+
+ expect(project_ids).to include(project_2.id, project_3.id)
+ expect(project_ids).not_to include(project_1.id, project_4.id)
+ end
+ end
+
describe '.with_feature_available_for_user' do
let(:user) { create(:user) }
let(:feature) { MergeRequest }
@@ -3975,64 +3992,6 @@ describe Project do
end
end
- describe '#auto_devops_variables' do
- set(:project) { create(:project) }
-
- subject { project.auto_devops_variables }
-
- context 'when enabled in instance settings' do
- before do
- stub_application_setting(auto_devops_enabled: true)
- end
-
- context 'when domain is empty' do
- before do
- stub_application_setting(auto_devops_domain: nil)
- end
-
- it 'variables does not include AUTO_DEVOPS_DOMAIN' do
- is_expected.not_to include(domain_variable)
- end
- end
-
- context 'when domain is configured' do
- before do
- stub_application_setting(auto_devops_domain: 'example.com')
- end
-
- it 'variables includes AUTO_DEVOPS_DOMAIN' do
- is_expected.to include(domain_variable)
- end
- end
- end
-
- context 'when explicitly enabled' do
- context 'when domain is empty' do
- before do
- create(:project_auto_devops, project: project, domain: nil)
- end
-
- it 'variables does not include AUTO_DEVOPS_DOMAIN' do
- is_expected.not_to include(domain_variable)
- end
- end
-
- context 'when domain is configured' do
- before do
- create(:project_auto_devops, project: project, domain: 'example.com')
- end
-
- it 'variables includes AUTO_DEVOPS_DOMAIN' do
- is_expected.to include(domain_variable)
- end
- end
- end
-
- def domain_variable
- { key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true }
- end
- end
-
describe '#latest_successful_builds_for' do
let(:project) { build(:project) }
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 738398a06f9..1cb49d83ffa 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -11,21 +11,37 @@ describe ProjectStatistics do
it { is_expected.to belong_to(:namespace) }
end
+ describe 'scopes' do
+ describe '.for_project_ids' do
+ it 'returns only requested projects' do
+ stats = create_list(:project_statistics, 3)
+ project_ids = stats[0..1].map { |s| s.project_id }
+ expected_ids = stats[0..1].map { |s| s.id }
+
+ requested_stats = described_class.for_project_ids(project_ids).pluck(:id)
+
+ expect(requested_stats).to eq(expected_ids)
+ end
+ end
+ end
+
describe 'statistics columns' do
it "support values up to 8 exabytes" do
statistics.update!(
commit_count: 8.exabytes - 1,
repository_size: 2.exabytes,
+ wiki_size: 1.exabytes,
lfs_objects_size: 2.exabytes,
- build_artifacts_size: 4.exabytes - 1
+ build_artifacts_size: 3.exabytes - 1
)
statistics.reload
expect(statistics.commit_count).to eq(8.exabytes - 1)
expect(statistics.repository_size).to eq(2.exabytes)
+ expect(statistics.wiki_size).to eq(1.exabytes)
expect(statistics.lfs_objects_size).to eq(2.exabytes)
- expect(statistics.build_artifacts_size).to eq(4.exabytes - 1)
+ expect(statistics.build_artifacts_size).to eq(3.exabytes - 1)
expect(statistics.storage_size).to eq(8.exabytes - 1)
end
end
@@ -33,6 +49,7 @@ describe ProjectStatistics do
describe '#total_repository_size' do
it "sums repository and LFS object size" do
statistics.repository_size = 2
+ statistics.wiki_size = 6
statistics.lfs_objects_size = 3
statistics.build_artifacts_size = 4
@@ -40,10 +57,17 @@ describe ProjectStatistics do
end
end
+ describe '#wiki_size' do
+ it "is initialized with not null value" do
+ expect(statistics.wiki_size).to eq 0
+ end
+ end
+
describe '#refresh!' do
before do
allow(statistics).to receive(:update_commit_count)
allow(statistics).to receive(:update_repository_size)
+ allow(statistics).to receive(:update_wiki_size)
allow(statistics).to receive(:update_lfs_objects_size)
allow(statistics).to receive(:update_storage_size)
end
@@ -56,6 +80,7 @@ describe ProjectStatistics do
it "sums all counters" do
expect(statistics).to have_received(:update_commit_count)
expect(statistics).to have_received(:update_repository_size)
+ expect(statistics).to have_received(:update_wiki_size)
expect(statistics).to have_received(:update_lfs_objects_size)
end
end
@@ -69,6 +94,45 @@ describe ProjectStatistics do
expect(statistics).to have_received(:update_lfs_objects_size)
expect(statistics).not_to have_received(:update_commit_count)
expect(statistics).not_to have_received(:update_repository_size)
+ expect(statistics).not_to have_received(:update_wiki_size)
+ end
+ end
+
+ context 'without repositories' do
+ it 'does not crash' do
+ expect(project.repository.exists?).to be_falsey
+ expect(project.wiki.repository.exists?).to be_falsey
+
+ statistics.refresh!
+
+ expect(statistics).to have_received(:update_commit_count)
+ expect(statistics).to have_received(:update_repository_size)
+ expect(statistics).to have_received(:update_wiki_size)
+ expect(statistics.repository_size).to eq(0)
+ expect(statistics.commit_count).to eq(0)
+ expect(statistics.wiki_size).to eq(0)
+ end
+ end
+
+ context 'with deleted repositories' do
+ let(:project) { create(:project, :repository, :wiki_repo) }
+
+ before do
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ FileUtils.rm_rf(project.repository.path)
+ FileUtils.rm_rf(project.wiki.repository.path)
+ end
+ end
+
+ it 'does not crash' do
+ statistics.refresh!
+
+ expect(statistics).to have_received(:update_commit_count)
+ expect(statistics).to have_received(:update_repository_size)
+ expect(statistics).to have_received(:update_wiki_size)
+ expect(statistics.repository_size).to eq(0)
+ expect(statistics.commit_count).to eq(0)
+ expect(statistics.wiki_size).to eq(0)
end
end
end
@@ -95,6 +159,17 @@ describe ProjectStatistics do
end
end
+ describe '#update_wiki_size' do
+ before do
+ allow(project.wiki.repository).to receive(:size).and_return(34)
+ statistics.update_wiki_size
+ end
+
+ it "stores the size of the wiki" do
+ expect(statistics.wiki_size).to eq 34.megabytes
+ end
+ end
+
describe '#update_lfs_objects_size' do
let!(:lfs_object1) { create(:lfs_object, size: 23.megabytes) }
let!(:lfs_object2) { create(:lfs_object, size: 34.megabytes) }
@@ -114,6 +189,19 @@ describe ProjectStatistics do
it "sums all storage counters" do
statistics.update!(
repository_size: 2,
+ wiki_size: 4,
+ lfs_objects_size: 3
+ )
+
+ statistics.reload
+
+ expect(statistics.storage_size).to eq 9
+ end
+
+ it 'works during wiki_size backfill' do
+ statistics.update!(
+ repository_size: 2,
+ wiki_size: nil,
lfs_objects_size: 3
)
diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb
index 7eeb2fae57d..cb52f154299 100644
--- a/spec/models/resource_label_event_spec.rb
+++ b/spec/models/resource_label_event_spec.rb
@@ -82,13 +82,13 @@ RSpec.describe ResourceLabelEvent, type: :model do
end
it 'returns true if markdown is outdated' do
- subject.attributes = { cached_markdown_version: ((CacheMarkdownField::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 }
+ subject.attributes = { cached_markdown_version: ((Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 }
expect(subject.outdated_markdown?).to be true
end
it 'returns false if label and reference are set' do
- subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 }
+ subject.attributes = { reference: 'whatever', cached_markdown_version: Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
expect(subject.outdated_markdown?).to be false
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 8075fcade5f..ed0e82ef179 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -66,7 +66,7 @@ describe ProjectPolicy do
%i[
change_namespace change_visibility_level rename_project remove_project
archive_project remove_fork_project destroy_merge_request destroy_issue
- set_issue_iid set_issue_created_at set_note_created_at
+ set_issue_iid set_issue_created_at set_issue_updated_at set_note_created_at
]
end
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index bb1db9a3d51..eacf383be7d 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -14,6 +14,16 @@ describe BlobPresenter, :seed_helper do
end
let(:blob) { Blob.new(git_blob) }
+ describe '.web_url' do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.blobs.first, repository) }
+
+ subject { described_class.new(blob) }
+
+ it { expect(subject.web_url).to eq("http://localhost/#{project.full_path}/blob/#{blob.commit_id}/#{blob.path}") }
+ end
+
describe '#highlight' do
subject { described_class.new(blob) }
diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb
index ad6cb012d0b..620f34bac79 100644
--- a/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -119,39 +119,31 @@ describe Ci::BuildRunnerPresenter do
end
describe '#git_depth' do
- subject { presenter.git_depth }
-
let(:build) { create(:ci_build) }
- it 'returns the correct git depth' do
- is_expected.to eq(0)
- end
+ subject(:git_depth) { presenter.git_depth }
context 'when GIT_DEPTH variable is specified' do
before do
create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline)
end
- it 'returns the correct git depth' do
- is_expected.to eq(1)
+ it 'returns its value' do
+ expect(git_depth).to eq(1)
end
end
- context 'when pipeline is detached merge request pipeline' do
- let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
- let(:pipeline) { merge_request.all_pipelines.first }
- let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) }
+ it 'defaults to git depth setting for the project' do
+ expect(git_depth).to eq(build.project.default_git_depth)
+ end
- it 'returns the default git depth for pipelines for merge requests' do
- is_expected.to eq(described_class::DEFAULT_GIT_DEPTH_MERGE_REQUEST)
+ context 'when feature flag :ci_project_git_depth is disabled' do
+ before do
+ stub_feature_flags(ci_project_git_depth: { enabled: false })
end
- context 'when pipeline is legacy detached merge request pipeline' do
- let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) }
-
- it 'behaves as branch pipeline' do
- is_expected.to eq(0)
- end
+ it 'defaults to 0' do
+ expect(git_depth).to eq(0)
end
end
end
@@ -162,24 +154,24 @@ describe Ci::BuildRunnerPresenter do
let(:build) { create(:ci_build) }
it 'returns the correct refspecs' do
- is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
- '+refs/heads/*:refs/remotes/origin/*')
+ is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
end
- context 'when GIT_DEPTH variable is specified' do
- before do
- create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline)
- end
+ context 'when ref is tag' do
+ let(:build) { create(:ci_build, :tag) }
it 'returns the correct refspecs' do
- is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
+ is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}")
end
- context 'when ref is tag' do
- let(:build) { create(:ci_build, :tag) }
+ context 'when GIT_DEPTH is zero' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline)
+ end
it 'returns the correct refspecs' do
- is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}")
+ is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
+ '+refs/heads/*:refs/remotes/origin/*')
end
end
end
@@ -194,12 +186,24 @@ describe Ci::BuildRunnerPresenter do
.to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head')
end
+ context 'when GIT_DEPTH is zero' do
+ before do
+ create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 0, pipeline: build.pipeline)
+ end
+
+ it 'returns the correct refspecs' do
+ is_expected
+ .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head',
+ '+refs/heads/*:refs/remotes/origin/*',
+ '+refs/tags/*:refs/tags/*')
+ end
+ end
+
context 'when pipeline is legacy detached merge request pipeline' do
let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) }
it 'returns the correct refspecs' do
- is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
- '+refs/heads/*:refs/remotes/origin/*')
+ is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
end
end
end
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 42701a5f8d1..7054a70e2ed 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -158,46 +158,6 @@ describe Clusters::ClusterPresenter do
it { is_expected.to include(cluster.name) }
end
- describe '#can_toggle_cluster' do
- let(:user) { create(:user) }
-
- before do
- allow(cluster).to receive(:current_user).and_return(user)
- end
-
- subject { described_class.new(cluster).can_toggle_cluster? }
-
- context 'when user can update' do
- before do
- allow_any_instance_of(described_class).to receive(:can?).with(user, :update_cluster, cluster).and_return(true)
- end
-
- context 'when cluster is created' do
- before do
- allow(cluster).to receive(:created?).and_return(true)
- end
-
- it { is_expected.to eq(true) }
- end
-
- context 'when cluster is not created' do
- before do
- allow(cluster).to receive(:created?).and_return(false)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
- context 'when user can not update' do
- before do
- allow_any_instance_of(described_class).to receive(:can?).with(user, :update_cluster, cluster).and_return(false)
- end
-
- it { is_expected.to eq(false) }
- end
- end
-
describe '#cluster_type_description' do
subject { described_class.new(cluster).cluster_type_description }
diff --git a/spec/presenters/issue_presenter_spec.rb b/spec/presenters/issue_presenter_spec.rb
new file mode 100644
index 00000000000..8e24559341b
--- /dev/null
+++ b/spec/presenters/issue_presenter_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IssuePresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:issue) { create(:issue, project: project) }
+ let(:presenter) { described_class.new(issue, current_user: user) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ describe '#web_url' do
+ it 'returns correct path' do
+ expect(presenter.web_url).to eq "http://localhost/#{group.name}/#{project.name}/issues/#{issue.iid}"
+ end
+ end
+
+ describe '#issue_path' do
+ it 'returns correct path' do
+ expect(presenter.issue_path).to eq "/#{group.name}/#{project.name}/issues/#{issue.iid}"
+ end
+ end
+end
diff --git a/spec/presenters/label_presenter_spec.rb b/spec/presenters/label_presenter_spec.rb
index fae8188670f..d566da7c872 100644
--- a/spec/presenters/label_presenter_spec.rb
+++ b/spec/presenters/label_presenter_spec.rb
@@ -62,4 +62,32 @@ describe LabelPresenter do
expect(label.can_subscribe_to_label_in_different_levels?).to be_falsey
end
end
+
+ describe '#project_label?' do
+ context 'with group label' do
+ subject { group_label.project_label? }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'with project label' do
+ subject { label.project_label? }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#subject_name' do
+ context 'with group label' do
+ subject { group_label.subject_name }
+
+ it { is_expected.to eq(group_label.group.name) }
+ end
+
+ context 'with project label' do
+ subject { label.subject_name }
+
+ it { is_expected.to eq(label.project.name) }
+ end
+ end
end
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index 0e1aed42cc5..6408b0bd748 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -207,25 +207,25 @@ describe MergeRequestPresenter do
end
end
- describe '#cancel_merge_when_pipeline_succeeds_path' do
+ describe '#cancel_auto_merge_path' do
subject do
described_class.new(resource, current_user: user)
- .cancel_merge_when_pipeline_succeeds_path
+ .cancel_auto_merge_path
end
context 'when can cancel mwps' do
it 'returns path' do
- allow(resource).to receive(:can_cancel_merge_when_pipeline_succeeds?)
+ allow(resource).to receive(:can_cancel_auto_merge?)
.with(user)
.and_return(true)
- is_expected.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/cancel_merge_when_pipeline_succeeds")
+ is_expected.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/cancel_auto_merge")
end
end
context 'when cannot cancel mwps' do
it 'returns nil' do
- allow(resource).to receive(:can_cancel_merge_when_pipeline_succeeds?)
+ allow(resource).to receive(:can_cancel_auto_merge?)
.with(user)
.and_return(false)
diff --git a/spec/presenters/tree_entry_presenter_spec.rb b/spec/presenters/tree_entry_presenter_spec.rb
new file mode 100644
index 00000000000..d74ee5dc28f
--- /dev/null
+++ b/spec/presenters/tree_entry_presenter_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe TreeEntryPresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:tree) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.trees.first, repository) }
+ let(:presenter) { described_class.new(tree) }
+
+ describe '.web_url' do
+ it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/tree/#{tree.commit_id}/#{tree.path}") }
+ end
+end
diff --git a/spec/rack_servers/puma_spec.rb b/spec/rack_servers/puma_spec.rb
index 8290473821c..a4b37905af3 100644
--- a/spec/rack_servers/puma_spec.rb
+++ b/spec/rack_servers/puma_spec.rb
@@ -20,7 +20,7 @@ describe 'Puma' do
File.write(config_path, config_lines)
cmd = %W[puma -e test -C #{config_path} #{File.join(__dir__, 'configs/config.ru')}]
- @puma_master_pid = spawn(*cmd)
+ @puma_master_pid = spawn({ 'DISABLE_PUMA_WORKER_KILLER' => '1' }, *cmd)
wait_puma_boot!(@puma_master_pid, File.join(project_root, 'tmp/tests/puma-worker-ready'))
WebMock.allow_net_connect!
end
diff --git a/spec/requests/api/circuit_breakers_spec.rb b/spec/requests/api/circuit_breakers_spec.rb
deleted file mode 100644
index 6c7cb151c74..00000000000
--- a/spec/requests/api/circuit_breakers_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'spec_helper'
-
-describe API::CircuitBreakers do
- set(:user) { create(:user) }
- set(:admin) { create(:admin) }
-
- describe 'GET circuit_breakers/repository_storage' do
- it 'returns a 401 for anonymous users' do
- get api('/circuit_breakers/repository_storage')
-
- expect(response).to have_gitlab_http_status(401)
- end
-
- it 'returns a 403 for users' do
- get api('/circuit_breakers/repository_storage', user)
-
- expect(response).to have_gitlab_http_status(403)
- end
-
- it 'returns an Array of storages' do
- get api('/circuit_breakers/repository_storage', admin)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_kind_of(Array)
- expect(json_response).to be_empty
- end
-
- describe 'GET circuit_breakers/repository_storage/failing' do
- it 'returns an array of failing storages' do
- get api('/circuit_breakers/repository_storage/failing', admin)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_kind_of(Array)
- expect(json_response).to be_empty
- end
- end
- end
-
- describe 'DELETE circuit_breakers/repository_storage' do
- it 'clears all circuit_breakers' do
- delete api('/circuit_breakers/repository_storage', admin)
-
- expect(response).to have_gitlab_http_status(204)
- end
- end
-end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index a132b85b878..f104da6ebba 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
require 'mime/types'
describe API::Commits do
+ include ProjectForksHelper
+
let(:user) { create(:user) }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
@@ -317,6 +319,96 @@ describe API::Commits do
expect(response).to have_gitlab_http_status(201)
end
end
+
+ context 'when the API user is a guest' do
+ def last_commit_id(project, branch_name)
+ project.repository.find_branch(branch_name)&.dereferenced_target&.id
+ end
+
+ let(:public_project) { create(:project, :public, :repository) }
+ let!(:url) { "/projects/#{public_project.id}/repository/commits" }
+ let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } }
+
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ context 'when start_project is provided' do
+ context 'when posting to a forked project the user owns' do
+ let!(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) }
+ let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+
+ before do
+ valid_c_params[:start_branch] = "master"
+ valid_c_params[:branch] = "patch"
+ end
+
+ context 'identified by Integer (id)' do
+ before do
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+ end
+
+ context 'identified by String (full_path)' do
+ before do
+ valid_c_params[:start_project] = public_project.full_path
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+
+ expect(response).to have_gitlab_http_status(201)
+ end
+ end
+ end
+
+ context 'when the target project is not part of the fork network of start_project' do
+ let(:unrelated_project) { create(:project, :public, :repository, creator: guest) }
+ let!(:url) { "/projects/#{unrelated_project.id}/repository/commits" }
+
+ before do
+ valid_c_params[:start_branch] = "master"
+ valid_c_params[:branch] = "patch"
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
+ context 'when posting to a forked project the user does not have write access' do
+ let!(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) }
+ let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+
+ before do
+ valid_c_params[:start_branch] = "master"
+ valid_c_params[:branch] = "patch"
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
end
describe 'delete' do
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index dd518274f82..28676bb02f4 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -3,41 +3,101 @@ require 'spec_helper'
describe 'GitlabSchema configurations' do
include GraphqlHelpers
- let(:project) { create(:project, :repository) }
- let(:query) { graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) }
- let(:current_user) { create(:user) }
+ set(:project) { create(:project) }
- describe '#max_complexity' do
- context 'when complexity is too high' do
- it 'shows an error' do
- allow(GitlabSchema).to receive(:max_query_complexity).and_return 1
+ shared_examples 'imposing query limits' do
+ describe '#max_complexity' do
+ context 'when complexity is too high' do
+ it 'shows an error' do
+ allow(GitlabSchema).to receive(:max_query_complexity).and_return 1
- post_graphql(query, current_user: nil)
+ subject
- expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1')
+ expect(graphql_errors.flatten.first['message']).to include('which exceeds max complexity of 1')
+ end
+ end
+ end
+
+ describe '#max_depth' do
+ context 'when query depth is too high' do
+ it 'shows error' do
+ errors = { "message" => "Query has depth of 2, which exceeds max depth of 1" }
+ allow(GitlabSchema).to receive(:max_query_depth).and_return 1
+
+ subject
+
+ expect(graphql_errors.flatten).to include(errors)
+ end
+ end
+
+ context 'when query depth is within range' do
+ it 'has no error' do
+ allow(GitlabSchema).to receive(:max_query_depth).and_return 5
+
+ subject
+
+ expect(Array.wrap(graphql_errors).compact).to be_empty
+ end
end
end
end
- describe '#max_depth' do
- context 'when query depth is too high' do
- it 'shows error' do
- errors = [{ "message" => "Query has depth of 2, which exceeds max depth of 1" }]
- allow(GitlabSchema).to receive(:max_query_depth).and_return 1
+ context 'regular queries' do
+ subject do
+ query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description))
+ post_graphql(query)
+ end
+
+ it_behaves_like 'imposing query limits'
+ end
+
+ context 'multiplexed queries' do
+ let(:current_user) { nil }
+
+ subject do
+ queries = [
+ { query: graphql_query_for('project', { 'fullPath' => '$fullPath' }, %w(id name description)) },
+ { query: graphql_query_for('echo', { 'text' => "$test" }, []), variables: { "test" => "Hello world" } },
+ { query: graphql_query_for('project', { 'fullPath' => project.full_path }, "userPermissions { createIssue }") }
+ ]
+
+ post_multiplex(queries, current_user: current_user)
+ end
+
+ it 'does not authenticate all queries' do
+ subject
+
+ expect(json_response.last['data']['project']).to be_nil
+ end
- post_graphql(query)
+ it_behaves_like 'imposing query limits' do
+ it "fails all queries when only one of the queries is too complex" do
+ # The `project` query above has a complexity of 5
+ allow(GitlabSchema).to receive(:max_query_complexity).and_return 4
- expect(graphql_errors).to eq(errors)
+ subject
+
+ # Expect a response for each query, even though it will be empty
+ expect(json_response.size).to eq(3)
+ json_response.each do |single_query_response|
+ expect(single_query_response).not_to have_key('data')
+ end
+
+ # Expect errors for each query
+ expect(graphql_errors.size).to eq(3)
+ graphql_errors.each do |single_query_errors|
+ expect(single_query_errors.first['message']).to include('which exceeds max complexity of 4')
+ end
end
end
- context 'when query depth is within range' do
- it 'has no error' do
- allow(GitlabSchema).to receive(:max_query_depth).and_return 5
+ context 'authentication' do
+ let(:current_user) { project.owner }
- post_graphql(query)
+ it 'authenticates all queries' do
+ subject
- expect(graphql_errors).to be_nil
+ expect(json_response.last['data']['project']['userPermissions']['createIssue']).to be(true)
end
end
end
@@ -51,4 +111,40 @@ describe 'GitlabSchema configurations' do
expect(graphql_errors).to be_nil
end
end
+
+ context 'logging' do
+ let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) }
+
+ it 'logs the query complexity and depth' do
+ analyzer_memo = {
+ query_string: query,
+ variables: {}.to_s,
+ complexity: 181,
+ depth: 0,
+ duration: 7
+ }
+
+ expect_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:duration).and_return(7)
+ expect(Gitlab::GraphqlLogger).to receive(:info).with(analyzer_memo)
+
+ post_graphql(query, current_user: nil)
+ end
+
+ it 'logs using `format_message`' do
+ expect_any_instance_of(Gitlab::GraphqlLogger).to receive(:format_message)
+
+ post_graphql(query, current_user: nil)
+ end
+ end
+
+ context "global id's" do
+ it 'uses GlobalID to expose ids' do
+ post_graphql(graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id)),
+ current_user: project.owner)
+
+ parsed_id = GlobalID.parse(graphql_data['project']['id'])
+
+ expect(parsed_id).to eq(project.to_global_id)
+ end
+ end
end
diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb
index 8ff95cc9af2..e0f1e4dbe9e 100644
--- a/spec/requests/api/graphql/group_query_spec.rb
+++ b/spec/requests/api/graphql/group_query_spec.rb
@@ -56,7 +56,7 @@ describe 'getting group information' do
post_graphql(group_query(group1), current_user: user1)
expect(response).to have_gitlab_http_status(200)
- expect(graphql_data['group']['id']).to eq(group1.id.to_s)
+ expect(graphql_data['group']['id']).to eq(group1.to_global_id.to_s)
expect(graphql_data['group']['name']).to eq(group1.name)
expect(graphql_data['group']['path']).to eq(group1.path)
expect(graphql_data['group']['description']).to eq(group1.description)
@@ -86,17 +86,18 @@ describe 'getting group information' do
end
it 'avoids N+1 queries' do
- post_graphql(group_query(group1), current_user: admin)
-
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(group_query(group1), current_user: admin)
end.count
- create(:project, namespace: group1)
+ queries = [{ query: group_query(group1) },
+ { query: group_query(group2) }]
expect do
- post_graphql(group_query(group1), current_user: admin)
+ post_multiplex(queries, current_user: admin)
end.not_to exceed_query_limit(control_count)
+
+ expect(graphql_errors).to contain_exactly(nil, nil)
end
end
diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb
new file mode 100644
index 00000000000..844fd979285
--- /dev/null
+++ b/spec/requests/api/graphql/multiplexed_queries_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'Multiplexed queries' do
+ include GraphqlHelpers
+
+ it 'returns responses for multiple queries' do
+ queries = [
+ { query: 'query($text: String) { echo(text: $text) }',
+ variables: { 'text' => 'Hello' } },
+ { query: 'query($text: String) { echo(text: $text) }',
+ variables: { 'text' => 'World' } }
+ ]
+
+ post_multiplex(queries)
+
+ first_response = json_response.first['data']['echo']
+ second_response = json_response.last['data']['echo']
+
+ expect(first_response).to eq('nil says: Hello')
+ expect(second_response).to eq('nil says: World')
+ end
+
+ it 'returns error and data combinations' do
+ queries = [
+ { query: 'query($text: String) { broken query }' },
+ { query: 'query working($text: String) { echo(text: $text) }',
+ variables: { 'text' => 'World' } }
+ ]
+
+ post_multiplex(queries)
+
+ first_response = json_response.first['errors']
+ second_response = json_response.last['data']['echo']
+
+ expect(first_response).not_to be_empty
+ expect(second_response).to eq('nil says: World')
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
index 8f427d71a32..d75f0df9fd3 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
@@ -11,7 +11,7 @@ describe 'Setting WIP status of a merge request' do
let(:mutation) do
variables = {
project_path: project.full_path,
- iid: merge_request.iid
+ iid: merge_request.iid.to_s
}
graphql_mutation(:merge_request_set_wip, variables.merge(input))
end
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
new file mode 100644
index 00000000000..de1cd9586b6
--- /dev/null
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'getting projects', :nested_groups do
+ include GraphqlHelpers
+
+ let(:group) { create(:group) }
+ let!(:project) { create(:project, namespace: subject) }
+ let(:nested_group) { create(:group, parent: group) }
+ let!(:nested_project) { create(:project, group: nested_group) }
+ let!(:public_project) { create(:project, :public, namespace: subject) }
+ let(:user) { create(:user) }
+ let(:include_subgroups) { true }
+
+ subject { group }
+
+ let(:query) do
+ graphql_query_for(
+ 'namespace',
+ { 'fullPath' => subject.full_path },
+ <<~QUERY
+ projects(includeSubgroups: #{include_subgroups}) {
+ edges {
+ node {
+ #{all_graphql_fields_for('Project')}
+ }
+ }
+ }
+ QUERY
+ )
+ end
+
+ before do
+ group.add_owner(user)
+ end
+
+ shared_examples 'a graphql namespace' do
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: user)
+ end
+ end
+
+ it "includes the packages size if the user can read the statistics" do
+ post_graphql(query, current_user: user)
+
+ count = if include_subgroups
+ subject.all_projects.count
+ else
+ subject.projects.count
+ end
+
+ expect(graphql_data['namespace']['projects']['edges'].size).to eq(count)
+ end
+
+ context 'with no user' do
+ it 'finds only public projects' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data['namespace']['projects']['edges'].size).to eq(1)
+ project = graphql_data['namespace']['projects']['edges'][0]['node']
+ expect(project['id']).to eq(public_project.to_global_id.to_s)
+ end
+ end
+ end
+
+ it_behaves_like 'a graphql namespace'
+
+ context 'when the namespace is a user' do
+ subject { user.namespace }
+ let(:include_subgroups) { false }
+
+ it_behaves_like 'a graphql namespace'
+ end
+
+ context 'when not including subgroups' do
+ let(:include_subgroups) { false }
+
+ it_behaves_like 'a graphql namespace'
+ end
+end
diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb
new file mode 100644
index 00000000000..8683fa1f390
--- /dev/null
+++ b/spec/requests/api/graphql/project/project_statistics_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'rendering namespace statistics' do
+ include GraphqlHelpers
+
+ let(:project) { create(:project) }
+ let!(:project_statistics) { create(:project_statistics, project: project, packages_size: 5.megabytes) }
+ let(:user) { create(:user) }
+
+ let(:query) do
+ graphql_query_for('project',
+ { 'fullPath' => project.full_path },
+ "statistics { #{all_graphql_fields_for('ProjectStatistics')} }")
+ end
+
+ before do
+ project.add_reporter(user)
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: user)
+ end
+ end
+
+ it "includes the packages size if the user can read the statistics" do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data['project']['statistics']['packagesSize']).to eq(5.megabytes)
+ end
+
+ context 'when the project is public' do
+ let(:project) { create(:project, :public) }
+
+ it 'includes the statistics regardless of the user' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data['project']['statistics']).to be_present
+ end
+ end
+end
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index cca87c16f27..656d6f8b50b 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -16,6 +16,54 @@ describe 'GraphQL' do
end
end
+ context 'logging' do
+ shared_examples 'logging a graphql query' do
+ let(:expected_params) do
+ { query_string: query, variables: variables.to_s, duration: anything, depth: 1, complexity: 1 }
+ end
+
+ it 'logs a query with the expected params' do
+ expect(Gitlab::GraphqlLogger).to receive(:info).with(expected_params).once
+
+ post_graphql(query, variables: variables)
+ end
+
+ it 'does not instantiate any query analyzers' do # they are static and re-used
+ expect(GraphQL::Analysis::QueryComplexity).not_to receive(:new)
+ expect(GraphQL::Analysis::QueryDepth).not_to receive(:new)
+
+ 2.times { post_graphql(query, variables: variables) }
+ end
+ end
+
+ context 'with no variables' do
+ let(:variables) { {} }
+
+ it_behaves_like 'logging a graphql query'
+ end
+
+ context 'with variables' do
+ let(:variables) do
+ { "foo" => "bar" }
+ end
+
+ it_behaves_like 'logging a graphql query'
+ end
+
+ context 'when there is an error in the logger' do
+ before do
+ allow_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:process_variables).and_raise(StandardError.new("oh noes!"))
+ end
+
+ it 'logs the exception in Sentry and continues with the request' do
+ expect(Gitlab::Sentry).to receive(:track_exception).at_least(1).times
+ expect(Gitlab::GraphqlLogger).to receive(:info)
+
+ post_graphql(query, variables: {})
+ end
+ end
+ end
+
context 'invalid variables' do
it 'returns an error' do
post_graphql(query, variables: "This is not JSON")
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 7176bc23e34..c41408fba65 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -90,8 +90,9 @@ describe API::Groups do
it "includes statistics if requested" do
attributes = {
- storage_size: 702,
+ storage_size: 1158,
repository_size: 123,
+ wiki_size: 456,
lfs_objects_size: 234,
build_artifacts_size: 345
}.stringify_keys
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index a07d7673345..0b0f754ab57 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -172,7 +172,9 @@ describe API::Issues do
end
it 'returns 404 when project does not exist' do
- get api('/projects/1000/issues', non_member)
+ max_project_id = Project.maximum(:id).to_i
+
+ get api("/projects/#{max_project_id + 1}/issues", non_member)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index 9b9cc778fb3..f32ffd1c77b 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -276,14 +276,6 @@ describe API::Issues do
it 'returns issues with no assignee' do
issue2 = create(:issue, author: user2, project: project)
- get api('/issues', user), params: { assignee_id: 0, scope: 'all' }
-
- expect_paginated_array_response(issue2.id)
- end
-
- it 'returns issues with no assignee' do
- issue2 = create(:issue, author: user2, project: project)
-
get api('/issues', user), params: { assignee_id: 'None', scope: 'all' }
expect_paginated_array_response(issue2.id)
@@ -496,18 +488,6 @@ describe API::Issues do
expect_paginated_array_response(closed_issue.id)
end
-
- it 'returns an array of issues with no label when using the legacy No+Label filter' do
- get api('/issues', user), params: { labels: 'No Label' }
-
- expect_paginated_array_response(closed_issue.id)
- end
-
- it 'returns an array of issues with no label when using the legacy No+Label filter with labels param as array' do
- get api('/issues', user), params: { labels: ['No Label'] }
-
- expect_paginated_array_response(closed_issue.id)
- end
end
it 'returns an empty array if no issue matches milestone' do
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 43462913497..89ee6f896f9 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -864,7 +864,7 @@ describe API::Jobs do
end
describe 'POST /projects/:id/jobs/:job_id/retry' do
- let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
+ let(:job) { create(:ci_build, :canceled, pipeline: pipeline) }
before do
post api("/projects/#{project.id}/jobs/#{job.id}/retry", api_user)
@@ -874,7 +874,7 @@ describe API::Jobs do
context 'user with :update_build permission' do
it 'retries non-running job' do
expect(response).to have_gitlab_http_status(201)
- expect(project.builds.first.status).to eq('failed')
+ expect(project.builds.first.status).to eq('canceled')
expect(json_response['status']).to eq('pending')
end
end
@@ -913,8 +913,8 @@ describe API::Jobs do
expect(response).to have_gitlab_http_status(201)
expect(job.job_artifacts.count).to eq(0)
expect(job.trace.exist?).to be_falsy
- expect(job.artifacts_file.exists?).to be_falsy
- expect(job.artifacts_metadata.exists?).to be_falsy
+ expect(job.artifacts_file.present?).to be_falsy
+ expect(job.artifacts_metadata.present?).to be_falsy
expect(job.has_job_artifacts?).to be_falsy
end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 48869cab4da..55f38079b1f 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -132,6 +132,19 @@ describe API::Members do
expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id, nested_user.id, project_user.id, linked_group_user.id]
end
+ it 'returns only one member for each user without returning duplicated members' do
+ linked_group.add_developer(developer)
+
+ get api("/projects/#{project.id}/members/all", developer)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |u| u['id'] }).to eq [developer.id, maintainer.id, nested_user.id, project_user.id, linked_group_user.id]
+ expect(json_response.map { |u| u['access_level'] }).to eq [Gitlab::Access::DEVELOPER, Gitlab::Access::OWNER, Gitlab::Access::DEVELOPER,
+ Gitlab::Access::DEVELOPER, Gitlab::Access::DEVELOPER]
+ end
+
it 'finds all group members including inherited members' do
get api("/groups/#{nested_group.id}/members/all", developer)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 5c94a87529b..9f9180bc8c9 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1473,7 +1473,7 @@ describe API::MergeRequests do
end
it "enables merge when pipeline succeeds if the pipeline is active" do
- allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
+ allow_any_instance_of(MergeRequest).to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
allow(pipeline).to receive(:active?).and_return(true)
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true }
@@ -1484,7 +1484,7 @@ describe API::MergeRequests do
end
it "enables merge when pipeline succeeds if the pipeline is active and only_allow_merge_if_pipeline_succeeds is true" do
- allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
+ allow_any_instance_of(MergeRequest).to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
allow(pipeline).to receive(:active?).and_return(true)
project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true)
@@ -1950,7 +1950,7 @@ describe API::MergeRequests do
describe 'POST :id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
before do
- ::MergeRequests::MergeWhenPipelineSucceedsService.new(merge_request.target_project, user).execute(merge_request)
+ ::AutoMergeService.new(merge_request.target_project, user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
end
it 'removes the merge_when_pipeline_succeeds status' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 16d306f39cd..799e84e83c1 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1025,7 +1025,54 @@ describe API::Projects do
end
end
- context 'when authenticated' do
+ context 'when authenticated as an admin' do
+ it 'returns a project by id' do
+ project
+ project_member
+ group = create(:group)
+ link = create(:project_group_link, project: project, group: group)
+
+ get api("/projects/#{project.id}", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(project.id)
+ expect(json_response['description']).to eq(project.description)
+ expect(json_response['default_branch']).to eq(project.default_branch)
+ expect(json_response['tag_list']).to be_an Array
+ expect(json_response['archived']).to be_falsey
+ expect(json_response['visibility']).to be_present
+ expect(json_response['ssh_url_to_repo']).to be_present
+ expect(json_response['http_url_to_repo']).to be_present
+ expect(json_response['web_url']).to be_present
+ expect(json_response['owner']).to be_a Hash
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['path']).to be_present
+ expect(json_response['issues_enabled']).to be_present
+ expect(json_response['merge_requests_enabled']).to be_present
+ expect(json_response['wiki_enabled']).to be_present
+ expect(json_response['jobs_enabled']).to be_present
+ expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['container_registry_enabled']).to be_present
+ expect(json_response['created_at']).to be_present
+ expect(json_response['last_activity_at']).to be_present
+ expect(json_response['shared_runners_enabled']).to be_present
+ expect(json_response['creator_id']).to be_present
+ expect(json_response['namespace']).to be_present
+ expect(json_response['avatar_url']).to be_nil
+ expect(json_response['star_count']).to be_present
+ expect(json_response['forks_count']).to be_present
+ expect(json_response['public_jobs']).to be_present
+ expect(json_response['shared_with_groups']).to be_an Array
+ expect(json_response['shared_with_groups'].length).to eq(1)
+ expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
+ expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
+ expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
+ expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
+ expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
+ end
+ end
+
+ context 'when authenticated as a regular user' do
before do
project
project_member
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 4006e697a41..038c958b5cc 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -444,8 +444,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
'sha' => job.sha,
'before_sha' => job.before_sha,
'ref_type' => 'branch',
- 'refspecs' => %w[+refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*],
- 'depth' => 0 }
+ 'refspecs' => ["+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}"],
+ 'depth' => project.default_git_depth }
end
let(:expected_steps) do
@@ -531,7 +531,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when GIT_DEPTH is not specified' do
+ context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
+ before do
+ project.update!(default_git_depth: nil)
+ end
+
it 'specifies refspecs' do
request_job
@@ -587,7 +591,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when GIT_DEPTH is not specified' do
+ context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
+ before do
+ project.update!(default_git_depth: nil)
+ end
+
it 'specifies refspecs' do
request_job
@@ -1632,8 +1640,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let!(:metadata) { file_upload2 }
let!(:metadata_sha256) { Digest::SHA256.file(metadata.path).hexdigest }
- let(:stored_artifacts_file) { job.reload.artifacts_file.file }
- let(:stored_metadata_file) { job.reload.artifacts_metadata.file }
+ let(:stored_artifacts_file) { job.reload.artifacts_file }
+ let(:stored_metadata_file) { job.reload.artifacts_metadata }
let(:stored_artifacts_size) { job.reload.artifacts_size }
let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 }
let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 }
@@ -1654,9 +1662,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it 'stores artifacts and artifacts metadata' do
expect(response).to have_gitlab_http_status(201)
- expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
- expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
- expect(stored_artifacts_size).to eq(72821)
+ expect(stored_artifacts_file.filename).to eq(artifacts.original_filename)
+ expect(stored_metadata_file.filename).to eq(metadata.original_filename)
+ expect(stored_artifacts_size).to eq(artifacts.size)
expect(stored_artifacts_sha256).to eq(artifacts_sha256)
expect(stored_metadata_sha256).to eq(metadata_sha256)
end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 7d61ec9c4d8..3e0b478abb3 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -70,11 +70,30 @@ describe API::Search do
context 'for milestones scope' do
before do
create(:milestone, project: project, title: 'awesome milestone')
+ end
+
+ context 'when user can read project milestones' do
+ before do
+ get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
+ end
- get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
+ it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
- it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
+ context 'when user cannot read project milestones' do
+ before do
+ project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it 'returns empty array' do
+ get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
+
+ milestones = JSON.parse(response.body)
+
+ expect(milestones).to be_empty
+ end
+ end
end
context 'for users scope' do
@@ -318,11 +337,30 @@ describe API::Search do
context 'for milestones scope' do
before do
create(:milestone, project: project, title: 'awesome milestone')
+ end
+
+ context 'when user can read milestones' do
+ before do
+ get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
+ end
- get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
+ it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
- it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
+ context 'when user cannot read project milestones' do
+ before do
+ project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it 'returns empty array' do
+ get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
+
+ milestones = JSON.parse(response.body)
+
+ expect(milestones).to be_empty
+ end
+ end
end
context 'for users scope' do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 527ab1cfb66..8a60980fe80 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -13,6 +13,7 @@ describe API::Settings, 'Settings' do
expect(json_response['default_projects_limit']).to eq(42)
expect(json_response['password_authentication_enabled_for_web']).to be_truthy
expect(json_response['repository_storages']).to eq(['default'])
+ expect(json_response['password_authentication_enabled']).to be_truthy
expect(json_response['plantuml_enabled']).to be_falsey
expect(json_response['plantuml_url']).to be_nil
expect(json_response['default_project_visibility']).to be_a String
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index b6e8d74c2e9..0e2f3face71 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -1,12 +1,14 @@
require 'spec_helper'
describe API::SystemHooks do
+ include StubRequests
+
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let!(:hook) { create(:system_hook, url: "http://example.com") }
before do
- stub_request(:post, hook.url)
+ stub_full_request(hook.url, method: :post)
end
describe "GET /hooks" do
@@ -68,6 +70,8 @@ describe API::SystemHooks do
end
it 'sets default values for events' do
+ stub_full_request('http://mep.mep', method: :post)
+
post api('/hooks', admin), params: { url: 'http://mep.mep' }
expect(response).to have_gitlab_http_status(201)
@@ -78,6 +82,8 @@ describe API::SystemHooks do
end
it 'sets explicit values for events' do
+ stub_full_request('http://mep.mep', method: :post)
+
post api('/hooks', admin),
params: {
url: 'http://mep.mep',
diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb
new file mode 100644
index 00000000000..ee2531197b1
--- /dev/null
+++ b/spec/requests/api/task_completion_status_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'task completion status response' do
+ set(:user) { create(:user) }
+ set(:project) do
+ create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ end
+
+ shared_examples 'taskable completion status provider' do |path|
+ samples = [
+ {
+ description: '',
+ expected_count: 0,
+ expected_completed_count: 0
+ },
+ {
+ description: 'Lorem ipsum',
+ expected_count: 0,
+ expected_completed_count: 0
+ },
+ {
+ description: %{- [ ] task 1
+ - [x] task 2 },
+ expected_count: 2,
+ expected_completed_count: 1
+ },
+ {
+ description: %{- [ ] task 1
+ - [ ] task 2 },
+ expected_count: 2,
+ expected_completed_count: 0
+ },
+ {
+ description: %{- [x] task 1
+ - [x] task 2 },
+ expected_count: 2,
+ expected_completed_count: 2
+ },
+ {
+ description: %{- [ ] task 1},
+ expected_count: 1,
+ expected_completed_count: 0
+ },
+ {
+ description: %{- [x] task 1},
+ expected_count: 1,
+ expected_completed_count: 1
+ }
+ ]
+ samples.each do |sample_data|
+ context "with a description of #{sample_data[:description].inspect}" do
+ before do
+ taskable.update!(description: sample_data[:description])
+
+ get api("#{path}?iids[]=#{taskable.iid}", user)
+ end
+
+ it { expect(response).to have_gitlab_http_status(200) }
+
+ it 'returns the expected results' do
+ expect(json_response).to be_an Array
+ expect(json_response).not_to be_empty
+
+ task_completion_status = json_response.first['task_completion_status']
+ expect(task_completion_status['count']).to eq(sample_data[:expected_count])
+ expect(task_completion_status['completed_count']).to eq(sample_data[:expected_completed_count])
+ end
+ end
+ end
+ end
+
+ context 'task list completion status for issues' do
+ it_behaves_like 'taskable completion status provider', '/issues' do
+ let(:taskable) { create(:issue, project: project, author: user) }
+ end
+ end
+
+ context 'task list completion status for merge_requests' do
+ it_behaves_like 'taskable completion status provider', '/merge_requests' do
+ let(:taskable) { create(:merge_request, source_project: project, target_project: project, author: user) }
+ end
+ end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index b84202364e1..bab1520b960 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -276,6 +276,18 @@ describe API::Users do
expect(response).to have_gitlab_http_status(400)
end
end
+
+ context "when authenticated and ldap is enabled" do
+ it "returns non-ldap user" do
+ create :omniauth_user, provider: "ldapserver1"
+
+ get api("/users", user), params: { skip_ldap: "true" }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first["username"]).to eq user.username
+ end
+ end
end
describe "GET /users/:id" do
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index cc07869a744..55b1419a004 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -43,6 +43,7 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(200)
expect(json_response['value']).to eq(variable.value)
expect(json_response['protected']).to eq(variable.protected?)
+ expect(json_response['masked']).to eq(variable.masked?)
expect(json_response['variable_type']).to eq('env_var')
end
@@ -74,13 +75,14 @@ describe API::Variables do
context 'authorized user with proper permissions' do
it 'creates variable' do
expect do
- post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true }
+ post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'PROTECTED_VALUE_2', protected: true, masked: true }
end.to change {project.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
expect(json_response['value']).to eq('PROTECTED_VALUE_2')
expect(json_response['protected']).to be_truthy
+ expect(json_response['masked']).to be_truthy
expect(json_response['variable_type']).to eq('env_var')
end
@@ -93,6 +95,7 @@ describe API::Variables do
expect(json_response['key']).to eq('TEST_VARIABLE_2')
expect(json_response['value']).to eq('VALUE_2')
expect(json_response['protected']).to be_falsey
+ expect(json_response['masked']).to be_falsey
expect(json_response['variable_type']).to eq('file')
end
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index a12646ea222..89adbc77a7f 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -182,6 +182,17 @@ describe 'Rack Attack global throttles' do
end
end
end
+
+ it 'logs RackAttack info into structured logs' do
+ requests_per_period.times do
+ get url_that_does_not_require_authentication
+ expect(response).to have_http_status 200
+ end
+
+ expect(Gitlab::AuthLogger).to receive(:error).once
+
+ get url_that_does_not_require_authentication
+ end
end
context 'when the throttle is disabled' do
@@ -327,6 +338,17 @@ describe 'Rack Attack global throttles' do
expect_rejection { get url_that_requires_authentication }
end
+
+ it 'logs RackAttack info into structured logs' do
+ requests_per_period.times do
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+
+ expect(Gitlab::AuthLogger).to receive(:error).once
+
+ get url_that_requires_authentication
+ end
end
context 'when the throttle is disabled' do
diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb
index 106f92082e4..3fdede7914d 100644
--- a/spec/routing/import_routing_spec.rb
+++ b/spec/routing/import_routing_spec.rb
@@ -174,3 +174,15 @@ describe Import::GitlabProjectsController, 'routing' do
expect(get('/import/gitlab_project/new')).to route_to('import/gitlab_projects#new')
end
end
+
+# new_import_phabricator GET /import/phabricator/new(.:format) import/phabricator#new
+# import_phabricator POST /import/phabricator(.:format) import/phabricator#create
+describe Import::PhabricatorController, 'routing' do
+ it 'to #create' do
+ expect(post("/import/phabricator")).to route_to("import/phabricator#create")
+ end
+
+ it 'to #new' do
+ expect(get("/import/phabricator/new")).to route_to("import/phabricator#new")
+ end
+end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index dd9540c11d1..83775b1040e 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -138,9 +138,11 @@ describe 'project routing' do
describe Projects::AutocompleteSourcesController, 'routing' do
[:members, :issues, :merge_requests, :labels, :milestones, :commands, :snippets].each do |action|
it "to ##{action}" do
- expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get("/gitlab/gitlabhq/-/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/autocomplete_sources/labels", "/gitlab/gitlabhq/-/autocomplete_sources/labels"
end
# pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages
@@ -239,7 +241,10 @@ describe 'project routing' do
it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :new, :create, :edit, :update] }
let(:controller) { 'deploy_keys' }
+ let(:controller_path) { '/-/deploy_keys' }
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/deploy_keys", "/gitlab/gitlabhq/-/deploy_keys"
end
# project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index
@@ -447,7 +452,10 @@ describe 'project routing' do
it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :create, :update, :destroy] }
let(:controller) { 'project_members' }
+ let(:controller_path) { '/-/project_members' }
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/project_members", "/gitlab/gitlabhq/-/project_members"
end
# project_milestones GET /:project_id/milestones(.:format) milestones#index
@@ -462,18 +470,23 @@ describe 'project routing' do
it_behaves_like 'RESTful project resources' do
let(:controller) { 'milestones' }
let(:actions) { [:index, :create, :new, :edit, :show, :update] }
+ let(:controller_path) { '/-/milestones' }
end
it 'to #promote' do
- expect(post('/gitlab/gitlabhq/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1")
+ expect(post('/gitlab/gitlabhq/-/milestones/1/promote')).to route_to('projects/milestones#promote', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "1")
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/milestones", "/gitlab/gitlabhq/-/milestones"
end
# project_labels GET /:project_id/labels(.:format) labels#index
describe Projects::LabelsController, 'routing' do
it 'to #index' do
- expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/-/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/labels", "/gitlab/gitlabhq/-/labels"
end
# sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort
@@ -615,20 +628,24 @@ describe 'project routing' do
describe Projects::ForksController, 'routing' do
it 'to #new' do
- expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/-/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
it 'to #create' do
- expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(post('/gitlab/gitlabhq/-/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/forks", "/gitlab/gitlabhq/-/forks"
end
# project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy
describe Projects::AvatarsController, 'routing' do
it 'to #destroy' do
- expect(delete('/gitlab/gitlabhq/avatar')).to route_to(
+ expect(delete('/gitlab/gitlabhq/-/avatar')).to route_to(
'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
+
+ it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/avatar", "/gitlab/gitlabhq/-/avatar"
end
describe Projects::PagesDomainsController, 'routing' do
diff --git a/spec/rubocop/cop/qa/element_with_pattern_spec.rb b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
index c5beb40f9fd..ef20d9a1f26 100644
--- a/spec/rubocop/cop/qa/element_with_pattern_spec.rb
+++ b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rubocop'
@@ -23,7 +25,7 @@ describe RuboCop::Cop::QA::ElementWithPattern do
element :groups_filter, 'search_field_tag :filter'
^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter` instead.
element :groups_filter_placeholder, /Search by name/
- ^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead.
+ ^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead.
end
RUBY
end
@@ -35,6 +37,13 @@ describe RuboCop::Cop::QA::ElementWithPattern do
element :groups_filter_placeholder
end
RUBY
+
+ expect_no_offenses(<<-RUBY)
+ view 'app/views/shared/groups/_search_form.html.haml' do
+ element :groups_filter, required: true
+ element :groups_filter_placeholder, required: false
+ end
+ RUBY
end
end
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
index dbfb3eace83..5b05c2f2ef3 100644
--- a/spec/serializers/analytics_stage_serializer_spec.rb
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -21,4 +21,34 @@ describe AnalyticsStageSerializer do
it 'contains important elements of AnalyticsStage' do
expect(subject).to include(:title, :description, :value)
end
+
+ context 'when median is equal 0' do
+ before do
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0)
+ end
+
+ it 'sets the value to nil' do
+ expect(subject.fetch(:value)).to be_nil
+ end
+ end
+
+ context 'when median is below 1' do
+ before do
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0.12)
+ end
+
+ it 'sets the value to equal to median' do
+ expect(subject.fetch(:value)).to eq('less than a minute')
+ end
+ end
+
+ context 'when median is above 1' do
+ before do
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(60.12)
+ end
+
+ it 'sets the value to equal to median' do
+ expect(subject.fetch(:value)).to eq('1 minute')
+ end
+ end
end
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
index 1edf69dc290..d922e8246c7 100644
--- a/spec/serializers/build_details_entity_spec.rb
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -122,5 +122,38 @@ describe BuildDetailsEntity do
it { is_expected.to include(failure_reason: 'unmet_prerequisites') }
end
+
+ context 'when a build has environment with latest deployment' do
+ let(:build) do
+ create(:ci_build, :running, environment: environment.name, pipeline: pipeline)
+ end
+
+ let(:environment) do
+ create(:environment, project: project, name: 'staging', state: :available)
+ end
+
+ before do
+ create(:deployment, :success, environment: environment, project: project)
+
+ allow(request).to receive(:project).and_return(project)
+ end
+
+ it 'does not serialize latest deployment commit and associated builds' do
+ response = subject.with_indifferent_access
+
+ response.dig(:deployment_status, :environment, :last_deployment).tap do |deployment|
+ expect(deployment).not_to include(:commit, :manual_actions, :scheduled_actions)
+ end
+ end
+ end
+
+ context 'when the build has reports' do
+ let!(:report) { create(:ci_job_artifact, :codequality, job: build) }
+
+ it 'exposes the report artifacts' do
+ expect(subject[:reports].count).to eq(1)
+ expect(subject[:reports].first[:file_type]).to eq('codequality')
+ end
+ end
end
end
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 894fd7a0a12..76ad2aee5c5 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -10,6 +10,7 @@ describe DeploymentEntity do
let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
let(:pipeline) { create(:ci_pipeline, project: project, user: user) }
let(:entity) { described_class.new(deployment, request: request) }
+
subject { entity.as_json }
before do
@@ -47,6 +48,16 @@ describe DeploymentEntity do
expect(subject[:manual_actions]).not_to be_present
end
end
+
+ context 'when deployment details serialization was disabled' do
+ let(:entity) do
+ described_class.new(deployment, request: request, deployment_details: false)
+ end
+
+ it 'does not serialize manual actions details' do
+ expect(subject.with_indifferent_access).not_to include(:manual_actions)
+ end
+ end
end
describe 'scheduled_actions' do
@@ -69,5 +80,35 @@ describe DeploymentEntity do
expect(subject[:scheduled_actions]).to be_empty
end
end
+
+ context 'when deployment details serialization was disabled' do
+ let(:entity) do
+ described_class.new(deployment, request: request, deployment_details: false)
+ end
+
+ it 'does not serialize scheduled actions details' do
+ expect(subject.with_indifferent_access).not_to include(:scheduled_actions)
+ end
+ end
+ end
+
+ context 'when deployment details serialization was disabled' do
+ include Gitlab::Routing
+
+ let(:entity) do
+ described_class.new(deployment, request: request, deployment_details: false)
+ end
+
+ it 'does not serialize deployment details' do
+ expect(subject.with_indifferent_access)
+ .not_to include(:commit, :manual_actions, :scheduled_actions)
+ end
+
+ it 'only exposes deployable name and path' do
+ project_job_path(project, deployment.deployable).tap do |path|
+ expect(subject.fetch(:deployable))
+ .to eq(name: 'test', build_path: path)
+ end
+ end
end
end
diff --git a/spec/serializers/job_artifact_report_entity_spec.rb b/spec/serializers/job_artifact_report_entity_spec.rb
new file mode 100644
index 00000000000..eef5c16d0fb
--- /dev/null
+++ b/spec/serializers/job_artifact_report_entity_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe JobArtifactReportEntity do
+ let(:report) { create(:ci_job_artifact, :codequality) }
+ let(:entity) { described_class.new(report, request: double) }
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'exposes file_type' do
+ expect(subject[:file_type]).to eq(report.file_type)
+ end
+
+ it 'exposes file_format' do
+ expect(subject[:file_format]).to eq(report.file_format)
+ end
+
+ it 'exposes size' do
+ expect(subject[:size]).to eq(report.size)
+ end
+
+ it 'exposes download path' do
+ expect(subject[:download_path]).to include("jobs/#{report.job.id}/artifacts/download")
+ end
+ end
+end
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index b89898f26f7..a27c22191f4 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -297,4 +297,50 @@ describe MergeRequestWidgetEntity do
end
end
end
+
+ describe 'auto merge' do
+ context 'when auto merge is enabled' do
+ let(:resource) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'returns auto merge related information' do
+ expect(subject[:auto_merge_enabled]).to be_truthy
+ expect(subject[:auto_merge_strategy]).to eq('merge_when_pipeline_succeeds')
+ end
+ end
+
+ context 'when auto merge is not enabled' do
+ let(:resource) { create(:merge_request) }
+
+ it 'returns auto merge related information' do
+ expect(subject[:auto_merge_enabled]).to be_falsy
+ expect(subject[:auto_merge_strategy]).to be_nil
+ end
+ end
+
+ context 'when head pipeline is running' do
+ before do
+ create(:ci_pipeline, :running, project: project,
+ ref: resource.source_branch,
+ sha: resource.diff_head_sha)
+ resource.update_head_pipeline
+ end
+
+ it 'returns available auto merge strategies' do
+ expect(subject[:available_auto_merge_strategies]).to eq(%w[merge_when_pipeline_succeeds])
+ end
+ end
+
+ context 'when head pipeline is finished' do
+ before do
+ create(:ci_pipeline, :success, project: project,
+ ref: resource.source_branch,
+ sha: resource.diff_head_sha)
+ resource.update_head_pipeline
+ end
+
+ it 'returns available auto merge strategies' do
+ expect(subject[:available_auto_merge_strategies]).to be_empty
+ end
+ end
+ end
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 47f767ae4ab..6be612ec226 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -48,8 +48,8 @@ describe PipelineEntity do
it 'contains flags' do
expect(subject).to include :flags
expect(subject[:flags])
- .to include :latest, :stuck, :auto_devops,
- :yaml_errors, :retryable, :cancelable, :merge_request
+ .to include :stuck, :auto_devops, :yaml_errors,
+ :retryable, :cancelable, :merge_request
end
end
@@ -64,6 +64,12 @@ describe PipelineEntity do
create(:ci_build, :failed, pipeline: pipeline)
end
+ it 'does not serialize stage builds' do
+ subject.with_indifferent_access.dig(:details, :stages, 0).tap do |stage|
+ expect(stage).not_to include(:groups, :latest_statuses, :retries)
+ end
+ end
+
context 'user has ability to retry pipeline' do
before do
project.add_developer(user)
@@ -92,6 +98,12 @@ describe PipelineEntity do
create(:ci_build, :pending, pipeline: pipeline)
end
+ it 'does not serialize stage builds' do
+ subject.with_indifferent_access.dig(:details, :stages, 0).tap do |stage|
+ expect(stage).not_to include(:groups, :latest_statuses, :retries)
+ end
+ end
+
context 'user has ability to cancel pipeline' do
before do
project.add_developer(user)
diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb
new file mode 100644
index 00000000000..cd08e0b6f32
--- /dev/null
+++ b/spec/services/auto_merge/base_service_spec.rb
@@ -0,0 +1,178 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AutoMerge::BaseService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user, params) }
+ let(:merge_request) { create(:merge_request) }
+ let(:params) { {} }
+
+ describe '#execute' do
+ subject { service.execute(merge_request) }
+
+ before do
+ allow(AutoMergeProcessWorker).to receive(:perform_async) {}
+ end
+
+ it 'sets properies to the merge request' do
+ subject
+
+ merge_request.reload
+ expect(merge_request).to be_auto_merge_enabled
+ expect(merge_request.merge_user).to eq(user)
+ expect(merge_request.auto_merge_strategy).to eq('base')
+ end
+
+ it 'yields block' do
+ expect { |b| service.execute(merge_request, &b) }.to yield_control.once
+ end
+
+ it 'returns activated strategy name' do
+ is_expected.to eq(:base)
+ end
+
+ context 'when merge parameters are given' do
+ let(:params) do
+ {
+ 'commit_message' => "Merge branch 'patch-12' into 'master'",
+ 'sha' => "200fcc9c260f7219eaf0daba87d818f0922c5b18",
+ 'should_remove_source_branch' => false,
+ 'squash' => false,
+ 'squash_commit_message' => "Update README.md"
+ }
+ end
+
+ it 'sets merge parameters' do
+ subject
+
+ merge_request.reload
+ expect(merge_request.merge_params['commit_message']).to eq("Merge branch 'patch-12' into 'master'")
+ expect(merge_request.merge_params['sha']).to eq('200fcc9c260f7219eaf0daba87d818f0922c5b18')
+ expect(merge_request.merge_params['should_remove_source_branch']).to eq(false)
+ expect(merge_request.merge_params['squash']).to eq(false)
+ expect(merge_request.merge_params['squash_commit_message']).to eq('Update README.md')
+ end
+ end
+
+ context 'when strategy is merge when pipeline succeeds' do
+ let(:service) { AutoMerge::MergeWhenPipelineSucceedsService.new(project, user) }
+
+ it 'sets the auto merge strategy' do
+ subject
+
+ merge_request.reload
+ expect(merge_request.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ end
+
+ it 'returns activated strategy name' do
+ is_expected.to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS.to_sym)
+ end
+
+ it 'calls AutoMergeProcessWorker' do
+ expect(AutoMergeProcessWorker).to receive(:perform_async).with(merge_request.id).once
+
+ subject
+ end
+ end
+
+ context 'when failed to save' do
+ before do
+ allow(merge_request).to receive(:save) { false }
+ end
+
+ it 'does not yield block' do
+ expect { |b| service.execute(merge_request, &b) }.not_to yield_control
+ end
+
+ it 'returns failed' do
+ is_expected.to eq(:failed)
+ end
+ end
+ end
+
+ describe '#update' do
+ subject { service.update(merge_request) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ context 'when merge params are specified' do
+ let(:params) do
+ {
+ 'commit_message' => "Merge branch 'patch-12' into 'master'",
+ 'sha' => "200fcc9c260f7219eaf0daba87d818f0922c5b18",
+ 'should_remove_source_branch' => false,
+ 'squash' => false,
+ 'squash_commit_message' => "Update README.md"
+ }
+ end
+
+ it 'updates merge params' do
+ expect { subject }.to change {
+ merge_request.reload.merge_params.slice(*params.keys)
+ }.from({}).to(params)
+ end
+ end
+ end
+
+ describe '#cancel' do
+ subject { service.cancel(merge_request) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'removes properies from the merge request' do
+ subject
+
+ merge_request.reload
+ expect(merge_request).not_to be_auto_merge_enabled
+ expect(merge_request.merge_user).to be_nil
+ expect(merge_request.auto_merge_strategy).to be_nil
+ end
+
+ it 'yields block' do
+ expect { |b| service.cancel(merge_request, &b) }.to yield_control.once
+ end
+
+ it 'returns success status' do
+ expect(subject[:status]).to eq(:success)
+ end
+
+ context 'when merge params are set' do
+ before do
+ merge_request.update!(merge_params:
+ {
+ 'should_remove_source_branch' => false,
+ 'commit_message' => "Merge branch 'patch-12' into 'master'",
+ 'squash_commit_message' => "Update README.md",
+ 'auto_merge_strategy' => 'merge_when_pipeline_succeeds'
+ })
+ end
+
+ it 'removes merge parameters' do
+ subject
+
+ merge_request.reload
+ expect(merge_request.merge_params['should_remove_source_branch']).to be_nil
+ expect(merge_request.merge_params['commit_message']).to be_nil
+ expect(merge_request.merge_params['squash_commit_message']).to be_nil
+ expect(merge_request.merge_params['auto_merge_strategy']).to be_nil
+ end
+ end
+
+ context 'when failed to save' do
+ before do
+ allow(merge_request).to receive(:save) { false }
+ end
+
+ it 'does not yield block' do
+ expect { |b| service.execute(merge_request, &b) }.not_to yield_control
+ end
+
+ it 'returns error status' do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq("Can't cancel the automatic merge")
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
index 96f61f3f103..a20bf8e17e4 100644
--- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeWhenPipelineSucceedsService do
+describe AutoMerge::MergeWhenPipelineSucceedsService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
@@ -21,6 +21,27 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
described_class.new(project, user, commit_message: 'Awesome message')
end
+ describe "#available_for?" do
+ subject { service.available_for?(mr_merge_if_green_enabled) }
+
+ let(:pipeline_status) { :running }
+
+ before do
+ create(:ci_pipeline, pipeline_status, ref: mr_merge_if_green_enabled.source_branch,
+ sha: mr_merge_if_green_enabled.diff_head_sha,
+ project: mr_merge_if_green_enabled.source_project)
+ mr_merge_if_green_enabled.update_head_pipeline
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when the head piipeline succeeded' do
+ let(:pipeline_status) { :success }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe "#execute" do
let(:merge_request) do
create(:merge_request, target_project: project, source_project: project,
@@ -30,8 +51,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
context 'first time enabling' do
before do
allow(merge_request)
- .to receive(:head_pipeline)
- .and_return(pipeline)
+ .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
service.execute(merge_request)
end
@@ -39,7 +59,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
it 'sets the params, merge_user, and flag' do
expect(merge_request).to be_valid
expect(merge_request.merge_when_pipeline_succeeds).to be_truthy
- expect(merge_request.merge_params).to eq commit_message: 'Awesome message'
+ expect(merge_request.merge_params).to include commit_message: 'Awesome message'
expect(merge_request.merge_user).to be user
end
@@ -54,8 +74,8 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
before do
- allow(mr_merge_if_green_enabled).to receive(:head_pipeline)
- .and_return(pipeline)
+ allow(mr_merge_if_green_enabled)
+ .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
allow(mr_merge_if_green_enabled).to receive(:mergeable?)
.and_return(true)
@@ -72,7 +92,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
end
end
- describe "#trigger" do
+ describe "#process" do
let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch }
let(:merge_request_head) do
project.commit(mr_merge_if_green_enabled.source_branch).id
@@ -86,8 +106,11 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
end
it "merges all merge requests with merge when the pipeline succeeds enabled" do
+ allow(mr_merge_if_green_enabled)
+ .to receive_messages(head_pipeline: triggering_pipeline, actual_head_pipeline: triggering_pipeline)
+
expect(MergeWorker).to receive(:perform_async)
- service.trigger(triggering_pipeline)
+ service.process(mr_merge_if_green_enabled)
end
end
@@ -99,7 +122,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
it 'does not merge request' do
expect(MergeWorker).not_to receive(:perform_async)
- service.trigger(old_pipeline)
+ service.process(mr_merge_if_green_enabled)
end
end
@@ -111,7 +134,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
it 'does not merge request' do
expect(MergeWorker).not_to receive(:perform_async)
- service.trigger(unrelated_pipeline)
+ service.process(mr_merge_if_green_enabled)
end
end
@@ -125,8 +148,11 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
end
it 'merges the associated merge request' do
+ allow(mr_merge_if_green_enabled)
+ .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
+
expect(MergeWorker).to receive(:perform_async)
- service.trigger(pipeline)
+ service.process(mr_merge_if_green_enabled)
end
end
end
diff --git a/spec/services/auto_merge_service_spec.rb b/spec/services/auto_merge_service_spec.rb
new file mode 100644
index 00000000000..7e9c412adb3
--- /dev/null
+++ b/spec/services/auto_merge_service_spec.rb
@@ -0,0 +1,164 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AutoMergeService do
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '.all_strategies' do
+ subject { described_class.all_strategies }
+
+ it 'returns all strategies' do
+ is_expected.to eq(AutoMergeService::STRATEGIES)
+ end
+ end
+
+ describe '#available_strategies' do
+ subject { service.available_strategies(merge_request) }
+
+ let(:merge_request) { create(:merge_request) }
+ let(:pipeline_status) { :running }
+
+ before do
+ create(:ci_pipeline, pipeline_status, ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ project: merge_request.source_project)
+
+ merge_request.update_head_pipeline
+ end
+
+ it 'returns available strategies' do
+ is_expected.to include('merge_when_pipeline_succeeds')
+ end
+
+ context 'when the head piipeline succeeded' do
+ let(:pipeline_status) { :success }
+
+ it 'returns available strategies' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.get_service_class' do
+ subject { described_class.get_service_class(strategy) }
+
+ let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
+
+ it 'returns service instance' do
+ is_expected.to eq(AutoMerge::MergeWhenPipelineSucceedsService)
+ end
+
+ context 'when strategy is not present' do
+ let(:strategy) { }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#execute' do
+ subject { service.execute(merge_request, strategy) }
+
+ let(:merge_request) { create(:merge_request) }
+ let(:pipeline_status) { :running }
+ let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
+
+ before do
+ create(:ci_pipeline, pipeline_status, ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ project: merge_request.source_project)
+
+ merge_request.update_head_pipeline
+ end
+
+ it 'delegates to a relevant service instance' do
+ expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
+ expect(service).to receive(:execute).with(merge_request)
+ end
+
+ subject
+ end
+
+ context 'when the head piipeline succeeded' do
+ let(:pipeline_status) { :success }
+
+ it 'returns failed' do
+ is_expected.to eq(:failed)
+ end
+ end
+ end
+
+ describe '#update' do
+ subject { service.update(merge_request) }
+
+ context 'when auto merge is enabled' do
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'delegates to a relevant service instance' do
+ expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
+ expect(service).to receive(:update).with(merge_request)
+ end
+
+ subject
+ end
+ end
+
+ context 'when auto merge is not enabled' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'returns failed' do
+ is_expected.to eq(:failed)
+ end
+ end
+ end
+
+ describe '#process' do
+ subject { service.process(merge_request) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'delegates to a relevant service instance' do
+ expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
+ expect(service).to receive(:process).with(merge_request)
+ end
+
+ subject
+ end
+
+ context 'when auto merge is not enabled' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#cancel' do
+ subject { service.cancel(merge_request) }
+
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'delegates to a relevant service instance' do
+ expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
+ expect(service).to receive(:cancel).with(merge_request)
+ end
+
+ subject
+ end
+
+ context 'when auto merge is not enabled' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'returns error' do
+ expect(subject[:message]).to eq("Can't cancel the automatic merge")
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:http_status]).to eq(406)
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 9a3ac75e418..867692d4d64 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -973,7 +973,7 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
- source_branch: ref_name,
+ source_branch: Gitlab::Git.ref_name(ref_name),
target_project: project,
target_branch: 'master')
end
@@ -1004,7 +1004,7 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
- source_branch: ref_name,
+ source_branch: Gitlab::Git.ref_name(ref_name),
target_project: project,
target_branch: 'master')
end
@@ -1033,7 +1033,7 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
- source_branch: ref_name,
+ source_branch: Gitlab::Git.ref_name(ref_name),
target_project: project,
target_branch: 'master')
end
diff --git a/spec/services/ci/pipeline_schedule_service_spec.rb b/spec/services/ci/pipeline_schedule_service_spec.rb
new file mode 100644
index 00000000000..867ed0acc0d
--- /dev/null
+++ b/spec/services/ci/pipeline_schedule_service_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::PipelineScheduleService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ describe '#execute' do
+ subject { service.execute(schedule) }
+
+ let(:schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
+
+ it 'schedules next run' do
+ expect(schedule).to receive(:schedule_next_run!)
+
+ subject
+ end
+
+ it 'runs RunPipelineScheduleWorker' do
+ expect(RunPipelineScheduleWorker)
+ .to receive(:perform_async).with(schedule.id, schedule.owner.id)
+
+ subject
+ end
+
+ context 'when owner is nil' do
+ let(:schedule) { create(:ci_pipeline_schedule, project: project, owner: nil) }
+
+ it 'does not raise an error' do
+ expect { subject }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index df2376384ca..e9a26400723 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -23,7 +23,7 @@ describe Ci::RetryBuildService do
REJECT_ACCESSORS =
%i[id status user token token_encrypted coverage trace runner
- artifacts_expire_at artifacts_file artifacts_metadata artifacts_size
+ artifacts_expire_at
created_at updated_at started_at finished_at queued_at erased_by
erased_at auto_canceled_by job_artifacts job_artifacts_archive
job_artifacts_metadata job_artifacts_trace job_artifacts_junit
@@ -38,7 +38,8 @@ describe Ci::RetryBuildService do
runner_id tag_taggings taggings tags trigger_request_id
user_id auto_canceled_by_id retried failure_reason
sourced_pipelines artifacts_file_store artifacts_metadata_store
- metadata runner_session trace_chunks].freeze
+ metadata runner_session trace_chunks
+ artifacts_file artifacts_metadata artifacts_size].freeze
shared_examples 'build duplication' do
let(:another_pipeline) { create(:ci_empty_pipeline, project: project) }
diff --git a/spec/services/issuable/clone/content_rewriter_spec.rb b/spec/services/issuable/clone/content_rewriter_spec.rb
index 4d3cb0bd254..230e1123280 100644
--- a/spec/services/issuable/clone/content_rewriter_spec.rb
+++ b/spec/services/issuable/clone/content_rewriter_spec.rb
@@ -149,5 +149,21 @@ describe Issuable::Clone::ContentRewriter do
expect(new_note.author).to eq(note.author)
end
end
+
+ context 'notes with upload' do
+ let(:uploader) { build(:file_uploader, project: project1) }
+ let(:text) { "Simple text with image: #{uploader.markdown_link} "}
+ let!(:note) { create(:note, noteable: original_issue, note: text, project: project1) }
+
+ it 'rewrites note content correctly' do
+ subject.execute
+ new_note = new_issue.notes.first
+
+ expect(note.note).to match(/Simple text with image: #{FileUploader::MARKDOWN_PATTERN}/)
+ expect(new_note.note).to match(/Simple text with image: #{FileUploader::MARKDOWN_PATTERN}/)
+ expect(note.note).not_to eq(new_note.note)
+ expect(note.note_html).not_to eq(new_note.note_html)
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index ffa612cf315..29b7e0f17e2 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -52,6 +52,14 @@ describe MergeRequests::CloseService do
it 'marks todos as done' do
expect(todo.reload).to be_done
end
+
+ context 'when auto merge is enabled' do
+ let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
+
+ it 'cancels the auto merge' do
+ expect(@merge_request).not_to be_auto_merge_enabled
+ end
+ end
end
it 'updates metrics' do
diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb
new file mode 100644
index 00000000000..9479439bde8
--- /dev/null
+++ b/spec/services/merge_requests/create_pipeline_service_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::CreatePipelineService do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+ let(:service) { described_class.new(project, user, params) }
+ let(:params) { {} }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
+ subject { service.execute(merge_request) }
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ let(:config) do
+ { rspec: { script: 'echo', only: ['merge_requests'] } }
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_branch: 'feature',
+ source_project: project,
+ target_branch: 'master',
+ target_project: project)
+ end
+
+ it 'creates a detached merge request pipeline' do
+ expect { subject }.to change { Ci::Pipeline.count }.by(1)
+
+ expect(subject).to be_persisted
+ expect(subject).to be_detached_merge_request_pipeline
+ end
+
+ context 'when service is called multiple times' do
+ it 'creates a pipeline once' do
+ expect do
+ service.execute(merge_request)
+ service.execute(merge_request)
+ end.to change { Ci::Pipeline.count }.by(1)
+ end
+
+ context 'when allow_duplicate option is true' do
+ let(:params) { { allow_duplicate: true } }
+
+ it 'creates pipelines multiple times' do
+ expect do
+ service.execute(merge_request)
+ service.execute(merge_request)
+ end.to change { Ci::Pipeline.count }.by(2)
+ end
+ end
+ end
+
+ context 'when .gitlab-ci.yml does not have only: [merge_requests] keyword' do
+ let(:config) do
+ { rspec: { script: 'echo' } }
+ end
+
+ it 'does not create a pipeline' do
+ expect { subject }.not_to change { Ci::Pipeline.count }
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index f7a39bb42d5..54b9c6dae38 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -76,10 +76,11 @@ describe MergeRequests::PushOptionsHandlerService do
shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do
subject(:last_mr) { MergeRequest.last }
- it 'sets merge_when_pipeline_succeeds' do
+ it 'sets auto_merge_enabled' do
service.execute
- expect(last_mr.merge_when_pipeline_succeeds).to eq(true)
+ expect(last_mr.auto_merge_enabled).to eq(true)
+ expect(last_mr.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
end
it 'sets merge_user to the user' do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 7258428589f..6ba67c7165c 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -23,7 +23,8 @@ describe MergeRequests::RefreshService do
source_branch: 'master',
target_branch: 'feature',
target_project: @project,
- merge_when_pipeline_succeeds: true,
+ auto_merge_enabled: true,
+ auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
merge_user: @user)
@another_merge_request = create(:merge_request,
@@ -31,7 +32,8 @@ describe MergeRequests::RefreshService do
source_branch: 'master',
target_branch: 'test',
target_project: @project,
- merge_when_pipeline_succeeds: true,
+ auto_merge_enabled: true,
+ auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
merge_user: @user)
@fork_merge_request = create(:merge_request,
@@ -83,7 +85,7 @@ describe MergeRequests::RefreshService do
expect(@merge_request.notes).not_to be_empty
expect(@merge_request).to be_open
- expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey
+ expect(@merge_request.auto_merge_enabled).to be_falsey
expect(@merge_request.diff_head_sha).to eq(@newrev)
expect(@fork_merge_request).to be_open
expect(@fork_merge_request.notes).to be_empty
@@ -292,7 +294,7 @@ describe MergeRequests::RefreshService do
expect(@merge_request.notes).not_to be_empty
expect(@merge_request).to be_open
- expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey
+ expect(@merge_request.auto_merge_enabled).to be_falsey
expect(@merge_request.diff_head_sha).to eq(@newrev)
expect(@fork_merge_request).to be_open
expect(@fork_merge_request.notes).to be_empty
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index ba4c9ce60f3..f566d235787 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -217,8 +217,9 @@ describe MergeRequests::UpdateService, :mailer do
head_pipeline_of: merge_request
)
- expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user)
+ expect(AutoMerge::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user, {})
.and_return(service_mock)
+ allow(service_mock).to receive(:available_for?) { true }
expect(service_mock).to receive(:execute).with(merge_request)
end
@@ -405,6 +406,18 @@ describe MergeRequests::UpdateService, :mailer do
expect(pending_todo.reload).to be_done
end
end
+
+ context 'when auto merge is enabled and target branch changed' do
+ before do
+ AutoMergeService.new(project, user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+
+ update_merge_request({ target_branch: 'target' })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
end
context 'when the merge request is relabeled' do
diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb
new file mode 100644
index 00000000000..d59aa9b979e
--- /dev/null
+++ b/spec/services/pages_domains/create_acme_order_service_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomains::CreateAcmeOrderService do
+ include LetsEncryptHelpers
+
+ let(:pages_domain) { create(:pages_domain) }
+
+ let(:challenge) { ::Gitlab::LetsEncrypt::Challenge.new(acme_challenge_double) }
+
+ let(:order_double) do
+ Gitlab::LetsEncrypt::Order.new(acme_order_double).tap do |order|
+ allow(order).to receive(:new_challenge).and_return(challenge)
+ end
+ end
+
+ let(:lets_encrypt_client) do
+ instance_double('Gitlab::LetsEncrypt::Client').tap do |client|
+ allow(client).to receive(:new_order).with(pages_domain.domain)
+ .and_return(order_double)
+ end
+ end
+
+ let(:service) { described_class.new(pages_domain) }
+
+ before do
+ allow(::Gitlab::LetsEncrypt::Client).to receive(:new).and_return(lets_encrypt_client)
+ end
+
+ it 'saves order to database before requesting validation' do
+ allow(pages_domain.acme_orders).to receive(:create!).and_call_original
+ allow(challenge).to receive(:request_validation).and_call_original
+
+ service.execute
+
+ expect(pages_domain.acme_orders).to have_received(:create!).ordered
+ expect(challenge).to have_received(:request_validation).ordered
+ end
+
+ it 'generates and saves private key' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect { OpenSSL::PKey::RSA.new(saved_order.private_key) }.not_to raise_error
+ end
+
+ it 'properly saves order attributes' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect(saved_order.url).to eq(order_double.url)
+ expect(saved_order.expires_at).to be_like_time(order_double.expires)
+ end
+
+ it 'properly saves challenge attributes' do
+ service.execute
+
+ saved_order = PagesDomainAcmeOrder.last
+ expect(saved_order.challenge_token).to eq(challenge.token)
+ expect(saved_order.challenge_file_content).to eq(challenge.file_content)
+ end
+end
diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
new file mode 100644
index 00000000000..6d7be27939c
--- /dev/null
+++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomains::ObtainLetsEncryptCertificateService do
+ include LetsEncryptHelpers
+
+ let(:pages_domain) { create(:pages_domain, :without_certificate, :without_key) }
+ let(:service) { described_class.new(pages_domain) }
+
+ before do
+ stub_lets_encrypt_settings
+ end
+
+ def expect_to_create_acme_challenge
+ expect(::PagesDomains::CreateAcmeOrderService).to receive(:new).with(pages_domain)
+ .and_wrap_original do |m, *args|
+ create_service = m.call(*args)
+
+ expect(create_service).to receive(:execute)
+
+ create_service
+ end
+ end
+
+ def stub_lets_encrypt_order(url, status)
+ order = ::Gitlab::LetsEncrypt::Order.new(acme_order_double(status: status))
+
+ allow_any_instance_of(::Gitlab::LetsEncrypt::Client).to(
+ receive(:load_order).with(url).and_return(order)
+ )
+
+ order
+ end
+
+ context 'when there is no acme order' do
+ it 'creates acme order' do
+ expect_to_create_acme_challenge
+
+ service.execute
+ end
+ end
+
+ context 'when there is expired acme order' do
+ let!(:existing_order) do
+ create(:pages_domain_acme_order, :expired, pages_domain: pages_domain)
+ end
+
+ it 'removes acme order and creates new one' do
+ expect_to_create_acme_challenge
+
+ service.execute
+
+ expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil
+ end
+ end
+
+ %w(pending processing).each do |status|
+ context "there is an order in '#{status}' status" do
+ let(:existing_order) do
+ create(:pages_domain_acme_order, pages_domain: pages_domain)
+ end
+
+ before do
+ stub_lets_encrypt_order(existing_order.url, status)
+ end
+
+ it 'does not raise errors' do
+ expect do
+ service.execute
+ end.not_to raise_error
+ end
+ end
+ end
+
+ context 'when order is ready' do
+ let(:existing_order) do
+ create(:pages_domain_acme_order, pages_domain: pages_domain)
+ end
+
+ let!(:api_order) do
+ stub_lets_encrypt_order(existing_order.url, 'ready')
+ end
+
+ it 'request certificate' do
+ expect(api_order).to receive(:request_certificate).and_call_original
+
+ service.execute
+ end
+ end
+
+ context 'when order is valid' do
+ let(:existing_order) do
+ create(:pages_domain_acme_order, pages_domain: pages_domain)
+ end
+
+ let!(:api_order) do
+ stub_lets_encrypt_order(existing_order.url, 'valid')
+ end
+
+ let(:certificate) do
+ key = OpenSSL::PKey.read(existing_order.private_key)
+
+ subject = "/C=BE/O=Test/OU=Test/CN=#{pages_domain.domain}"
+
+ cert = OpenSSL::X509::Certificate.new
+ cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
+ cert.not_before = Time.now
+ cert.not_after = 1.year.from_now
+ cert.public_key = key.public_key
+ cert.serial = 0x0
+ cert.version = 2
+
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.subject_certificate = cert
+ ef.issuer_certificate = cert
+ cert.extensions = [
+ ef.create_extension("basicConstraints", "CA:TRUE", true),
+ ef.create_extension("subjectKeyIdentifier", "hash")
+ ]
+ cert.add_extension ef.create_extension("authorityKeyIdentifier",
+ "keyid:always,issuer:always")
+
+ cert.sign key, OpenSSL::Digest::SHA1.new
+
+ cert.to_pem
+ end
+
+ before do
+ expect(api_order).to receive(:certificate) { certificate }
+ end
+
+ it 'saves private_key and certificate for domain' do
+ service.execute
+
+ expect(pages_domain.key).to be_present
+ expect(pages_domain.certificate).to eq(certificate)
+ end
+
+ it 'removes order from database' do
+ service.execute
+
+ expect(PagesDomainAcmeOrder.find_by_id(existing_order.id)).to be_nil
+ end
+ end
+end
diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb
index f7261cd7125..d25e9958831 100644
--- a/spec/services/preview_markdown_service_spec.rb
+++ b/spec/services/preview_markdown_service_spec.rb
@@ -56,7 +56,9 @@ describe PreviewMarkdownService do
expect(Gitlab::Diff::SuggestionsParser)
.to receive(:parse)
- .with(text, position: position, project: merge_request.project)
+ .with(text, position: position,
+ project: merge_request.project,
+ supports_suggestion: true)
.and_call_original
result = service.execute
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index ec3f1782e8f..3211a6e1310 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -145,6 +145,30 @@ describe Projects::ForkService do
end
end
+ context "CI/CD settings" do
+ let(:to_project) { fork_project(@from_project, @to_user) }
+
+ context "when origin has git depth specified" do
+ before do
+ @from_project.update(default_git_depth: 42)
+ end
+
+ it "inherits default_git_depth from the origin project" do
+ expect(to_project.default_git_depth).to eq(42)
+ end
+ end
+
+ context "when origin does not define git depth" do
+ before do
+ @from_project.update!(default_git_depth: nil)
+ end
+
+ it "the fork has git depth set to 0" do
+ expect(to_project.default_git_depth).to eq(0)
+ end
+ end
+ end
+
context "when project has restricted visibility level" do
context "and only one visibility level is restricted" do
before do
diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
index f4470b50753..75d534c59bf 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
@@ -2,6 +2,8 @@
require 'spec_helper'
describe Projects::LfsPointers::LfsDownloadService do
+ include StubRequests
+
let(:project) { create(:project) }
let(:lfs_content) { SecureRandom.random_bytes(10) }
let(:oid) { Digest::SHA256.hexdigest(lfs_content) }
@@ -62,7 +64,7 @@ describe Projects::LfsPointers::LfsDownloadService do
describe '#execute' do
context 'when file download succeeds' do
before do
- WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
+ stub_full_request(download_link).to_return(body: lfs_content)
end
it_behaves_like 'lfs object is created'
@@ -104,7 +106,7 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:size) { 1 }
before do
- WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
+ stub_full_request(download_link).to_return(body: lfs_content)
end
it_behaves_like 'no lfs object is created'
@@ -118,7 +120,7 @@ describe Projects::LfsPointers::LfsDownloadService do
context 'when downloaded lfs file has a different oid' do
before do
- WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
+ stub_full_request(download_link).to_return(body: lfs_content)
allow_any_instance_of(Digest::SHA256).to receive(:hexdigest).and_return('foobar')
end
@@ -136,7 +138,7 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:lfs_object) { LfsDownloadObject.new(oid: oid, size: size, link: download_link_with_credentials) }
before do
- WebMock.stub_request(:get, download_link).with(headers: { 'Authorization' => 'Basic dXNlcjpwYXNzd29yZA==' }).to_return(body: lfs_content)
+ stub_full_request(download_link).with(headers: { 'Authorization' => 'Basic dXNlcjpwYXNzd29yZA==' }).to_return(body: lfs_content)
end
it 'the request adds authorization headers' do
@@ -149,7 +151,7 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:local_request_setting) { true }
before do
- WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
+ stub_full_request(download_link, ip_address: '192.168.2.120').to_return(body: lfs_content)
end
it_behaves_like 'lfs object is created'
@@ -173,7 +175,8 @@ describe Projects::LfsPointers::LfsDownloadService do
with_them do
before do
- WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link })
+ stub_full_request(download_link, ip_address: '192.168.2.120')
+ .to_return(status: 301, headers: { 'Location' => redirect_link })
end
it_behaves_like 'no lfs object is created'
@@ -184,8 +187,8 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:redirect_link) { "http://example.com/"}
before do
- WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link })
- WebMock.stub_request(:get, redirect_link).to_return(body: lfs_content)
+ stub_full_request(download_link).to_return(status: 301, headers: { 'Location' => redirect_link })
+ stub_full_request(redirect_link).to_return(body: lfs_content)
end
it_behaves_like 'lfs object is created'
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 7c91f0bbe6e..b597717c347 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -27,59 +27,6 @@ describe Projects::UpdatePagesService do
it { is_expected.not_to match(Gitlab::PathRegex.namespace_format_regex) }
end
- context 'legacy artifacts' do
- before do
- build.update(legacy_artifacts_file: file)
- build.update(legacy_artifacts_metadata: metadata)
- end
-
- describe 'pages artifacts' do
- it "doesn't delete artifacts after deploying" do
- expect(execute).to eq(:success)
-
- expect(build.reload.artifacts?).to eq(true)
- end
- end
-
- it 'succeeds' do
- expect(project.pages_deployed?).to be_falsey
- expect(execute).to eq(:success)
- expect(project.pages_deployed?).to be_truthy
-
- # Check that all expected files are extracted
- %w[index.html zero .hidden/file].each do |filename|
- expect(File.exist?(File.join(project.public_pages_path, filename))).to be_truthy
- end
- end
-
- it 'limits pages size' do
- stub_application_setting(max_pages_size: 1)
- expect(execute).not_to eq(:success)
- end
-
- it 'removes pages after destroy' do
- expect(PagesWorker).to receive(:perform_in)
- expect(project.pages_deployed?).to be_falsey
- expect(execute).to eq(:success)
- expect(project.pages_deployed?).to be_truthy
- project.destroy
- expect(project.pages_deployed?).to be_falsey
- end
-
- it 'fails if sha on branch is not latest' do
- build.update(ref: 'feature')
-
- expect(execute).not_to eq(:success)
- end
-
- it 'fails for empty file fails' do
- build.update(legacy_artifacts_file: empty_file)
-
- expect { execute }
- .to raise_error(Projects::UpdatePagesService::FailedToExtractError)
- end
- end
-
context 'for new artifacts' do
context "for a valid job" do
before do
@@ -207,7 +154,7 @@ describe Projects::UpdatePagesService do
end
it 'fails for invalid archive' do
- build.update(legacy_artifacts_file: invalid_file)
+ create(:ci_job_artifact, :archive, file: invalid_file, job: build)
expect(execute).not_to eq(:success)
end
@@ -218,8 +165,8 @@ describe Projects::UpdatePagesService do
file = fixture_file_upload('spec/fixtures/pages.zip')
metafile = fixture_file_upload('spec/fixtures/pages.zip.meta')
- build.update(legacy_artifacts_file: file)
- build.update(legacy_artifacts_metadata: metafile)
+ create(:ci_job_artifact, :archive, file: file, job: build)
+ create(:ci_job_artifact, :metadata, file: metafile, job: build)
allow(build).to receive(:artifacts_metadata_entry)
.and_return(metadata)
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 5ad30b58511..1dcfb739eb6 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -45,6 +45,7 @@ describe Projects::UpdateService do
it 'updates the project to private' do
expect(TodosDestroyer::ProjectPrivateWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, project.id)
+ expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, nil, project.id)
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
diff --git a/spec/services/projects/update_statistics_service_spec.rb b/spec/services/projects/update_statistics_service_spec.rb
index 5000ea58e5f..8534853fbc7 100644
--- a/spec/services/projects/update_statistics_service_spec.rb
+++ b/spec/services/projects/update_statistics_service_spec.rb
@@ -17,19 +17,9 @@ describe Projects::UpdateStatisticsService do
end
end
- context 'with an existing project without a repository' do
+ context 'with an existing project' do
let(:project) { create(:project) }
- it 'does nothing' do
- expect_any_instance_of(ProjectStatistics).not_to receive(:refresh!)
-
- service.execute
- end
- end
-
- context 'with an existing project with a repository' do
- let(:project) { create(:project, :repository) }
-
it 'refreshes the project statistics' do
expect_any_instance_of(ProjectStatistics).to receive(:refresh!)
.with(only: statistics.map(&:to_sym))
diff --git a/spec/services/submit_usage_ping_service_spec.rb b/spec/services/submit_usage_ping_service_spec.rb
index 78df9bf96bf..653f17a4324 100644
--- a/spec/services/submit_usage_ping_service_spec.rb
+++ b/spec/services/submit_usage_ping_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe SubmitUsagePingService do
+ include StubRequests
+
context 'when usage ping is disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
@@ -99,7 +101,7 @@ describe SubmitUsagePingService do
end
def stub_response(body)
- stub_request(:post, 'https://version.gitlab.com/usage_data')
+ stub_full_request('https://version.gitlab.com/usage_data', method: :post)
.to_return(
headers: { 'Content-Type' => 'application/json' },
body: body.to_json
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index 7732767137c..bdbcb0fdb07 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -5,6 +5,16 @@ require 'spec_helper'
describe Suggestions::ApplyService do
include ProjectForksHelper
+ def build_position(args = {})
+ default_args = { old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs }
+
+ Gitlab::Diff::Position.new(default_args.merge(args))
+ end
+
shared_examples 'successfully creates commit and updates suggestion' do
def apply(suggestion)
result = subject.execute(suggestion)
@@ -43,13 +53,7 @@ describe Suggestions::ApplyService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, :commit_email) }
- let(:position) do
- Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
- new_path: "files/ruby/popen.rb",
- old_line: nil,
- new_line: 9,
- diff_refs: merge_request.diff_refs)
- end
+ let(:position) { build_position }
let(:diff_note) do
create(:diff_note_on_merge_request, noteable: merge_request, position: position, project: project)
@@ -333,6 +337,56 @@ describe Suggestions::ApplyService do
it_behaves_like 'successfully creates commit and updates suggestion'
end
+
+ context 'remove an empty line suggestion' do
+ let(:expected_content) do
+ <<~CONTENT
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ raise RuntimeError, "System commands must be given as an array of strings"
+ end
+
+ path ||= Dir.pwd
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+ end
+ CONTENT
+ end
+
+ let(:position) { build_position(new_line: 13) }
+ let(:suggestion) do
+ create(:suggestion, :content_from_repo, note: diff_note, to_content: "")
+ end
+
+ it_behaves_like 'successfully creates commit and updates suggestion'
+ end
end
context 'fork-project' do
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
index ce4990a34a4..d95f9e3349b 100644
--- a/spec/services/suggestions/create_service_spec.rb
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -96,7 +96,7 @@ describe Suggestions::CreateService do
it 'creates no suggestion when diff file is not found' do
expect_next_instance_of(DiffNote) do |diff_note|
- expect(diff_note).to receive(:latest_diff_file).twice { nil }
+ expect(diff_note).to receive(:latest_diff_file).once { nil }
end
expect { subject.execute }.not_to change(Suggestion, :count)
@@ -151,6 +151,26 @@ describe Suggestions::CreateService do
subject.execute
end
end
+
+ context 'when a patch removes an empty line' do
+ let(:markdown) do
+ <<-MARKDOWN.strip_heredoc
+ ```suggestion
+ ```
+ MARKDOWN
+ end
+ let(:position) { build_position(new_line: 13) }
+
+ it 'creates an appliable suggestion' do
+ subject.execute
+
+ suggestion = note.suggestions.last
+
+ expect(suggestion).to be_appliable
+ expect(suggestion.from_content).to eq("\n")
+ expect(suggestion.to_content).to eq("")
+ end
+ end
end
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 4d33c6f4094..2420817e1f7 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -132,7 +132,7 @@ describe SystemNoteService do
end
it 'sets the note text' do
- link = "http://localhost/#{project.full_path}/-/tags/#{tag_name}"
+ link = "/#{project.full_path}/-/tags/#{tag_name}"
expect(subject.note).to eq "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})"
end
@@ -1139,7 +1139,7 @@ describe SystemNoteService do
diff_id = merge_request.merge_request_diff.id
line_code = change_position.line_code(project.repository)
- expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code))
+ expect(subject.note).to include(diffs_project_merge_request_path(project, merge_request, diff_id: diff_id, anchor: line_code))
end
end
diff --git a/spec/services/todos/destroy/confidential_issue_service_spec.rb b/spec/services/todos/destroy/confidential_issue_service_spec.rb
index 78b6744b426..9f7e656f7d3 100644
--- a/spec/services/todos/destroy/confidential_issue_service_spec.rb
+++ b/spec/services/todos/destroy/confidential_issue_service_spec.rb
@@ -9,36 +9,60 @@ describe Todos::Destroy::ConfidentialIssueService do
let(:assignee) { create(:user) }
let(:guest) { create(:user) }
let(:project_member) { create(:user) }
- let(:issue) { create(:issue, project: project, author: author, assignees: [assignee]) }
-
- let!(:todo_issue_non_member) { create(:todo, user: user, target: issue, project: project) }
- let!(:todo_issue_member) { create(:todo, user: project_member, target: issue, project: project) }
- let!(:todo_issue_author) { create(:todo, user: author, target: issue, project: project) }
- let!(:todo_issue_asignee) { create(:todo, user: assignee, target: issue, project: project) }
- let!(:todo_issue_guest) { create(:todo, user: guest, target: issue, project: project) }
- let!(:todo_another_non_member) { create(:todo, user: user, project: project) }
+ let(:issue_1) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) }
describe '#execute' do
before do
project.add_developer(project_member)
project.add_guest(guest)
+
+ # todos not to be deleted
+ create(:todo, user: project_member, target: issue_1, project: project)
+ create(:todo, user: author, target: issue_1, project: project)
+ create(:todo, user: assignee, target: issue_1, project: project)
+ create(:todo, user: user, project: project)
+ # Todos to be deleted
+ create(:todo, user: guest, target: issue_1, project: project)
+ create(:todo, user: user, target: issue_1, project: project)
end
- subject { described_class.new(issue.id).execute }
+ subject { described_class.new(issue_id: issue_1.id).execute }
- context 'when provided issue is confidential' do
- before do
- issue.update!(confidential: true)
+ context 'when issue_id parameter is present' do
+ context 'when provided issue is confidential' do
+ it 'removes issue todos for users who can not access the confidential issue' do
+ expect { subject }.to change { Todo.count }.from(6).to(4)
+ end
end
- it 'removes issue todos for users who can not access the confidential issue' do
- expect { subject }.to change { Todo.count }.from(6).to(4)
+ context 'when provided issue is not confidential' do
+ it 'does not remove any todos' do
+ issue_1.update(confidential: false)
+
+ expect { subject }.not_to change { Todo.count }
+ end
end
end
- context 'when provided issue is not confidential' do
- it 'does not remove any todos' do
- expect { subject }.not_to change { Todo.count }
+ context 'when project_id parameter is present' do
+ subject { described_class.new(issue_id: nil, project_id: project.id).execute }
+
+ it 'removes issues todos for users that cannot access confidential issues' do
+ issue_2 = create(:issue, :confidential, project: project)
+ issue_3 = create(:issue, :confidential, project: project, author: author, assignees: [assignee])
+ issue_4 = create(:issue, project: project)
+ # Todos not to be deleted
+ create(:todo, user: guest, target: issue_1, project: project)
+ create(:todo, user: assignee, target: issue_1, project: project)
+ create(:todo, user: project_member, target: issue_2, project: project)
+ create(:todo, user: author, target: issue_3, project: project)
+ create(:todo, user: user, target: issue_4, project: project)
+ create(:todo, user: user, project: project)
+ # Todos to be deleted
+ create(:todo, user: user, target: issue_1, project: project)
+ create(:todo, user: guest, target: issue_2, project: project)
+
+ expect { subject }.to change { Todo.count }.from(14).to(10)
end
end
end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 75ba2479b63..37bafc0c002 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe WebHookService do
+ include StubRequests
+
let(:project) { create(:project) }
let(:project_hook) { create(:project_hook) }
let(:headers) do
@@ -67,11 +69,11 @@ describe WebHookService do
let(:project_hook) { create(:project_hook, url: 'https://demo:demo@example.org/') }
it 'uses the credentials' do
- WebMock.stub_request(:post, url)
+ stub_full_request(url, method: :post)
service_instance.execute
- expect(WebMock).to have_requested(:post, url).with(
+ expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with(
headers: headers.merge('Authorization' => 'Basic ZGVtbzpkZW1v')
).once
end
@@ -82,11 +84,11 @@ describe WebHookService do
let(:project_hook) { create(:project_hook, url: 'https://demo@example.org/') }
it 'uses the credentials anyways' do
- WebMock.stub_request(:post, url)
+ stub_full_request(url, method: :post)
service_instance.execute
- expect(WebMock).to have_requested(:post, url).with(
+ expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with(
headers: headers.merge('Authorization' => 'Basic ZGVtbzo=')
).once
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 69589c9aa33..390a869d93f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -44,6 +44,8 @@ Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f }
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
+quality_level = Quality::TestLevel.new
+
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.use_instantiated_fixtures = false
@@ -55,9 +57,10 @@ RSpec.configure do |config|
config.infer_spec_type_from_file_location!
config.full_backtrace = !!ENV['CI']
- config.define_derived_metadata(file_path: %r{/spec/}) do |metadata|
+ config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata|
location = metadata[:location]
+ metadata[:level] = quality_level.level_for(location)
metadata[:api] = true if location =~ %r{/spec/requests/api/}
# do not overwrite type if it's already set
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 875a9a76e12..14ce3c32e77 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -42,6 +42,9 @@ Capybara.register_driver :chrome do |app|
# Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER']
+ # Explicitly set user-data-dir to prevent crashes. See https://gitlab.com/gitlab-org/gitlab-ce/issues/58882#note_179811508
+ options.add_argument("user-data-dir=/tmp/chrome") if ENV['CI'] || ENV['CI_SERVER']
+
Capybara::Selenium::Driver.new(
app,
browser: :chrome,
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 89dfbf931d2..5d5a0a7b5d2 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -20,7 +20,7 @@ shared_examples 'reportable note' do |type|
dropdown = comment.find(more_actions_selector)
open_dropdown(dropdown)
- expect(dropdown).to have_link('Report abuse to GitLab', href: abuse_report_path)
+ expect(dropdown).to have_link('Report abuse to admin', href: abuse_report_path)
if type == 'issue' || type == 'merge_request'
expect(dropdown).to have_button('Delete comment')
@@ -33,7 +33,7 @@ shared_examples 'reportable note' do |type|
dropdown = comment.find(more_actions_selector)
open_dropdown(dropdown)
- dropdown.click_link('Report abuse to GitLab')
+ dropdown.click_link('Report abuse to admin')
expect(find('#user_name')['value']).to match(note.author.username)
expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note))
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index 92a19dd22a2..01531864c1f 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -45,12 +45,12 @@ shared_examples 'variable list' do
end
end
- it 'defaults to masked' do
+ it 'defaults to unmasked' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('key')
find('.js-ci-variable-input-value').set('key_value')
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
end
click_button('Save variables')
@@ -62,7 +62,7 @@ shared_examples 'variable list' do
page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
end
end
@@ -234,12 +234,14 @@ shared_examples 'variable list' do
end
it 'edits variable to be unmasked' do
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('unmasked_key')
+ find('.js-ci-variable-input-value').set('unmasked_value')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
find('.ci-variable-masked-item .js-project-feature-toggle').click
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
end
click_button('Save variables')
@@ -248,12 +250,6 @@ shared_examples 'variable list' do
visit page_path
page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
- expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
- end
- end
-
- it 'edits variable to be masked' do
- page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
find('.ci-variable-masked-item .js-project-feature-toggle').click
@@ -268,6 +264,14 @@ shared_examples 'variable list' do
page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
+ end
+ end
+
+ it 'edits variable to be masked' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('masked_key')
+ find('.js-ci-variable-input-value').set('masked_value')
+ expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
find('.ci-variable-masked-item .js-project-feature-toggle').click
@@ -348,10 +352,11 @@ shared_examples 'variable list' do
end
end
- it 'shows validation error box about empty values' do
+ it 'shows validation error box about masking empty values' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('empty_value')
find('.js-ci-variable-input-value').set('')
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
end
click_button('Save variables')
@@ -367,6 +372,7 @@ shared_examples 'variable list' do
page.within('.js-ci-variable-list-section .js-row:last-child') do
find('.js-ci-variable-input-key').set('unmaskable_value')
find('.js-ci-variable-input-value').set('???')
+ find('.ci-variable-masked-item .js-project-feature-toggle').click
end
click_button('Save variables')
diff --git a/spec/support/helpers/git_helpers.rb b/spec/support/helpers/git_helpers.rb
index 99a7c39852e..99c5871ba54 100644
--- a/spec/support/helpers/git_helpers.rb
+++ b/spec/support/helpers/git_helpers.rb
@@ -6,12 +6,4 @@ module GitHelpers
Rugged::Repository.new(path)
end
-
- def project_hook_exists?(project)
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- project_path = project.repository.raw_repository.path
-
- File.exist?(File.join(project_path, 'hooks', 'post-receive'))
- end
- end
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 44ed9da25fc..e95c7f2a6d6 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -134,6 +134,10 @@ module GraphqlHelpers
end.join(", ")
end
+ def post_multiplex(queries, current_user: nil, headers: {})
+ post api('/', current_user, version: 'graphql'), params: { _json: queries }, headers: headers
+ end
+
def post_graphql(query, current_user: nil, variables: nil, headers: {})
post api('/', current_user, version: 'graphql'), params: { query: query, variables: variables }, headers: headers
end
@@ -147,7 +151,14 @@ module GraphqlHelpers
end
def graphql_errors
- json_response['errors']
+ case json_response
+ when Hash # regular query
+ json_response['errors']
+ when Array # multiplexed queries
+ json_response.map { |response| response['errors'] }
+ else
+ raise "Unkown GraphQL response type #{json_response.class}"
+ end
end
def graphql_mutation_response(mutation_name)
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 78b7ae9c00c..011c4df0fe5 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -17,17 +17,38 @@ module KubernetesHelpers
kube_response(kube_deployments_body)
end
- def stub_kubeclient_discover(api_url)
+ def stub_kubeclient_discover_base(api_url)
WebMock.stub_request(:get, api_url + '/api/v1').to_return(kube_response(kube_v1_discovery_body))
- WebMock.stub_request(:get, api_url + '/apis/extensions/v1beta1').to_return(kube_response(kube_v1beta1_discovery_body))
- WebMock.stub_request(:get, api_url + '/apis/rbac.authorization.k8s.io/v1').to_return(kube_response(kube_v1_rbac_authorization_discovery_body))
- WebMock.stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1').to_return(kube_response(kube_v1alpha1_serving_knative_discovery_body))
+ WebMock
+ .stub_request(:get, api_url + '/apis/extensions/v1beta1')
+ .to_return(kube_response(kube_v1beta1_discovery_body))
+ WebMock
+ .stub_request(:get, api_url + '/apis/rbac.authorization.k8s.io/v1')
+ .to_return(kube_response(kube_v1_rbac_authorization_discovery_body))
+ end
+
+ def stub_kubeclient_discover(api_url)
+ stub_kubeclient_discover_base(api_url)
+
+ WebMock
+ .stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1')
+ .to_return(kube_response(kube_v1alpha1_serving_knative_discovery_body))
+ end
+
+ def stub_kubeclient_discover_knative_not_found(api_url)
+ stub_kubeclient_discover_base(api_url)
+
+ WebMock
+ .stub_request(:get, api_url + '/apis/serving.knative.dev/v1alpha1')
+ .to_return(status: [404, "Resource Not Found"])
end
- def stub_kubeclient_service_pods(status: nil)
+ def stub_kubeclient_service_pods(response = nil, options = {})
stub_kubeclient_discover(service.api_url)
- pods_url = service.api_url + "/api/v1/pods"
- response = { status: status } if status
+
+ namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""
+
+ pods_url = service.api_url + "/api/v1/#{namespace_path}pods"
WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
end
@@ -56,15 +77,18 @@ module KubernetesHelpers
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
- def stub_kubeclient_knative_services(**options)
+ def stub_kubeclient_knative_services(options = {})
+ namespace_path = options[:namespace].present? ? "namespaces/#{options[:namespace]}/" : ""
+
options[:name] ||= "kubetest"
- options[:namespace] ||= "default"
options[:domain] ||= "example.com"
+ options[:response] ||= kube_response(kube_knative_services_body(options))
stub_kubeclient_discover(service.api_url)
- knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/services"
- WebMock.stub_request(:get, knative_url).to_return(kube_response(kube_knative_services_body(options)))
+ knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/#{namespace_path}services"
+
+ WebMock.stub_request(:get, knative_url).to_return(options[:response])
end
def stub_kubeclient_get_secret(api_url, **options)
diff --git a/spec/support/helpers/lets_encrypt_helpers.rb b/spec/support/helpers/lets_encrypt_helpers.rb
index 7f0886b451c..2857416ad95 100644
--- a/spec/support/helpers/lets_encrypt_helpers.rb
+++ b/spec/support/helpers/lets_encrypt_helpers.rb
@@ -1,6 +1,26 @@
# frozen_string_literal: true
module LetsEncryptHelpers
+ ACME_ORDER_METHODS = {
+ url: 'https://example.com/',
+ status: 'valid',
+ expires: 2.days.from_now
+ }.freeze
+
+ ACME_CHALLENGE_METHODS = {
+ status: 'pending',
+ token: 'tokenvalue',
+ file_content: 'hereisfilecontent',
+ request_validation: true
+ }.freeze
+
+ def stub_lets_encrypt_settings
+ stub_application_setting(
+ lets_encrypt_notification_email: 'myemail@test.example.com',
+ lets_encrypt_terms_of_service_accepted: true
+ )
+ end
+
def stub_lets_encrypt_client
client = instance_double('Acme::Client')
@@ -16,4 +36,24 @@ module LetsEncryptHelpers
client
end
+
+ def acme_challenge_double
+ challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01')
+ allow(challenge).to receive_messages(ACME_CHALLENGE_METHODS)
+ challenge
+ end
+
+ def acme_authorization_double
+ authorization = instance_double('Acme::Client::Resources::Authorization')
+ allow(authorization).to receive(:http).and_return(acme_challenge_double)
+ authorization
+ end
+
+ def acme_order_double(attributes = {})
+ acme_order = instance_double('Acme::Client::Resources::Order')
+ allow(acme_order).to receive_messages(ACME_ORDER_METHODS.merge(attributes))
+ allow(acme_order).to receive(:authorizations).and_return([acme_authorization_double])
+ allow(acme_order).to receive(:finalize)
+ acme_order
+ end
end
diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb
new file mode 100644
index 00000000000..5cad35282c0
--- /dev/null
+++ b/spec/support/helpers/stub_requests.rb
@@ -0,0 +1,40 @@
+module StubRequests
+ IP_ADDRESS_STUB = '8.8.8.9'.freeze
+
+ # Fully stubs a request using WebMock class. This class also
+ # stubs the IP address the URL is translated to (DNS lookup).
+ #
+ # It expects the final request to go to the `ip_address` instead the given url.
+ # That's primarily a DNS rebind attack prevention of Gitlab::HTTP
+ # (see: Gitlab::UrlBlocker).
+ #
+ def stub_full_request(url, ip_address: IP_ADDRESS_STUB, port: 80, method: :get)
+ stub_dns(url, ip_address: ip_address, port: port)
+
+ url = stubbed_hostname(url, hostname: ip_address)
+ WebMock.stub_request(method, url)
+ end
+
+ def stub_dns(url, ip_address:, port: 80)
+ url = parse_url(url)
+ socket = Socket.sockaddr_in(port, ip_address)
+ addr = Addrinfo.new(socket)
+
+ # See Gitlab::UrlBlocker
+ allow(Addrinfo).to receive(:getaddrinfo)
+ .with(url.hostname, url.port, nil, :STREAM)
+ .and_return([addr])
+ end
+
+ def stubbed_hostname(url, hostname: IP_ADDRESS_STUB)
+ url = parse_url(url)
+ url.hostname = hostname
+ url.to_s
+ end
+
+ private
+
+ def parse_url(url)
+ url.is_a?(URI) ? url : URI(url)
+ end
+end
diff --git a/spec/support/matchers/eq_pem.rb b/spec/support/matchers/eq_pem.rb
new file mode 100644
index 00000000000..158281e4a19
--- /dev/null
+++ b/spec/support/matchers/eq_pem.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :eq_pem do |expected_pem_string|
+ match do |actual|
+ actual.to_pem == expected_pem_string
+ end
+
+ description do
+ "contain pem #{expected_pem_string}"
+ end
+end
diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
index 54d9f5b15f2..1aa40dcde3d 100644
--- a/spec/support/shared_context/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -64,7 +64,7 @@ RSpec.shared_context 'ProjectPolicy context' do
%i[
change_namespace change_visibility_level rename_project remove_project
archive_project remove_fork_project destroy_merge_request destroy_issue
- set_issue_iid set_issue_created_at set_note_created_at
+ set_issue_iid set_issue_created_at set_issue_updated_at set_note_created_at
]
end
diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
index 982e0317f7f..b7080c68270 100644
--- a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
+++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
@@ -9,87 +9,87 @@
# - `filepath`: path of the file (contains filename)
# - `subject`: the request to be made to the controller. Example:
# subject { get :show, namespace_id: project.namespace, project_id: project }
-shared_examples 'repository lfs file load' do
- context 'when file is stored in lfs' do
- let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
- let(:lfs_size) { '1575078' }
- let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
+shared_examples 'a controller that can serve LFS files' do
+ let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
+ let(:lfs_size) { '1575078' }
+ let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
+
+ context 'when lfs is enabled' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+ end
- context 'when lfs is enabled' do
+ context 'when project has access' do
before do
- allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+ project.lfs_objects << lfs_object
+ allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
+ allow(controller).to receive(:send_file) { controller.head :ok }
end
- context 'when project has access' do
- before do
- project.lfs_objects << lfs_object
- allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
- allow(controller).to receive(:send_file) { controller.head :ok }
- end
+ it 'serves the file' do
+ lfs_uploader = LfsObjectUploader.new(lfs_object)
- it 'serves the file' do
- # Notice the filename= is omitted from the disposition; this is because
- # Rails 5 will append this header in send_file
- expect(controller).to receive(:send_file)
- .with(
- "#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897",
- filename: filename,
- disposition: %Q(attachment; filename*=UTF-8''#{filename}))
+ # Notice the filename= is omitted from the disposition; this is because
+ # Rails 5 will append this header in send_file
+ expect(controller).to receive(:send_file)
+ .with(
+ File.join(lfs_uploader.root, lfs_uploader.store_dir, lfs_uploader.filename),
+ filename: filename,
+ disposition: %Q(attachment; filename*=UTF-8''#{filename}))
- subject
+ subject
- expect(response).to have_gitlab_http_status(200)
- end
+ expect(response).to have_gitlab_http_status(200)
+ end
- context 'and lfs uses object storage' do
- let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) }
+ context 'and lfs uses object storage' do
+ let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) }
- before do
- stub_lfs_object_storage
- lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
- end
+ before do
+ stub_lfs_object_storage
+ lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+ end
- it 'responds with redirect to file' do
- subject
+ it 'responds with redirect to file' do
+ subject
- expect(response).to have_gitlab_http_status(302)
- expect(response.location).to include(lfs_object.reload.file.path)
- end
+ expect(response).to have_gitlab_http_status(302)
+ expect(response.location).to include(lfs_object.reload.file.path)
+ end
- it 'sets content disposition' do
- subject
+ it 'sets content disposition' do
+ subject
- file_uri = URI.parse(response.location)
- params = CGI.parse(file_uri.query)
+ file_uri = URI.parse(response.location)
+ params = CGI.parse(file_uri.query)
- expect(params["response-content-disposition"].first).to eq(%q(attachment; filename="lfs_object.iso"; filename*=UTF-8''lfs_object.iso))
- end
+ expect(params["response-content-disposition"].first).to eq(%Q(attachment; filename="#{filename}"; filename*=UTF-8''#{filename}))
end
end
+ end
- context 'when project does not have access' do
- it 'does not serve the file' do
- subject
+ context 'when project does not have access' do
+ it 'does not serve the file' do
+ subject
- expect(response).to have_gitlab_http_status(404)
- end
+ expect(response).to have_gitlab_http_status(404)
end
end
+ end
- context 'when lfs is not enabled' do
- before do
- allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
- end
+ context 'when lfs is not enabled' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
+ end
- it 'delivers ASCII file' do
- subject
+ it 'delivers ASCII file' do
+ subject
- expect(response).to have_gitlab_http_status(200)
- expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(response.header['Content-Disposition'])
- .to eq('inline')
- expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
- end
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.header['Content-Disposition'])
+ .to eq('inline')
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
end
diff --git a/spec/support/shared_examples/finders/assignees_filter_spec.rb b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb
index 782a2d97746..a931c4df99f 100644
--- a/spec/support/shared_examples/finders/assignees_filter_spec.rb
+++ b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb
@@ -20,12 +20,6 @@ shared_examples 'no assignee filter' do
end
it 'returns issuables not assigned to any assignee' do
- params[:assignee_id] = 0
-
- expect(issuables).to contain_exactly(*expected_issuables)
- end
-
- it 'returns issuables not assigned to any assignee' do
params[:assignee_id] = 'none'
expect(issuables).to contain_exactly(*expected_issuables)
diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
index a248f60d23e..a248f60d23e 100644
--- a/spec/support/shared_examples/models/atomic_internal_id_spec.rb
+++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
diff --git a/spec/support/shared_examples/models/chat_service_spec.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb
index 0a302e7d030..0a302e7d030 100644
--- a/spec/support/shared_examples/models/chat_service_spec.rb
+++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb
diff --git a/spec/support/shared_examples/models/update_project_statistics_spec.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
index 7a04e940ee5..1b09c3dd636 100644
--- a/spec/support/shared_examples/models/update_project_statistics_spec.rb
+++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
@@ -3,16 +3,16 @@
require 'spec_helper'
shared_examples_for 'UpdateProjectStatistics' do
- let(:project) { subject.project }
- let(:stat) { described_class.statistic_name }
- let(:attribute) { described_class.statistic_attribute }
+ let(:project) { subject.project }
+ let(:project_statistics_name) { described_class.project_statistics_name }
+ let(:statistic_attribute) { described_class.statistic_attribute }
def reload_stat
- project.statistics.reload.send(stat).to_i
+ project.statistics.reload.send(project_statistics_name).to_i
end
def read_attribute
- subject.read_attribute(attribute).to_i
+ subject.read_attribute(statistic_attribute).to_i
end
it { is_expected.to be_new_record }
@@ -39,7 +39,8 @@ shared_examples_for 'UpdateProjectStatistics' do
.to receive(:increment_statistic)
.and_call_original
- subject.write_attribute(attribute, read_attribute + delta)
+ subject.write_attribute(statistic_attribute, read_attribute + delta)
+
expect { subject.save! }
.to change { reload_stat }
.by(delta)
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index 4fff1c4e228..897c9106d77 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -1,5 +1,7 @@
shared_context 'gitlab email notification' do
- set(:project) { create(:project, :repository, name: 'a-known-name') }
+ set(:group) { create(:group) }
+ set(:subgroup) { create(:group, parent: group) }
+ set(:project) { create(:project, :repository, name: 'a-known-name', group: group) }
set(:recipient) { create(:user, email: 'recipient@example.com') }
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
@@ -39,6 +41,47 @@ shared_examples 'an email sent from GitLab' do
end
end
+shared_examples 'an email sent to a user' do
+ let(:group_notification_email) { 'user+group@example.com' }
+
+ it 'is sent to user\'s global notification email address' do
+ expect(subject).to deliver_to(test_recipient.notification_email)
+ end
+
+ context 'that is part of a project\'s group' do
+ it 'is sent to user\'s group notification email address when set' do
+ create(:notification_setting, user: test_recipient, source: project.group, notification_email: group_notification_email)
+ expect(subject).to deliver_to(group_notification_email)
+ end
+
+ it 'is sent to user\'s global notification email address when no group email set' do
+ create(:notification_setting, user: test_recipient, source: project.group, notification_email: '')
+ expect(subject).to deliver_to(test_recipient.notification_email)
+ end
+ end
+
+ context 'when project is in a sub-group', :nested_groups do
+ before do
+ project.update!(group: subgroup)
+ end
+
+ it 'is sent to user\'s subgroup notification email address when set' do
+ # Set top-level group notification email address to make sure it doesn't get selected
+ create(:notification_setting, user: test_recipient, source: group, notification_email: group_notification_email)
+
+ subgroup_notification_email = 'user+subgroup@example.com'
+ create(:notification_setting, user: test_recipient, source: subgroup, notification_email: subgroup_notification_email)
+
+ expect(subject).to deliver_to(subgroup_notification_email)
+ end
+
+ it 'is sent to user\'s group notification email address when set and subgroup email address not set' do
+ create(:notification_setting, user: test_recipient, source: subgroup, notification_email: '')
+ expect(subject).to deliver_to(test_recipient.notification_email)
+ end
+ end
+end
+
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
diff --git a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb b/spec/support/shared_examples/requests/api/issues_shared_examples.rb
index 1133e95e44e..1133e95e44e 100644
--- a/spec/support/shared_examples/requests/api/issues_shared_example_spec.rb
+++ b/spec/support/shared_examples/requests/api/issues_shared_examples.rb
diff --git a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
index 8544fb62b5a..be69c10d7c8 100644
--- a/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
+++ b/spec/tasks/gitlab/artifacts/migrate_rake_spec.rb
@@ -13,61 +13,6 @@ describe 'gitlab:artifacts namespace rake task' do
subject { run_rake_task('gitlab:artifacts:migrate') }
- context 'legacy artifacts' do
- describe 'migrate' do
- let!(:build) { create(:ci_build, :legacy_artifacts, artifacts_file_store: store, artifacts_metadata_store: store) }
-
- context 'when local storage is used' do
- let(:store) { ObjectStorage::Store::LOCAL }
-
- context 'and job does not have file store defined' do
- let(:object_storage_enabled) { true }
- let(:store) { nil }
-
- it "migrates file to remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
- end
- end
-
- context 'and remote storage is defined' do
- let(:object_storage_enabled) { true }
-
- it "migrates file to remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
- end
- end
-
- context 'and remote storage is not defined' do
- it "fails to migrate to remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::LOCAL)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::LOCAL)
- end
- end
- end
-
- context 'when remote storage is used' do
- let(:object_storage_enabled) { true }
-
- let(:store) { ObjectStorage::Store::REMOTE }
-
- it "file stays on remote storage" do
- subject
-
- expect(build.reload.artifacts_file_store).to eq(ObjectStorage::Store::REMOTE)
- expect(build.reload.artifacts_metadata_store).to eq(ObjectStorage::Store::REMOTE)
- end
- end
- end
- end
-
context 'job artifacts' do
let!(:artifact) { create(:ci_job_artifact, :archive, file_store: store) }
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index a9d14070177..c3e912b02c5 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -7,14 +7,8 @@ describe 'gitlab:shell rake tasks' do
stub_warn_user_is_not_gitlab
end
- after do
- TestEnv.sabotage_gitlab_shell_hooks
- end
-
describe 'install task' do
- it 'invokes create_hooks task' do
- expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke)
-
+ it 'installs and compiles gitlab-shell' do
storages = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
Gitlab.config.repositories.storages.values.map(&:legacy_disk_path)
end
@@ -24,14 +18,4 @@ describe 'gitlab:shell rake tasks' do
run_rake_task('gitlab:shell:install')
end
end
-
- describe 'create_hooks task' do
- it 'calls gitlab-shell bin/create_hooks' do
- expect_any_instance_of(Object).to receive(:system)
- .with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks",
- *Gitlab::TaskHelpers.repository_storage_paths_args)
-
- run_rake_task('gitlab:shell:create_hooks')
- end
- end
end
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb
index 555a58e9aa1..4188e7caccb 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_spec.rb
@@ -8,13 +8,13 @@ describe 'tokens rake tasks' do
end
describe 'reset_all_email task' do
- it 'invokes create_hooks task' do
+ it 'changes the incoming email token' do
expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token }
end
end
describe 'reset_all_feed task' do
- it 'invokes create_hooks task' do
+ it 'changes the feed token for the user' do
expect { run_rake_task('tokens:reset_all_feed') }.to change { user.reload.feed_token }
end
end
diff --git a/spec/uploaders/legacy_artifact_uploader_spec.rb b/spec/uploaders/legacy_artifact_uploader_spec.rb
deleted file mode 100644
index 0589563b502..00000000000
--- a/spec/uploaders/legacy_artifact_uploader_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'rails_helper'
-
-describe LegacyArtifactUploader do
- let(:store) { described_class::Store::LOCAL }
- let(:job) { create(:ci_build, artifacts_file_store: store) }
- let(:uploader) { described_class.new(job, :legacy_artifacts_file) }
- let(:local_path) { described_class.root }
-
- subject { uploader }
-
- # TODO: move to Workhorse::UploadPath
- describe '.workhorse_upload_path' do
- subject { described_class.workhorse_upload_path }
-
- it { is_expected.to start_with(local_path) }
- it { is_expected.to end_with('tmp/uploads') }
- end
-
- it_behaves_like "builds correct paths",
- store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z],
- cache_dir: %r[artifacts/tmp/cache],
- work_dir: %r[artifacts/tmp/work]
-
- context 'object store is remote' do
- before do
- stub_artifacts_object_storage
- end
-
- include_context 'with storage', described_class::Store::REMOTE
-
- it_behaves_like "builds correct paths",
- store_dir: %r[\d{4}_\d{1,2}/\d+/\d+\z]
- end
-
- describe '#filename' do
- # we need to use uploader, as this makes to use mounter
- # which initialises uploader.file object
- let(:uploader) { job.artifacts_file }
-
- subject { uploader.filename }
-
- it { is_expected.to be_nil }
- end
-
- context 'file is stored in valid path' do
- let(:file) do
- fixture_file_upload('spec/fixtures/ci_build_artifacts.zip', 'application/zip')
- end
-
- before do
- uploader.store!(file)
- end
-
- subject { uploader.file.path }
-
- it { is_expected.to start_with("#{uploader.root}") }
- it { is_expected.to include("/#{job.created_at.utc.strftime('%Y_%m')}/") }
- it { is_expected.to include("/#{job.project_id}/") }
- it { is_expected.to end_with("ci_build_artifacts.zip") }
- end
-end
diff --git a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
index 95813d15e52..cc8970d2ba0 100644
--- a/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
+++ b/spec/uploaders/workers/object_storage/background_move_worker_spec.rb
@@ -48,40 +48,6 @@ describe ObjectStorage::BackgroundMoveWorker do
end
end
- context 'for legacy artifacts' do
- let(:build) { create(:ci_build, :legacy_artifacts) }
- let(:uploader_class) { LegacyArtifactUploader }
- let(:subject_class) { Ci::Build }
- let(:file_field) { :artifacts_file }
- let(:subject_id) { build.id }
-
- context 'when local storage is used' do
- let(:store) { local }
-
- context 'and remote storage is defined' do
- before do
- stub_artifacts_object_storage(background_upload: true)
- end
-
- it "migrates file to remote storage" do
- perform
-
- expect(build.reload.artifacts_file_store).to eq(remote)
- end
-
- context 'for artifacts_metadata' do
- let(:file_field) { :artifacts_metadata }
-
- it 'migrates metadata to remote storage' do
- perform
-
- expect(build.reload.artifacts_metadata_store).to eq(remote)
- end
- end
- end
- end
- end
-
context 'for job artifacts' do
let(:artifact) { create(:ci_job_artifact, :archive) }
let(:uploader_class) { JobArtifactUploader }
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index 1086546c10d..457dd2e940f 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -27,7 +27,7 @@ describe 'projects/commit/_commit_box.html.haml' do
render
- expect(rendered).to have_text("Pipeline ##{third_pipeline.id} failed")
+ expect(rendered).to have_text("Pipeline ##{third_pipeline.id} (##{third_pipeline.iid}) failed")
end
end
@@ -40,7 +40,7 @@ describe 'projects/commit/_commit_box.html.haml' do
it 'shows correct pipeline description' do
render
- expect(rendered).to have_text "Pipeline ##{pipeline.id} " \
+ expect(rendered).to have_text "Pipeline ##{pipeline.id} (##{pipeline.iid}) " \
'waiting for manual action'
end
end
diff --git a/spec/views/projects/jobs/_build.html.haml_spec.rb b/spec/views/projects/jobs/_build.html.haml_spec.rb
index 1d58891036e..97b25a6976f 100644
--- a/spec/views/projects/jobs/_build.html.haml_spec.rb
+++ b/spec/views/projects/jobs/_build.html.haml_spec.rb
@@ -4,7 +4,7 @@ describe 'projects/ci/jobs/_build' do
include Devise::Test::ControllerHelpers
let(:project) { create(:project, :repository) }
- let(:pipeline) { create(:ci_empty_pipeline, id: 1337, project: project, sha: project.commit.id) }
+ let(:pipeline) { create(:ci_empty_pipeline, id: 1337, iid: 57, project: project, sha: project.commit.id) }
let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', stage_idx: 1, name: 'rspec 0:2', status: :pending) }
before do
@@ -15,14 +15,14 @@ describe 'projects/ci/jobs/_build' do
it 'won\'t include a column with a link to its pipeline by default' do
render partial: 'projects/ci/builds/build', locals: { build: build }
- expect(rendered).not_to have_link('#1337')
- expect(rendered).not_to have_text('#1337 by API')
+ expect(rendered).not_to have_link('#1337 (#57)')
+ expect(rendered).not_to have_text('#1337 (#57) by API')
end
it 'can include a column with a link to its pipeline' do
render partial: 'projects/ci/builds/build', locals: { build: build, pipeline_link: true }
- expect(rendered).to have_link('#1337')
- expect(rendered).to have_text('#1337 by API')
+ expect(rendered).to have_link('#1337 (#57)')
+ expect(rendered).to have_text('#1337 (#57) by API')
end
end
diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
index 8a9ab02eaca..ae47f364296 100644
--- a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
@@ -12,10 +12,10 @@ describe 'projects/notes/_more_actions_dropdown' do
assign(:project, project)
end
- it 'shows Report abuse to GitLab button if not editable and not current users comment' do
+ it 'shows Report abuse to admin button if not editable and not current users comment' do
render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: false, note: note
- expect(rendered).to have_link('Report abuse to GitLab')
+ expect(rendered).to have_link('Report abuse to admin')
end
it 'does not show the More actions button if not editable and current users comment' do
@@ -24,10 +24,10 @@ describe 'projects/notes/_more_actions_dropdown' do
expect(rendered).not_to have_selector('.dropdown.more-actions')
end
- it 'shows Report abuse to GitLab and Delete buttons if editable and not current users comment' do
+ it 'shows Report abuse to admin and Delete buttons if editable and not current users comment' do
render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: true, note: note
- expect(rendered).to have_link('Report abuse to GitLab')
+ expect(rendered).to have_link('Report abuse to admin')
expect(rendered).to have_link('Delete comment')
end
diff --git a/spec/workers/auto_merge_process_worker_spec.rb b/spec/workers/auto_merge_process_worker_spec.rb
new file mode 100644
index 00000000000..616727ce5ca
--- /dev/null
+++ b/spec/workers/auto_merge_process_worker_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AutoMergeProcessWorker do
+ describe '#perform' do
+ subject { described_class.new.perform(merge_request&.id) }
+
+ context 'when merge request is found' do
+ let(:merge_request) { create(:merge_request) }
+
+ it 'executes AutoMergeService' do
+ expect_next_instance_of(AutoMergeService) do |auto_merge|
+ expect(auto_merge).to receive(:process)
+ end
+
+ subject
+ end
+ end
+
+ context 'when merge request is not found' do
+ let(:merge_request) { nil }
+
+ it 'does not execute AutoMergeService' do
+ expect(AutoMergeService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
index bdb5a3801d9..39f676f1057 100644
--- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
@@ -21,7 +21,7 @@ describe ExpireBuildInstanceArtifactsWorker do
end
it 'does remove files' do
- expect(build.reload.artifacts_file.exists?).to be_falsey
+ expect(build.reload.artifacts_file.present?).to be_falsey
end
it 'does remove the job artifact record' do
@@ -40,7 +40,7 @@ describe ExpireBuildInstanceArtifactsWorker do
end
it 'does not remove files' do
- expect(build.reload.artifacts_file.exists?).to be_truthy
+ expect(build.reload.artifacts_file.present?).to be_truthy
end
it 'does not remove the job artifact record' do
@@ -56,7 +56,7 @@ describe ExpireBuildInstanceArtifactsWorker do
end
it 'does not remove files' do
- expect(build.reload.artifacts_file.exists?).to be_truthy
+ expect(build.reload.artifacts_file.present?).to be_truthy
end
it 'does not remove the job artifact record' do
diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb
index 186824a444f..3fb86adee11 100644
--- a/spec/workers/pages_domain_verification_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb
@@ -10,6 +10,13 @@ describe PagesDomainVerificationCronWorker do
let!(:reverify) { create(:pages_domain, :reverify) }
let!(:disabled) { create(:pages_domain, :disabled) }
+ it 'does nothing if the database is read-only' do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ expect(PagesDomainVerificationWorker).not_to receive(:perform_async).with(reverify.id)
+
+ worker.perform
+ end
+
it 'enqueues a PagesDomainVerificationWorker for domains needing verification' do
[reverify, disabled].each do |domain|
expect(PagesDomainVerificationWorker).to receive(:perform_async).with(domain.id)
diff --git a/spec/workers/pages_domain_verification_worker_spec.rb b/spec/workers/pages_domain_verification_worker_spec.rb
index 2f23dcb487f..f51ac1f4323 100644
--- a/spec/workers/pages_domain_verification_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_worker_spec.rb
@@ -8,6 +8,13 @@ describe PagesDomainVerificationWorker do
let(:domain) { create(:pages_domain) }
describe '#perform' do
+ it 'does nothing if the database is read-only' do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+ expect(PagesDomain).not_to receive(:find_by).with(id: domain.id)
+
+ worker.perform(domain.id)
+ end
+
it 'does nothing for a non-existent domain' do
domain.destroy
diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb
index 8c604b13297..9326db34209 100644
--- a/spec/workers/pipeline_schedule_worker_spec.rb
+++ b/spec/workers/pipeline_schedule_worker_spec.rb
@@ -41,16 +41,6 @@ describe PipelineScheduleWorker do
it_behaves_like 'successful scheduling'
- context 'when exclusive lease has already been taken by the other instance' do
- before do
- stub_exclusive_lease_taken(described_class::EXCLUSIVE_LOCK_KEY, timeout: described_class::LOCK_TIMEOUT)
- end
-
- it 'raises an error and does not start creating pipelines' do
- expect { subject }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
- end
- end
-
context 'when the latest commit contains [ci skip]' do
before do
allow_any_instance_of(Ci::Pipeline)
@@ -77,47 +67,19 @@ describe PipelineScheduleWorker do
stub_ci_pipeline_yaml_file(YAML.dump(rspec: { variables: 'rspec' } ))
end
- it 'creates a failed pipeline with the reason' do
- expect { subject }.to change { project.ci_pipelines.count }.by(1)
- expect(Ci::Pipeline.last).to be_config_error
- expect(Ci::Pipeline.last.yaml_errors).not_to be_nil
+ it 'does not creates a new pipeline' do
+ expect { subject }.not_to change { project.ci_pipelines.count }
end
end
end
context 'when the schedule is not runnable by the user' do
- before do
- expect(Gitlab::Sentry)
- .to receive(:track_exception)
- .with(Ci::CreatePipelineService::CreateError,
- issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
- extra: { schedule_id: pipeline_schedule.id } ).once
- end
-
it 'does not deactivate the schedule' do
subject
expect(pipeline_schedule.reload.active).to be_truthy
end
- it 'increments Prometheus counter' do
- expect(Gitlab::Metrics)
- .to receive(:counter)
- .with(:pipeline_schedule_creation_failed_total, "Counter of failed attempts of pipeline schedule creation")
- .and_call_original
-
- subject
- end
-
- it 'logging a pipeline error' do
- expect(Rails.logger)
- .to receive(:error)
- .with(a_string_matching("Insufficient permissions to create a new pipeline"))
- .and_call_original
-
- subject
- end
-
it 'does not create a pipeline' do
expect { subject }.not_to change { project.ci_pipelines.count }
end
@@ -131,21 +93,6 @@ describe PipelineScheduleWorker do
before do
stub_ci_pipeline_yaml_file(nil)
project.add_maintainer(user)
-
- expect(Gitlab::Sentry)
- .to receive(:track_exception)
- .with(Ci::CreatePipelineService::CreateError,
- issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
- extra: { schedule_id: pipeline_schedule.id } ).once
- end
-
- it 'logging a pipeline error' do
- expect(Rails.logger)
- .to receive(:error)
- .with(a_string_matching("Missing .gitlab-ci.yml file"))
- .and_call_original
-
- subject
end
it 'does not create a pipeline' do
diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb
deleted file mode 100644
index 4cbe384b47a..00000000000
--- a/spec/workers/pipeline_success_worker_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe PipelineSuccessWorker do
- describe '#perform' do
- context 'when pipeline exists' do
- let(:pipeline) { create(:ci_pipeline, status: 'success') }
-
- it 'performs "merge when pipeline succeeds"' do
- expect_any_instance_of(
- MergeRequests::MergeWhenPipelineSucceedsService
- ).to receive(:trigger)
-
- described_class.new.perform(pipeline.id)
- end
- end
-
- context 'when pipeline does not exist' do
- it 'does not raise exception' do
- expect { described_class.new.perform(123) }
- .not_to raise_error
- end
- end
- end
-end
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index 3c40269adc7..51afb076da1 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -25,10 +25,11 @@ describe ProjectCacheWorker do
end
context 'with an existing project without a repository' do
- it 'does nothing' do
+ it 'updates statistics but does not refresh the method cashes' do
allow_any_instance_of(Repository).to receive(:exists?).and_return(false)
- expect(worker).not_to receive(:update_statistics)
+ expect(worker).to receive(:update_statistics)
+ expect_any_instance_of(Repository).not_to receive(:refresh_method_caches)
worker.perform(project.id)
end
diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb
index 690af22f4dc..7414470f8e7 100644
--- a/spec/workers/run_pipeline_schedule_worker_spec.rb
+++ b/spec/workers/run_pipeline_schedule_worker_spec.rb
@@ -32,7 +32,37 @@ describe RunPipelineScheduleWorker do
it 'calls the Service' do
expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: pipeline_schedule.ref).and_return(create_pipeline_service)
- expect(create_pipeline_service).to receive(:execute).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule)
+ expect(create_pipeline_service).to receive(:execute!).with(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: pipeline_schedule)
+
+ worker.perform(pipeline_schedule.id, user.id)
+ end
+ end
+
+ context 'when database statement timeout happens' do
+ before do
+ allow(Ci::CreatePipelineService).to receive(:new) { raise ActiveRecord::StatementInvalid }
+
+ expect(Gitlab::Sentry)
+ .to receive(:track_exception)
+ .with(ActiveRecord::StatementInvalid,
+ issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
+ extra: { schedule_id: pipeline_schedule.id } ).once
+ end
+
+ it 'increments Prometheus counter' do
+ expect(Gitlab::Metrics)
+ .to receive(:counter)
+ .with(:pipeline_schedule_creation_failed_total, "Counter of failed attempts of pipeline schedule creation")
+ .and_call_original
+
+ worker.perform(pipeline_schedule.id, user.id)
+ end
+
+ it 'logging a pipeline error' do
+ expect(Rails.logger)
+ .to receive(:error)
+ .with(a_string_matching('ActiveRecord::StatementInvalid'))
+ .and_call_original
worker.perform(pipeline_schedule.id, user.id)
end
diff --git a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
index 18876b71615..0907e2768ba 100644
--- a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
+++ b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
@@ -3,12 +3,19 @@
require 'spec_helper'
describe TodosDestroyer::ConfidentialIssueWorker do
- it "calls the Todos::Destroy::ConfidentialIssueService with the params it was given" do
- service = double
+ let(:service) { double }
- expect(::Todos::Destroy::ConfidentialIssueService).to receive(:new).with(100).and_return(service)
+ it "calls the Todos::Destroy::ConfidentialIssueService with issue_id parameter" do
+ expect(::Todos::Destroy::ConfidentialIssueService).to receive(:new).with(issue_id: 100, project_id: nil).and_return(service)
expect(service).to receive(:execute)
described_class.new.perform(100)
end
+
+ it "calls the Todos::Destroy::ConfidentialIssueService with project_id parameter" do
+ expect(::Todos::Destroy::ConfidentialIssueService).to receive(:new).with(issue_id: nil, project_id: 100).and_return(service)
+ expect(service).to receive(:execute)
+
+ described_class.new.perform(nil, 100)
+ end
end
diff --git a/public/visual-review-toolbar.js b/vendor/assets/javascripts/visual_review_toolbar.js
index dc21197bda9..12a3a4c9672 100644
--- a/public/visual-review-toolbar.js
+++ b/vendor/assets/javascripts/visual_review_toolbar.js
@@ -2,260 +2,11 @@
/////////////////// STYLES ////////////////////
///////////////////////////////////////////////
+// this style must be applied inline
const buttonClearStyles = `
-webkit-appearance: none;
`;
-const buttonBaseStyles = `
- cursor: pointer;
- transition: background-color 100ms linear, border-color 100ms linear, color 100ms linear, box-shadow 100ms linear;
-`;
-
-const buttonSuccessActiveStyles = `
- background-color: #168f48;
- border-color: #12753a;
- color: #fff;
-`;
-
-const buttonSuccessHoverStyles = `
- color: #fff;
- background-color: #137e3f;
- border-color: #127339;
-`;
-
-const buttonSuccessStyles = `
- ${buttonBaseStyles}
- background-color: #1aaa55;
- border: 1px solid #168f48;
- color: #fff;
-`;
-
-const buttonSecondaryStyles = `
- ${buttonBaseStyles}
- background: none #fff;
- margin: 0 .5rem;
- border: 1px solid #e3e3e3;
-`;
-
-const buttonSecondaryActiveStyles = `
- color: #2e2e2e;
- background-color: #e1e1e1;
- border-color: #dadada;
-`;
-
-const buttonSecondaryHoverStyles = `
- background-color: #f0f0f0;
- border-color: #e3e3e3;
- color: #2e2e2e;
-`;
-
-const buttonWideStyles = `
- width: 100%;
-`;
-
-const buttonWrapperStyles = `
- margin-top: 1rem;
- display: flex;
- align-items: baseline;
- justify-content: flex-end;
-`;
-
-const collapseStyles = `
- ${buttonBaseStyles}
- width: 2.4rem;
- height: 2.2rem;
- margin-left: 1rem;
- padding: .5rem;
-`;
-
-const collapseClosedStyles = `
- ${collapseStyles}
- align-self: center;
-`;
-
-const collapseOpenStyles = `
- ${collapseStyles}
-`;
-
-const checkboxLabelStyles = `
- padding: 0 .2rem;
-`;
-
-const checkboxWrapperStyles = `
- display: flex;
- align-items: baseline;
-`;
-
-const formStyles = `
- display: flex;
- flex-direction: column;
- width: 100%
-`;
-
-const labelStyles = `
- font-weight: 600;
- display: inline-block;
- width: 100%;
-`;
-
-const linkStyles = `
- color: #1b69b6;
- text-decoration: none;
- background-color: transparent;
- background-image: none;
-`;
-
-const messageStyles = `
- padding: .25rem 0;
- margin: 0;
- line-height: 1.2rem;
-`;
-
-const metadataNoteStyles = `
- font-size: .7rem;
- line-height: 1rem;
- color: #666;
- margin-bottom: 0;
-`;
-
-const inputStyles = `
- width: 100%;
- border: 1px solid #dfdfdf;
- border-radius: 4px;
- padding: .1rem .2rem;
- min-height: 2rem;
- max-width: 17rem;
-`;
-
-const svgInnerStyles = `
- pointer-events: none;
-`;
-
-const wrapperClosedStyles = `
- max-width: 3.4rem;
- max-height: 3.4rem;
-`;
-
-const wrapperOpenStyles = `
- max-width: 22rem;
- max-height: 22rem;
-`;
-
-const wrapperStyles = `
- max-width: 22rem;
- max-height: 22rem;
- overflow: scroll;
- position: fixed;
- bottom: 1rem;
- right: 1rem;
- display: flex;
- flex-direction: row-reverse;
- padding: 1rem;
- background-color: #fff;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
- 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
- 'Noto Color Emoji';
- font-size: .8rem;
- font-weight: 400;
- color: #2e2e2e;
-`;
-
-const gitlabStyles = `
- #gitlab-collapse > * {
- ${svgInnerStyles}
- }
-
- #gitlab-form-wrapper {
- ${formStyles}
- }
-
- #gitlab-review-container {
- ${wrapperStyles}
- }
-
- .gitlab-open-wrapper {
- ${wrapperOpenStyles}
- }
-
- .gitlab-closed-wrapper {
- ${wrapperClosedStyles}
- }
-
- .gitlab-button-secondary {
- ${buttonSecondaryStyles}
- }
-
- .gitlab-button-secondary:hover {
- ${buttonSecondaryHoverStyles}
- }
-
- .gitlab-button-secondary:active {
- ${buttonSecondaryActiveStyles}
- }
-
- .gitlab-button-success:hover {
- ${buttonSuccessHoverStyles}
- }
-
- .gitlab-button-success:active {
- ${buttonSuccessActiveStyles}
- }
-
- .gitlab-button-success {
- ${buttonSuccessStyles}
- }
-
- .gitlab-button-wide {
- ${buttonWideStyles}
- }
-
- .gitlab-button-wrapper {
- ${buttonWrapperStyles}
- }
-
- .gitlab-collapse-closed {
- ${collapseClosedStyles}
- }
-
- .gitlab-collapse-open {
- ${collapseOpenStyles}
- }
-
- .gitlab-checkbox-label {
- ${checkboxLabelStyles}
- }
-
- .gitlab-checkbox-wrapper {
- ${checkboxWrapperStyles}
- }
-
- .gitlab-label {
- ${labelStyles}
- }
-
- .gitlab-link {
- ${linkStyles}
- }
-
- .gitlab-message {
- ${messageStyles}
- }
-
- .gitlab-metadata-note {
- ${metadataNoteStyles}
- }
-
- .gitlab-input {
- ${inputStyles}
- }
-`;
-
-function addStylesheet() {
- const styleEl = document.createElement('style');
- styleEl.insertAdjacentHTML('beforeend', gitlabStyles);
- document.head.appendChild(styleEl);
-}
-
///////////////////////////////////////////////
/////////////////// STATE ////////////////////
///////////////////////////////////////////////
@@ -275,25 +26,24 @@ const comment = `
<p class='gitlab-metadata-note'>Additional metadata will be included: browser, OS, current page, user agent, and viewport dimensions.</p>
</div>
<div class='gitlab-button-wrapper''>
- <button class='gitlab-button-secondary' style='${buttonClearStyles}' type='button' id='gitlab-logout-button'> Logout </button>
- <button class='gitlab-button-success' style='${buttonClearStyles}' type='button' id='gitlab-comment-button'> Send feedback </button>
+ <button class='gitlab-button gitlab-button-secondary' style='${buttonClearStyles}' type='button' id='gitlab-logout-button'> Logout </button>
+ <button class='gitlab-button gitlab-button-success' style='${buttonClearStyles}' type='button' id='gitlab-comment-button'> Send feedback </button>
</div>
`;
const commentIcon = `
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/comment</title><path d="M4 11.132l1.446-.964A1 1 0 0 1 6 10h5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v6.132zM6.303 12l-2.748 1.832A1 1 0 0 1 2 13V5a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3H6.303z" id="gitlab-comment-icon"/></svg>
-`
+`;
const compressIcon = `
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/compress</title><path d="M5.27 12.182l-1.562 1.561a1 1 0 0 1-1.414 0h-.001a1 1 0 0 1 0-1.415l1.56-1.56L2.44 9.353a.5.5 0 0 1 .353-.854H7.09a.5.5 0 0 1 .5.5v4.294a.5.5 0 0 1-.853.353l-1.467-1.465zm6.911-6.914l1.464 1.464a.5.5 0 0 1-.353.854H8.999a.5.5 0 0 1-.5-.5V2.793a.5.5 0 0 1 .854-.354l1.414 1.415 1.56-1.561a1 1 0 1 1 1.415 1.414l-1.561 1.56z" id="gitlab-compress-icon"/></svg>
`;
const collapseButton = `
- <button id='gitlab-collapse' style='${buttonClearStyles}' class='gitlab-collapse-open gitlab-button-secondary'>${compressIcon}</button>
+ <button id='gitlab-collapse' style='${buttonClearStyles}' class='gitlab-button gitlab-button-secondary gitlab-collapse gitlab-collapse-open'>${compressIcon}</button>
`;
-
-const form = (content) => `
+const form = content => `
<div id='gitlab-form-wrapper'>
${content}
</div>
@@ -310,7 +60,7 @@ const login = `
<label for="remember_token" class='gitlab-checkbox-label'>Remember me</label>
</div>
<div class='gitlab-button-wrapper'>
- <button class='gitlab-button-wide gitlab-button-success' style='${buttonClearStyles}' type='button' id='gitlab-login'> Submit </button>
+ <button class='gitlab-button-wide gitlab-button gitlab-button-success' style='${buttonClearStyles}' type='button' id='gitlab-login'> Submit </button>
</div>
`;
@@ -319,25 +69,25 @@ const login = `
///////////////////////////////////////////////
// from https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator
-function getBrowserId (sUsrAg) {
- var aKeys = ["MSIE", "Edge", "Firefox", "Safari", "Chrome", "Opera"],
- nIdx = aKeys.length - 1;
+function getBrowserId(sUsrAg) {
+ var aKeys = ['MSIE', 'Edge', 'Firefox', 'Safari', 'Chrome', 'Opera'],
+ nIdx = aKeys.length - 1;
for (nIdx; nIdx > -1 && sUsrAg.indexOf(aKeys[nIdx]) === -1; nIdx--);
return aKeys[nIdx];
}
-function addCommentForm () {
+function addCommentForm() {
const formWrapper = document.getElementById('gitlab-form-wrapper');
formWrapper.innerHTML = comment;
}
-function addLoginForm () {
+function addLoginForm() {
const formWrapper = document.getElementById('gitlab-form-wrapper');
formWrapper.innerHTML = login;
}
-function authorizeUser () {
+function authorizeUser() {
// Clear any old errors
clearNote('gitlab-token');
@@ -357,13 +107,12 @@ function authorizeUser () {
return;
}
-function authSuccess (token) {
+function authSuccess(token) {
data.token = token;
addCommentForm();
}
-
-function clearNote (inputId) {
+function clearNote(inputId) {
const note = document.getElementById('gitlab-validation-note');
note.innerText = '';
note.style.color = '';
@@ -374,17 +123,17 @@ function clearNote (inputId) {
}
}
-function confirmAndClear (discussionId) {
+function confirmAndClear(mergeRequestId) {
const commentButton = document.getElementById('gitlab-comment-button');
const note = document.getElementById('gitlab-validation-note');
commentButton.innerText = 'Feedback sent';
- note.innerText = `Your comment was successfully posted to issue #${discussionId}`;
+ note.innerText = `Your comment was successfully posted to merge request #${mergeRequestId}`;
setTimeout(resetCommentButton, 1000);
}
-function getInitialState () {
+function getInitialState() {
const { localStorage } = window;
try {
@@ -396,23 +145,22 @@ function getInitialState () {
}
return login;
-
} catch (err) {
return login;
}
}
-function getProjectDetails () {
- const { innerWidth,
- innerHeight,
- location: { href },
- navigator: {
- platform, userAgent
- } } = window;
+function getProjectDetails() {
+ const {
+ innerWidth,
+ innerHeight,
+ location: { href },
+ navigator: { platform, userAgent },
+ } = window;
const browser = getBrowserId(userAgent);
- const scriptEl = document.getElementById('review-app-toolbar-script')
- const { projectId, discussionId, mrUrl } = scriptEl.dataset;
+ const scriptEl = document.getElementById('review-app-toolbar-script');
+ const { projectId, mergeRequestId, mrUrl } = scriptEl.dataset;
return {
href,
@@ -422,12 +170,12 @@ function getProjectDetails () {
innerWidth,
innerHeight,
projectId,
- discussionId,
+ mergeRequestId,
mrUrl,
};
}
-function logoutUser () {
+function logoutUser() {
const { localStorage } = window;
// All the browsers we support have localStorage, so let's silently fail
@@ -441,7 +189,7 @@ function logoutUser () {
addLoginForm();
}
-function postComment ({
+function postComment({
href,
platform,
browser,
@@ -449,7 +197,7 @@ function postComment ({
innerWidth,
innerHeight,
projectId,
- discussionId,
+ mergeRequestId,
mrUrl,
}) {
// Clear any old errors
@@ -466,42 +214,46 @@ function postComment ({
}
const detailText = `
- <details>
- <summary>Metadata</summary>
- Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}.
- <br /><br />
- *User agent: ${userAgent}*
- </details>
+ \n
+<details>
+ <summary>Metadata</summary>
+ Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}.
+ <br /><br />
+ <em>User agent: ${userAgent}</em>
+</details>
`;
const url = `
- ${mrUrl}/api/v4/projects/${projectId}/issues/${discussionId}/discussions`;
+ ${mrUrl}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/discussions`;
- const body = `${commentText}${detailText}`;
+ const body = `${commentText} ${detailText}`;
fetch(url, {
- method: 'POST',
- headers: {
+ method: 'POST',
+ headers: {
'PRIVATE-TOKEN': data.token,
'Content-Type': 'application/json',
},
body: JSON.stringify({ body }),
})
- .then((response) => {
- if (response.ok) {
- confirmAndClear(discussionId);
- return;
- }
-
- throw new Error(`${response.status}: ${response.statusText}`)
- })
- .catch((err) => {
- postError(`The feedback was not sent successfully. Please try again. Error: ${err.message}`, 'gitlab-comment');
- resetCommentBox();
- });
+ .then(response => {
+ if (response.ok) {
+ confirmAndClear(mergeRequestId);
+ return;
+ }
+
+ throw new Error(`${response.status}: ${response.statusText}`);
+ })
+ .catch(err => {
+ postError(
+ `The feedback was not sent successfully. Please try again. Error: ${err.message}`,
+ 'gitlab-comment',
+ );
+ resetCommentBox();
+ });
}
-function postError (message, inputId) {
+function postError(message, inputId) {
const note = document.getElementById('gitlab-validation-note');
const field = document.getElementById(inputId);
field.style.borderColor = '#db3b21';
@@ -541,8 +293,7 @@ function setInProgressState() {
commentBox.style.pointerEvents = 'none';
}
-function storeToken (token) {
-
+function storeToken(token) {
const { localStorage } = window;
// All the browsers we support have localStorage, so let's silently fail
@@ -554,7 +305,7 @@ function storeToken (token) {
}
}
-function toggleForm () {
+function toggleForm() {
const container = document.getElementById('gitlab-review-container');
const collapseButton = document.getElementById('gitlab-collapse');
const form = document.getElementById('gitlab-form-wrapper');
@@ -576,7 +327,7 @@ function toggleForm () {
display: 'none',
backgroundColor: 'rgba(255, 255, 255, 0)',
},
- }
+ };
const nextState = collapseButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
@@ -591,9 +342,9 @@ function toggleForm () {
///////////////// INJECTION //////////////////
///////////////////////////////////////////////
-function noop() {};
+function noop() {}
-const eventLookup = ({target: { id }}) => {
+const eventLookup = ({ target: { id } }) => {
switch (id) {
case 'gitlab-collapse':
return toggleForm;
@@ -618,9 +369,9 @@ window.addEventListener('load', () => {
container.insertAdjacentHTML('beforeend', form(content));
document.body.insertBefore(container, document.body.firstChild);
- addStylesheet();
- document.getElementById('gitlab-review-container').addEventListener('click', (event) => {
+ document.getElementById('gitlab-review-container').addEventListener('click', event => {
eventLookup(event)();
});
+
});
diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml
index a5e13fdc104..0fbf36b39cc 100644
--- a/vendor/jupyter/values.yaml
+++ b/vendor/jupyter/values.yaml
@@ -4,9 +4,9 @@ rbac:
hub:
extraEnv:
JUPYTER_ENABLE_LAB: 1
- SINGLEUSER_IMAGE: 'registry.gitlab.com/gitlab-org/jupyterhub-user-image:latest'
extraConfig: |
c.KubeSpawner.cmd = ['jupyter-labhub']
+ c.GitLabOAuthenticator.scope = ['api read_repository write_repository']
async def add_auth_env(spawner):
'''
@@ -23,6 +23,8 @@ hub:
spawner.environment['GITLAB_ACCESS_TOKEN'] = auth_state['access_token']
spawner.environment['GITLAB_USER_LOGIN'] = auth_state['gitlab_user']['username']
spawner.environment['GITLAB_USER_ID'] = str(auth_state['gitlab_user']['id'])
+ spawner.environment['GITLAB_USER_EMAIL'] = auth_state['gitlab_user']['email']
+ spawner.environment['GITLAB_USER_NAME'] = auth_state['gitlab_user']['name']
c.KubeSpawner.pre_spawn_hook = add_auth_env
@@ -33,10 +35,22 @@ auth:
singleuser:
defaultUrl: "/lab"
+ image:
+ name: registry.gitlab.com/gitlab-org/jupyterhub-user-image
+ tag: latest
lifecycleHooks:
postStart:
exec:
- command: ["sh", "-c", "git clone https://gitlab.com/gitlab-org/nurtch-demo.git DevOps-Runbook-Demo || true"]
+ command:
+ - "sh"
+ - "-c"
+ - >
+ git clone https://gitlab.com/gitlab-org/nurtch-demo.git DevOps-Runbook-Demo || true;
+ echo "https://${GITLAB_USER_LOGIN}:${GITLAB_ACCESS_TOKEN}@${GITLAB_HOST}" > ~/.git-credentials;
+ git config --global credential.helper store;
+ git config --global user.email "${GITLAB_USER_EMAIL}";
+ git config --global user.name "${GITLAB_USER_NAME}";
+ jupyter serverextension enable --py jupyterlab_git
ingress:
enabled: true
diff --git a/yarn.lock b/yarn.lock
index 898159975fa..b6d31ea08e6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9,37 +9,17 @@
dependencies:
"@babel/highlight" "^7.0.0"
-"@babel/core@>=7.1.0", "@babel/core@^7.1.0":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b"
- integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.3.4"
- "@babel/helpers" "^7.2.0"
- "@babel/parser" "^7.3.4"
- "@babel/template" "^7.2.2"
- "@babel/traverse" "^7.3.4"
- "@babel/types" "^7.3.4"
- convert-source-map "^1.1.0"
- debug "^4.1.0"
- json5 "^2.1.0"
- lodash "^4.17.11"
- resolve "^1.3.2"
- semver "^5.4.1"
- source-map "^0.5.0"
-
-"@babel/core@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.4.tgz#84055750b05fcd50f9915a826b44fa347a825250"
- integrity sha512-lQgGX3FPRgbz2SKmhMtYgJvVzGZrmjaF4apZ2bLwofAKiSjxU0drPh4S/VasyYXwaTs+A1gvQ45BN8SQJzHsQQ==
+"@babel/core@>=7.1.0", "@babel/core@^7.1.0", "@babel/core@^7.4.4":
+ version "7.4.5"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a"
+ integrity sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/generator" "^7.4.4"
"@babel/helpers" "^7.4.4"
- "@babel/parser" "^7.4.4"
+ "@babel/parser" "^7.4.5"
"@babel/template" "^7.4.4"
- "@babel/traverse" "^7.4.4"
+ "@babel/traverse" "^7.4.5"
"@babel/types" "^7.4.4"
convert-source-map "^1.1.0"
debug "^4.1.0"
@@ -49,18 +29,7 @@
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.0.0":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.4.tgz#9aa48c1989257877a9d971296e5b73bfe72e446e"
- integrity sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==
- dependencies:
- "@babel/types" "^7.3.4"
- jsesc "^2.5.1"
- lodash "^4.17.11"
- source-map "^0.5.0"
- trim-right "^1.0.1"
-
-"@babel/generator@^7.3.4", "@babel/generator@^7.4.4":
+"@babel/generator@^7.4.0", "@babel/generator@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041"
integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==
@@ -161,19 +130,7 @@
dependencies:
"@babel/types" "^7.0.0"
-"@babel/helper-module-transforms@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz#470d4f9676d9fad50b324cdcce5fbabbc3da5787"
- integrity sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw==
- dependencies:
- "@babel/helper-module-imports" "^7.0.0"
- "@babel/helper-simple-access" "^7.1.0"
- "@babel/helper-split-export-declaration" "^7.0.0"
- "@babel/template" "^7.1.0"
- "@babel/types" "^7.0.0"
- lodash "^4.17.10"
-
-"@babel/helper-module-transforms@^7.4.4":
+"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz#96115ea42a2f139e619e98ed46df6019b94414b8"
integrity sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==
@@ -197,14 +154,7 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250"
integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==
-"@babel/helper-regex@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0.tgz#2c1718923b57f9bbe64705ffe5640ac64d9bdb27"
- integrity sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==
- dependencies:
- lodash "^4.17.10"
-
-"@babel/helper-regex@^7.4.4":
+"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.4.4.tgz#a47e02bc91fb259d2e6727c2a30013e3ac13c4a2"
integrity sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==
@@ -222,17 +172,7 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-replace-supers@^7.1.0":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz#19970020cf22677d62b3a689561dbd9644d8c5e5"
- integrity sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==
- dependencies:
- "@babel/helper-member-expression-to-functions" "^7.0.0"
- "@babel/helper-optimise-call-expression" "^7.0.0"
- "@babel/traverse" "^7.2.3"
- "@babel/types" "^7.0.0"
-
-"@babel/helper-replace-supers@^7.4.4":
+"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz#aee41783ebe4f2d3ab3ae775e1cc6f1a90cefa27"
integrity sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==
@@ -250,7 +190,7 @@
"@babel/template" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-split-export-declaration@^7.0.0", "@babel/helper-split-export-declaration@^7.4.4":
+"@babel/helper-split-export-declaration@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677"
integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==
@@ -267,7 +207,7 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helpers@^7.2.0", "@babel/helpers@^7.4.4":
+"@babel/helpers@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5"
integrity sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==
@@ -285,15 +225,10 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c"
- integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==
-
-"@babel/parser@^7.2.2", "@babel/parser@^7.3.4", "@babel/parser@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.4.tgz#5977129431b8fe33471730d255ce8654ae1250b6"
- integrity sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==
+"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5":
+ version "7.4.5"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872"
+ integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==
"@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0"
@@ -515,16 +450,7 @@
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-modules-commonjs@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz#c4f1933f5991d5145e9cfad1dfd848ea1727f404"
- integrity sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==
- dependencies:
- "@babel/helper-module-transforms" "^7.1.0"
- "@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-simple-access" "^7.1.0"
-
-"@babel/plugin-transform-modules-commonjs@^7.4.4":
+"@babel/plugin-transform-modules-commonjs@^7.2.0", "@babel/plugin-transform-modules-commonjs@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz#0bef4713d30f1d78c2e59b3d6db40e60192cac1e"
integrity sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==
@@ -706,16 +632,7 @@
resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.3.4.tgz#b622c1e522acef91b2a14f22bdcdd4f935a1a474"
integrity sha512-4L9c5i4WlGqbrjOVX0Yp8TIR5cEiw1/tPYYZENW/iuO2uI6viY38U7zALidzNfGdZIwNc+A/AWqMEWKeScWkBg==
-"@babel/template@^7.0.0":
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
- integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/parser" "^7.2.2"
- "@babel/types" "^7.2.2"
-
-"@babel/template@^7.1.0", "@babel/template@^7.2.2", "@babel/template@^7.4.4":
+"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237"
integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==
@@ -724,37 +641,22 @@
"@babel/parser" "^7.4.4"
"@babel/types" "^7.4.4"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.2.3":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06"
- integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.3.4"
- "@babel/helper-function-name" "^7.1.0"
- "@babel/helper-split-export-declaration" "^7.0.0"
- "@babel/parser" "^7.3.4"
- "@babel/types" "^7.3.4"
- debug "^4.1.0"
- globals "^11.1.0"
- lodash "^4.17.11"
-
-"@babel/traverse@^7.3.4", "@babel/traverse@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.4.tgz#0776f038f6d78361860b6823887d4f3937133fe8"
- integrity sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5":
+ version "7.4.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216"
+ integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/generator" "^7.4.4"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.4"
- "@babel/parser" "^7.4.4"
+ "@babel/parser" "^7.4.5"
"@babel/types" "^7.4.4"
debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.11"
-"@babel/types@^7.0.0", "@babel/types@^7.2.2", "@babel/types@^7.3.4", "@babel/types@^7.4.4":
+"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0"
integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==
@@ -763,6 +665,14 @@
lodash "^4.17.11"
to-fast-properties "^2.0.0"
+"@cnakazawa/watch@^1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
+ integrity sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==
+ dependencies:
+ exec-sh "^0.3.2"
+ minimist "^1.2.0"
+
"@gitlab/csslab@^1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.9.0.tgz#22fca5b1a30cbd9ca46fc6f9485ecbaba4dc300c"
@@ -770,10 +680,10 @@
dependencies:
bootstrap "^4.1.3"
-"@gitlab/eslint-config@^1.5.0":
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.5.0.tgz#0c8c3ae74f276eb6671bd7c60f331bc0f2d2e5cf"
- integrity sha512-KgJgoIZNpGauFpCV1iCptesYN7I8abtYRBLU9xcH0oocC/xp3JmbLfsZ+lEtrk8pl99Q2mKiAuaPpzxjXr6hBw==
+"@gitlab/eslint-config@^1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.6.0.tgz#1fd247d6ab477d53d4c330e05f007e3afa303689"
+ integrity sha512-EZffCwsRZmRWPP6N3wp20EJDVGYLG1v43/W7fF/gYQpUjcRclC8ks/jEv8UppasSDlanDmkh1bLWoE9CelSyyw==
dependencies:
babel-eslint "^10.0.1"
eslint-config-airbnb-base "^13.1.0"
@@ -783,15 +693,15 @@
eslint-plugin-promise "^4.1.1"
eslint-plugin-vue "^5.0.0"
-"@gitlab/svgs@^1.60.0":
- version "1.60.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.60.0.tgz#c24fe0d40193f4b722e858c2f766b1c80f961cb7"
- integrity sha512-Io13vB7LMmPHwUXOoHI+dBYRKDoL0E1dw0b+kJOjesFo+uogaggXJJ4WNEGbWTueYyLckv1YhePACG2xgoLWow==
+"@gitlab/svgs@^1.63.0":
+ version "1.63.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.63.0.tgz#9dd544026d203e4ce6efed72b05db68f710c4d49"
+ integrity sha512-YztrReFTg31B7v5wtUC5j15KHNcMebtW+kACytEU42XomMaIwk4USIbygqWlq0VRHA2VHJrHApfJHIjxiCCQcA==
-"@gitlab/ui@^3.10.0":
- version "3.10.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-3.10.0.tgz#0f16772b7fe8052dabd37aba2ae436255b9e0f0a"
- integrity sha512-po6fh2T8esa2Nach73AYLdoTg8N0YrRa5GkJk5GoxVrHYoAUD8T1Rn3pXXXKSsQdQcYjIZJ6uvY8sL+qg+Yjww==
+"@gitlab/ui@^3.11.0":
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-3.11.0.tgz#7bba82c893f47abbfe7995281dc0ce95290dcc4e"
+ integrity sha512-55Qxyj2wZILznZJUTUxY1SUuw028IgmP6ZyLd5XF3xk91HWSyq5/zrlr/qRTFGL1cABhxoBLScmXsnOc2CIO0w==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
@@ -802,7 +712,7 @@
js-beautify "^1.8.8"
lodash "^4.17.11"
url-search-params-polyfill "^5.0.0"
- vue "^2.5.21"
+ vue "^2.6.10"
vue-loader "^15.4.2"
"@gitlab/vue-toasted@^1.2.1":
@@ -810,6 +720,152 @@
resolved "https://registry.yarnpkg.com/@gitlab/vue-toasted/-/vue-toasted-1.2.1.tgz#f407b5aa710863e5b7f021f4a1f66160331ab263"
integrity sha512-ve2PLxKqrwNpsd+4bV5zGJT5+H5N/VJBZoFS2Vp1mH5cUDBYIHTzDmbS6AbBGUDh0F3TxmFMiqfXfpO/1VjBNQ==
+"@jest/console@^24.7.1":
+ version "24.7.1"
+ resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.7.1.tgz#32a9e42535a97aedfe037e725bd67e954b459545"
+ integrity sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==
+ dependencies:
+ "@jest/source-map" "^24.3.0"
+ chalk "^2.0.1"
+ slash "^2.0.0"
+
+"@jest/core@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.8.0.tgz#fbbdcd42a41d0d39cddbc9f520c8bab0c33eed5b"
+ integrity sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A==
+ dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/reporters" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ ansi-escapes "^3.0.0"
+ chalk "^2.0.1"
+ exit "^0.1.2"
+ graceful-fs "^4.1.15"
+ jest-changed-files "^24.8.0"
+ jest-config "^24.8.0"
+ jest-haste-map "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-resolve-dependencies "^24.8.0"
+ jest-runner "^24.8.0"
+ jest-runtime "^24.8.0"
+ jest-snapshot "^24.8.0"
+ jest-util "^24.8.0"
+ jest-validate "^24.8.0"
+ jest-watcher "^24.8.0"
+ micromatch "^3.1.10"
+ p-each-series "^1.0.0"
+ pirates "^4.0.1"
+ realpath-native "^1.1.0"
+ rimraf "^2.5.4"
+ strip-ansi "^5.0.0"
+
+"@jest/environment@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.8.0.tgz#0342261383c776bdd652168f68065ef144af0eac"
+ integrity sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw==
+ dependencies:
+ "@jest/fake-timers" "^24.8.0"
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ jest-mock "^24.8.0"
+
+"@jest/fake-timers@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.8.0.tgz#2e5b80a4f78f284bcb4bd5714b8e10dd36a8d3d1"
+ integrity sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw==
+ dependencies:
+ "@jest/types" "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-mock "^24.8.0"
+
+"@jest/reporters@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.8.0.tgz#075169cd029bddec54b8f2c0fc489fd0b9e05729"
+ integrity sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw==
+ dependencies:
+ "@jest/environment" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ chalk "^2.0.1"
+ exit "^0.1.2"
+ glob "^7.1.2"
+ istanbul-lib-coverage "^2.0.2"
+ istanbul-lib-instrument "^3.0.1"
+ istanbul-lib-report "^2.0.4"
+ istanbul-lib-source-maps "^3.0.1"
+ istanbul-reports "^2.1.1"
+ jest-haste-map "^24.8.0"
+ jest-resolve "^24.8.0"
+ jest-runtime "^24.8.0"
+ jest-util "^24.8.0"
+ jest-worker "^24.6.0"
+ node-notifier "^5.2.1"
+ slash "^2.0.0"
+ source-map "^0.6.0"
+ string-length "^2.0.0"
+
+"@jest/source-map@^24.3.0":
+ version "24.3.0"
+ resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.3.0.tgz#563be3aa4d224caf65ff77edc95cd1ca4da67f28"
+ integrity sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==
+ dependencies:
+ callsites "^3.0.0"
+ graceful-fs "^4.1.15"
+ source-map "^0.6.0"
+
+"@jest/test-result@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3"
+ integrity sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng==
+ dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/types" "^24.8.0"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+
+"@jest/test-sequencer@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz#2f993bcf6ef5eb4e65e8233a95a3320248cf994b"
+ integrity sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg==
+ dependencies:
+ "@jest/test-result" "^24.8.0"
+ jest-haste-map "^24.8.0"
+ jest-runner "^24.8.0"
+ jest-runtime "^24.8.0"
+
+"@jest/transform@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5"
+ integrity sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA==
+ dependencies:
+ "@babel/core" "^7.1.0"
+ "@jest/types" "^24.8.0"
+ babel-plugin-istanbul "^5.1.0"
+ chalk "^2.0.1"
+ convert-source-map "^1.4.0"
+ fast-json-stable-stringify "^2.0.0"
+ graceful-fs "^4.1.15"
+ jest-haste-map "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-util "^24.8.0"
+ micromatch "^3.1.10"
+ realpath-native "^1.1.0"
+ slash "^2.0.0"
+ source-map "^0.6.1"
+ write-file-atomic "2.4.1"
+
+"@jest/types@^24.8.0":
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad"
+ integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^1.1.1"
+ "@types/yargs" "^12.0.9"
+
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -833,6 +889,39 @@
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.0.tgz#d1d55958d1fccc5527d4aba29fc9c4b942f563ff"
integrity sha512-7WcbyctkE8GTzogDb0ulRAEw7v8oIS54ft9mQTU7PfM0hp5e+8kpa+HeQ7IQrFbKtJXBKcZ4bh+Em9dTw5L6AQ==
+"@types/babel__core@^7.1.0":
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f"
+ integrity sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc"
+ integrity sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==
+ dependencies:
+ "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307"
+ integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
+ version "7.0.6"
+ resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.6.tgz#328dd1a8fc4cfe3c8458be9477b219ea158fd7b2"
+ integrity sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==
+ dependencies:
+ "@babel/types" "^7.3.0"
+
"@types/events@*":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
@@ -847,6 +936,26 @@
"@types/minimatch" "*"
"@types/node" "*"
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
+ integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==
+
+"@types/istanbul-lib-report@*":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c"
+ integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a"
+ integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+ "@types/istanbul-lib-report" "*"
+
"@types/jquery@^2.0.40":
version "2.0.48"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-2.0.48.tgz#3e90d8cde2d29015e5583017f7830cb3975b2eef"
@@ -872,6 +981,11 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
+"@types/stack-utils@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
+ integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
+
"@types/tapable@*":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
@@ -917,6 +1031,11 @@
"@types/uglify-js" "*"
source-map "^0.6.0"
+"@types/yargs@^12.0.2", "@types/yargs@^12.0.9":
+ version "12.0.12"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
+ integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==
+
"@types/zen-observable@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
@@ -1201,9 +1320,9 @@ ansi-escapes@^1.1.0:
integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
ansi-escapes@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
- integrity sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
+ integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
ansi-html@0.0.7:
version "0.0.7"
@@ -1220,10 +1339,10 @@ ansi-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
-ansi-regex@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9"
- integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==
+ansi-regex@^4.0.0, ansi-regex@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
+ integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
ansi-styles@^2.2.1:
version "2.2.1"
@@ -1279,6 +1398,24 @@ apollo-client@^2.5.1:
tslib "^1.9.3"
zen-observable "^0.8.0"
+apollo-link-batch-http@^1.2.11:
+ version "1.2.11"
+ resolved "https://registry.yarnpkg.com/apollo-link-batch-http/-/apollo-link-batch-http-1.2.11.tgz#ae42dbcc02820658e1e267d05bf2aae7ac208088"
+ integrity sha512-f+KEdbP51I3AeEaBDW2lKS3eaPK/1IXaTM9F2moj02s1hgC/TzeUORRuUeOExW8ggXveW1Jzp6aYMJ2SQkZJyA==
+ dependencies:
+ apollo-link "^1.2.11"
+ apollo-link-batch "^1.1.12"
+ apollo-link-http-common "^0.2.13"
+ tslib "^1.9.3"
+
+apollo-link-batch@^1.1.12:
+ version "1.1.12"
+ resolved "https://registry.yarnpkg.com/apollo-link-batch/-/apollo-link-batch-1.1.12.tgz#64eb231082f182b0395ef7ab903600627f6c7fe8"
+ integrity sha512-6NqLiB9tEGxRiyhtnX/7CPHkmFG0IXfEP7pC5kirhjV+4KxqBaWvJnJGKpGp7Owgdph7KJlV+9+niOKEkcwreg==
+ dependencies:
+ apollo-link "^1.2.11"
+ tslib "^1.9.3"
+
apollo-link-dedup@^1.0.0:
version "1.0.10"
resolved "https://registry.yarnpkg.com/apollo-link-dedup/-/apollo-link-dedup-1.0.10.tgz#7b94589fe7f969777efd18a129043c78430800ae"
@@ -1286,7 +1423,7 @@ apollo-link-dedup@^1.0.0:
dependencies:
apollo-link "^1.2.3"
-apollo-link-http-common@^0.2.8:
+apollo-link-http-common@^0.2.13, apollo-link-http-common@^0.2.8:
version "0.2.13"
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz#c688f6baaffdc7b269b2db7ae89dae7c58b5b350"
integrity sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA==
@@ -1336,9 +1473,9 @@ aproba@^1.0.3, aproba@^1.1.1:
integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
are-we-there-yet@~1.1.2:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
- integrity sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
+ integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
dependencies:
delegates "^1.0.0"
readable-stream "^2.0.6"
@@ -1485,12 +1622,12 @@ async@1.x, async@^1.5.2:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
-async@^2.0.0, async@^2.5.0, async@^2.6.1:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
- integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
+async@^2.0.0, async@^2.6.1:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
+ integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==
dependencies:
- lodash "^4.17.10"
+ lodash "^4.17.11"
asynckit@^0.4.0:
version "0.4.0"
@@ -1570,13 +1707,16 @@ babel-eslint@^10.0.1:
eslint-scope "3.7.1"
eslint-visitor-keys "^1.0.0"
-babel-jest@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.1.0.tgz#441e23ef75ded3bd547e300ac3194cef87b55190"
- integrity sha512-MLcagnVrO9ybQGLEfZUqnOzv36iQzU7Bj4elm39vCukumLVSfoX+tRy3/jW7lUKc7XdpRmB/jech6L/UCsSZjw==
+babel-jest@^24.1.0, babel-jest@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589"
+ integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw==
dependencies:
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ "@types/babel__core" "^7.1.0"
babel-plugin-istanbul "^5.1.0"
- babel-preset-jest "^24.1.0"
+ babel-preset-jest "^24.6.0"
chalk "^2.4.2"
slash "^2.0.0"
@@ -1606,10 +1746,12 @@ babel-plugin-istanbul@^5.1.0:
istanbul-lib-instrument "^3.0.0"
test-exclude "^5.0.0"
-babel-plugin-jest-hoist@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.1.0.tgz#dfecc491fb15e2668abbd690a697a8fd1411a7f8"
- integrity sha512-gljYrZz8w1b6fJzKcsfKsipSru2DU2DmQ39aB6nV3xQ0DDv3zpIzKGortA5gknrhNnPN8DweaEgrnZdmbGmhnw==
+babel-plugin-jest-hoist@^24.6.0:
+ version "24.6.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz#f7f7f7ad150ee96d7a5e8e2c5da8319579e78019"
+ integrity sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==
+ dependencies:
+ "@types/babel__traverse" "^7.0.6"
babel-plugin-rewire@^1.2.0:
version "1.2.0"
@@ -1625,13 +1767,13 @@ babel-polyfill@6.23.0:
core-js "^2.4.0"
regenerator-runtime "^0.10.0"
-babel-preset-jest@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.1.0.tgz#83bc564fdcd4903641af65ec63f2f5de6b04132e"
- integrity sha512-FfNLDxFWsNX9lUmtwY7NheGlANnagvxq8LZdl5PKnVG3umP+S/g0XbVBfwtA4Ai3Ri/IMkWabBz3Tyk9wdspcw==
+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"
+ integrity sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==
dependencies:
"@babel/plugin-syntax-object-rest-spread" "^7.0.0"
- babel-plugin-jest-hoist "^24.1.0"
+ babel-plugin-jest-hoist "^24.6.0"
babel-runtime@^6.22.0:
version "6.26.0"
@@ -1829,17 +1971,15 @@ braces@^0.1.2:
expand-range "^0.1.0"
braces@^2.3.0, braces@^2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb"
- integrity sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
+ integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==
dependencies:
arr-flatten "^1.1.0"
array-unique "^0.3.2"
- define-property "^1.0.0"
extend-shallow "^2.0.1"
fill-range "^4.0.0"
isobject "^3.0.1"
- kind-of "^6.0.2"
repeat-element "^1.1.2"
snapdragon "^0.8.1"
snapdragon-node "^2.0.1"
@@ -1921,16 +2061,7 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"
-browserslist@^4.4.1:
- version "4.4.1"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.1.tgz#42e828954b6b29a7a53e352277be429478a69062"
- integrity sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==
- dependencies:
- caniuse-lite "^1.0.30000929"
- electron-to-chromium "^1.3.103"
- node-releases "^1.1.3"
-
-browserslist@^4.5.2, browserslist@^4.5.4:
+browserslist@^4.4.1, browserslist@^4.5.2, browserslist@^4.5.4:
version "4.6.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.0.tgz#5274028c26f4d933d5b1323307c1d1da5084c9ff"
integrity sha512-Jk0YFwXBuMOOol8n6FhgkDzn3mY9PYLYGk29zybF05SbRTsMgPqmTNeQQhOghCxq5oFqAXE3u4sYddr4C0uRhg==
@@ -2125,26 +2256,21 @@ camelcase@^4.0.0, camelcase@^4.1.0:
integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
camelcase@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
- integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
-
-caniuse-lite@^1.0.30000929, caniuse-lite@^1.0.30000932:
- version "1.0.30000936"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000936.tgz#5d33b118763988bf721b9b8ad436d0400e4a116b"
- integrity sha512-orX4IdpbFhdNO7bTBhSbahp1EBpqzBc+qrvTRVUFfZgA4zta7TdM6PN5ZxkEUgDnz36m+PfWGcdX7AVfFWItJw==
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+ integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-caniuse-lite@^1.0.30000967:
+caniuse-lite@^1.0.30000932, caniuse-lite@^1.0.30000967:
version "1.0.30000969"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000969.tgz#7664f571f2072657bde70b00a1fc1ba41f1942a9"
integrity sha512-Kus0yxkoAJgVc0bax7S4gLSlFifCa7MnSZL9p9VuS/HIKEL4seaqh28KIQAAO50cD/rJ5CiJkJFapkdDAlhFxQ==
-capture-exit@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f"
- integrity sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=
+capture-exit@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
+ integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==
dependencies:
- rsvp "^3.3.3"
+ rsvp "^4.8.4"
capture-stack-trace@^1.0.0:
version "1.0.0"
@@ -2271,7 +2397,7 @@ chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4:
optionalDependencies:
fsevents "^1.2.2"
-chownr@^1.0.1, chownr@^1.1.1:
+chownr@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==
@@ -2372,9 +2498,9 @@ cliui@^3.2.0:
wrap-ansi "^2.0.0"
cliui@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc"
- integrity sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
+ integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==
dependencies:
string-width "^2.1.1"
strip-ansi "^4.0.0"
@@ -2473,10 +2599,10 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@2, commander@^2.10.0, commander@^2.16.0, commander@^2.18.0, commander@^2.19.0:
- version "2.19.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
- integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
+commander@2, commander@^2.10.0, commander@^2.16.0, commander@^2.18.0, commander@^2.19.0, commander@~2.20.0:
+ version "2.20.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
+ integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
commander@~2.17.1:
version "2.17.1"
@@ -2690,11 +2816,16 @@ 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, core-js@^2.4.1:
+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@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.3.tgz#95700bca5f248f5f78c0ec63e784eca663ec4138"
+ integrity sha512-PWZ+ZfuaKf178BIAg+CRsljwjIMRV8MY00CbZczkR6Zk5LfkSkjGoaab3+bqRQWVITNZxQB7TFYz+CFcyuamvA==
+
core-js@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.3.0.tgz#fab83fbb0b2d8dc85fa636c4b9d34c75420c6d65"
@@ -3264,14 +3395,14 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
-debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
-debug@^3.1.0, debug@^3.2.5:
+debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -3476,10 +3607,10 @@ di@^0.0.1:
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=
-diff-sequences@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.0.0.tgz#cdf8e27ed20d8b8d3caccb4e0c0d8fe31a173013"
- integrity sha512-46OkIuVGBBnrC0soO/4LHu5LHGHx0uhP65OVz8XOrAJpqiCB2aVIuESvjI1F9oqebuvY8lekS1pt6TN7vt7qsw==
+diff-sequences@^24.3.0:
+ version "24.3.0"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
+ integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==
diff@^3.2.0, diff@^3.4.0:
version "3.5.0"
@@ -3678,11 +3809,6 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
-electron-to-chromium@^1.3.103:
- version "1.3.113"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9"
- integrity sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==
-
electron-to-chromium@^1.3.133:
version "1.3.135"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.135.tgz#f5799b95f2bcd8de17cde47d63392d83a4477041"
@@ -3818,24 +3944,25 @@ error-ex@^1.2.0, error-ex@^1.3.1:
is-arrayish "^0.2.1"
es-abstract@^1.5.1, es-abstract@^1.6.1:
- version "1.12.0"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
- integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
+ integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
dependencies:
- es-to-primitive "^1.1.1"
+ es-to-primitive "^1.2.0"
function-bind "^1.1.1"
- has "^1.0.1"
- is-callable "^1.1.3"
+ has "^1.0.3"
+ is-callable "^1.1.4"
is-regex "^1.0.4"
+ object-keys "^1.0.12"
-es-to-primitive@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
- integrity sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=
+es-to-primitive@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
+ integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
dependencies:
- is-callable "^1.1.1"
+ is-callable "^1.1.4"
is-date-object "^1.0.1"
- is-symbol "^1.0.1"
+ is-symbol "^1.0.2"
es6-promise@~3.0.2:
version "3.0.2"
@@ -4156,12 +4283,10 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
-exec-sh@^0.2.0:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36"
- integrity sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==
- dependencies:
- merge "^1.2.0"
+exec-sh@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b"
+ integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==
execa@^0.10.0:
version "0.10.0"
@@ -4251,16 +4376,17 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
-expect@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/expect/-/expect-24.1.0.tgz#88e73301c4c785cde5f16da130ab407bdaf8c0f2"
- integrity sha512-lVcAPhaYkQcIyMS+F8RVwzbm1jro20IG8OkvxQ6f1JfqhVZyyudCwYogQ7wnktlf14iF3ii7ArIUO/mqvrW9Gw==
+expect@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-24.8.0.tgz#471f8ec256b7b6129ca2524b2a62f030df38718d"
+ integrity sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA==
dependencies:
+ "@jest/types" "^24.8.0"
ansi-styles "^3.2.0"
- jest-get-type "^24.0.0"
- jest-matcher-utils "^24.0.0"
- jest-message-util "^24.0.0"
- jest-regex-util "^24.0.0"
+ jest-get-type "^24.8.0"
+ jest-matcher-utils "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-regex-util "^24.3.0"
exports-loader@^0.7.0:
version "0.7.0"
@@ -4666,9 +4792,9 @@ fs-access@^1.0.0:
null-check "^1.0.0"
fs-minipass@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
- integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
+ integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==
dependencies:
minipass "^2.2.1"
@@ -4687,7 +4813,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-fsevents@^1.2.2, fsevents@^1.2.3:
+fsevents@^1.2.2, fsevents@^1.2.7:
version "1.2.9"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==
@@ -4742,9 +4868,9 @@ gaze@^1.0.0:
globule "^1.0.0"
get-caller-file@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
- integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
+ integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
get-stdin@^4.0.1:
version "4.0.1"
@@ -4816,9 +4942,9 @@ glob-to-regexp@^0.3.0:
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1:
- version "7.1.3"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
- integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
+ version "7.1.4"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
+ integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@@ -4886,16 +5012,11 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
-globals@^11.1.0:
+globals@^11.1.0, globals@^11.7.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
-globals@^11.7.0:
- version "11.7.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673"
- integrity sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==
-
globby@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
@@ -5042,12 +5163,12 @@ handle-thing@^2.0.0:
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754"
integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
-handlebars@^4.0.1, handlebars@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a"
- integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==
+handlebars@^4.0.1, handlebars@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
+ integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
dependencies:
- async "^2.5.0"
+ neo-async "^2.6.0"
optimist "^0.6.1"
source-map "^0.6.1"
optionalDependencies:
@@ -5610,7 +5731,7 @@ is-buffer@^2.0.0:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
-is-callable@^1.1.1, is-callable@^1.1.3:
+is-callable@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==
@@ -5713,9 +5834,9 @@ is-fullwidth-code-point@^2.0.0:
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-generator-fn@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.0.0.tgz#038c31b774709641bda678b1f06a4e3227c10b3e"
- integrity sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g==
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
+ integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
is-glob@^3.1.0:
version "3.1.0"
@@ -5761,11 +5882,6 @@ is-number@^3.0.0:
dependencies:
kind-of "^3.0.2"
-is-number@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
- integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
-
is-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
@@ -5776,13 +5892,6 @@ is-object@^1.0.1:
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA=
-is-odd@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24"
- integrity sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==
- dependencies:
- is-number "^4.0.0"
-
is-path-cwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
@@ -5856,7 +5965,7 @@ is-supported-regexp-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz#21ee16518d2c1dd3edd3e9a0d57e50207ac364ca"
integrity sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==
-is-symbol@^1.0.1:
+is-symbol@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==
@@ -5930,7 +6039,7 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
-istanbul-api@^2.0.5, istanbul-api@^2.0.8:
+istanbul-api@^2.0.5:
version "2.1.1"
resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.1.tgz#194b773f6d9cbc99a9258446848b0f988951c4d0"
integrity sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==
@@ -5949,57 +6058,57 @@ istanbul-api@^2.0.5, istanbul-api@^2.0.8:
minimatch "^3.0.4"
once "^1.4.0"
-istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#0b891e5ad42312c2b9488554f603795f9a2211ba"
- integrity sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==
+istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3, istanbul-lib-coverage@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
+ integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==
istanbul-lib-hook@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz#e0e581e461c611be5d0e5ef31c5f0109759916fb"
- integrity sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133"
+ integrity sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==
dependencies:
append-transform "^1.0.0"
istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz#a2b5484a7d445f1f311e93190813fa56dfb62971"
- integrity sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==
- dependencies:
- "@babel/generator" "^7.0.0"
- "@babel/parser" "^7.0.0"
- "@babel/template" "^7.0.0"
- "@babel/traverse" "^7.0.0"
- "@babel/types" "^7.0.0"
- istanbul-lib-coverage "^2.0.3"
- semver "^5.5.0"
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630"
+ integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==
+ dependencies:
+ "@babel/generator" "^7.4.0"
+ "@babel/parser" "^7.4.3"
+ "@babel/template" "^7.4.0"
+ "@babel/traverse" "^7.4.3"
+ "@babel/types" "^7.4.0"
+ istanbul-lib-coverage "^2.0.5"
+ semver "^6.0.0"
istanbul-lib-report@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz#bfd324ee0c04f59119cb4f07dab157d09f24d7e4"
- integrity sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33"
+ integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==
dependencies:
- istanbul-lib-coverage "^2.0.3"
- make-dir "^1.3.0"
- supports-color "^6.0.0"
+ istanbul-lib-coverage "^2.0.5"
+ make-dir "^2.1.0"
+ supports-color "^6.1.0"
istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz#f1e817229a9146e8424a28e5d69ba220fda34156"
- integrity sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8"
+ integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==
dependencies:
debug "^4.1.1"
- istanbul-lib-coverage "^2.0.3"
- make-dir "^1.3.0"
- rimraf "^2.6.2"
+ istanbul-lib-coverage "^2.0.5"
+ make-dir "^2.1.0"
+ rimraf "^2.6.3"
source-map "^0.6.1"
istanbul-reports@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.1.1.tgz#72ef16b4ecb9a4a7bd0e2001e00f95d1eec8afa9"
- integrity sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af"
+ integrity sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==
dependencies:
- handlebars "^4.1.0"
+ handlebars "^4.1.2"
istanbul@^0.4.5:
version "0.4.5"
@@ -6065,158 +6174,152 @@ jed@^1.1.1:
resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4"
integrity sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=
-jest-changed-files@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.0.0.tgz#c02c09a8cc9ca93f513166bc773741bd39898ff7"
- integrity sha512-nnuU510R9U+UX0WNb5XFEcsrMqriSiRLeO9KWDFgPrpToaQm60prfQYpxsXigdClpvNot5bekDY440x9dNGnsQ==
+jest-changed-files@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.8.0.tgz#7e7eb21cf687587a85e50f3d249d1327e15b157b"
+ integrity sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug==
dependencies:
+ "@jest/types" "^24.8.0"
execa "^1.0.0"
throat "^4.0.0"
-jest-cli@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.1.0.tgz#f7cc98995f36e7210cce3cbb12974cbf60940843"
- integrity sha512-U/iyWPwOI0T1CIxVLtk/2uviOTJ/OiSWJSe8qt6X1VkbbgP+nrtLJlmT9lPBe4lK78VNFJtrJ7pttcNv/s7yCw==
+jest-cli@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.8.0.tgz#b075ac914492ed114fa338ade7362a301693e989"
+ integrity sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA==
dependencies:
- ansi-escapes "^3.0.0"
+ "@jest/core" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
chalk "^2.0.1"
exit "^0.1.2"
- glob "^7.1.2"
- graceful-fs "^4.1.15"
import-local "^2.0.0"
is-ci "^2.0.0"
- istanbul-api "^2.0.8"
- istanbul-lib-coverage "^2.0.2"
- istanbul-lib-instrument "^3.0.1"
- istanbul-lib-source-maps "^3.0.1"
- jest-changed-files "^24.0.0"
- jest-config "^24.1.0"
- jest-environment-jsdom "^24.0.0"
- jest-get-type "^24.0.0"
- jest-haste-map "^24.0.0"
- jest-message-util "^24.0.0"
- jest-regex-util "^24.0.0"
- jest-resolve-dependencies "^24.1.0"
- jest-runner "^24.1.0"
- jest-runtime "^24.1.0"
- jest-snapshot "^24.1.0"
- jest-util "^24.0.0"
- jest-validate "^24.0.0"
- jest-watcher "^24.0.0"
- jest-worker "^24.0.0"
- micromatch "^3.1.10"
- node-notifier "^5.2.1"
- p-each-series "^1.0.0"
- pirates "^4.0.0"
+ jest-config "^24.8.0"
+ jest-util "^24.8.0"
+ jest-validate "^24.8.0"
prompts "^2.0.1"
- realpath-native "^1.0.0"
- rimraf "^2.5.4"
- slash "^2.0.0"
- string-length "^2.0.0"
- strip-ansi "^5.0.0"
- which "^1.2.12"
+ realpath-native "^1.1.0"
yargs "^12.0.2"
-jest-config@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.1.0.tgz#6ea6881cfdd299bc86cc144ee36d937c97c3850c"
- integrity sha512-FbbRzRqtFC6eGjG5VwsbW4E5dW3zqJKLWYiZWhB0/4E5fgsMw8GODLbGSrY5t17kKOtCWb/Z7nsIThRoDpuVyg==
+jest-config@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.8.0.tgz#77db3d265a6f726294687cbbccc36f8a76ee0f4f"
+ integrity sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw==
dependencies:
"@babel/core" "^7.1.0"
- babel-jest "^24.1.0"
+ "@jest/test-sequencer" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ babel-jest "^24.8.0"
chalk "^2.0.1"
glob "^7.1.1"
- jest-environment-jsdom "^24.0.0"
- jest-environment-node "^24.0.0"
- jest-get-type "^24.0.0"
- jest-jasmine2 "^24.1.0"
- jest-regex-util "^24.0.0"
- jest-resolve "^24.1.0"
- jest-util "^24.0.0"
- jest-validate "^24.0.0"
+ jest-environment-jsdom "^24.8.0"
+ jest-environment-node "^24.8.0"
+ jest-get-type "^24.8.0"
+ jest-jasmine2 "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-resolve "^24.8.0"
+ jest-util "^24.8.0"
+ jest-validate "^24.8.0"
micromatch "^3.1.10"
- pretty-format "^24.0.0"
- realpath-native "^1.0.2"
+ pretty-format "^24.8.0"
+ realpath-native "^1.1.0"
-jest-diff@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.0.0.tgz#a3e5f573dbac482f7d9513ac9cfa21644d3d6b34"
- integrity sha512-XY5wMpRaTsuMoU+1/B2zQSKQ9RdE9gsLkGydx3nvApeyPijLA8GtEvIcPwISRCer+VDf9W1mStTYYq6fPt8ryA==
+jest-diff@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172"
+ integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==
dependencies:
chalk "^2.0.1"
- diff-sequences "^24.0.0"
- jest-get-type "^24.0.0"
- pretty-format "^24.0.0"
+ diff-sequences "^24.3.0"
+ jest-get-type "^24.8.0"
+ pretty-format "^24.8.0"
-jest-docblock@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.0.0.tgz#54d77a188743e37f62181a91a01eb9222289f94e"
- integrity sha512-KfAKZ4SN7CFOZpWg4i7g7MSlY0M+mq7K0aMqENaG2vHuhC9fc3vkpU/iNN9sOus7v3h3Y48uEjqz3+Gdn2iptA==
+jest-docblock@^24.3.0:
+ version "24.3.0"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.3.0.tgz#b9c32dac70f72e4464520d2ba4aec02ab14db5dd"
+ integrity sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==
dependencies:
detect-newline "^2.1.0"
-jest-each@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.0.0.tgz#10987a06b21c7ffbfb7706c89d24c52ed864be55"
- integrity sha512-gFcbY4Cu55yxExXMkjrnLXov3bWO3dbPAW7HXb31h/DNWdNc/6X8MtxGff8nh3/MjkF9DpVqnj0KsPKuPK0cpA==
+jest-each@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.8.0.tgz#a05fd2bf94ddc0b1da66c6d13ec2457f35e52775"
+ integrity sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA==
dependencies:
+ "@jest/types" "^24.8.0"
chalk "^2.0.1"
- jest-get-type "^24.0.0"
- jest-util "^24.0.0"
- pretty-format "^24.0.0"
-
-jest-environment-jsdom@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.0.0.tgz#5affa0654d6e44cd798003daa1a8701dbd6e4d11"
- integrity sha512-1YNp7xtxajTRaxbylDc2pWvFnfDTH5BJJGyVzyGAKNt/lEULohwEV9zFqTgG4bXRcq7xzdd+sGFws+LxThXXOw==
- dependencies:
- jest-mock "^24.0.0"
- jest-util "^24.0.0"
+ jest-get-type "^24.8.0"
+ jest-util "^24.8.0"
+ pretty-format "^24.8.0"
+
+jest-environment-jsdom@^24.0.0, jest-environment-jsdom@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857"
+ integrity sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ==
+ dependencies:
+ "@jest/environment" "^24.8.0"
+ "@jest/fake-timers" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ jest-mock "^24.8.0"
+ jest-util "^24.8.0"
jsdom "^11.5.1"
-jest-environment-node@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.0.0.tgz#330948980656ed8773ce2e04eb597ed91e3c7190"
- integrity sha512-62fOFcaEdU0VLaq8JL90TqwI7hLn0cOKOl8vY2n477vRkCJRojiRRtJVRzzCcgFvs6gqU97DNqX5R0BrBP6Rxg==
+jest-environment-node@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.8.0.tgz#d3f726ba8bc53087a60e7a84ca08883a4c892231"
+ integrity sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q==
dependencies:
- jest-mock "^24.0.0"
- jest-util "^24.0.0"
+ "@jest/environment" "^24.8.0"
+ "@jest/fake-timers" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ jest-mock "^24.8.0"
+ jest-util "^24.8.0"
-jest-get-type@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.0.0.tgz#36e72930b78e33da59a4f63d44d332188278940b"
- integrity sha512-z6/Eyf6s9ZDGz7eOvl+fzpuJmN9i0KyTt1no37/dHu8galssxz5ZEgnc1KaV8R31q1khxyhB4ui/X5ZjjPk77w==
+jest-get-type@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc"
+ integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==
-jest-haste-map@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.0.0.tgz#e9ef51b2c9257384b4d6beb83bd48c65b37b5e6e"
- integrity sha512-CcViJyUo41IQqttLxXVdI41YErkzBKbE6cS6dRAploCeutePYfUimWd3C9rQEWhX0YBOQzvNsC0O9nYxK2nnxQ==
+jest-haste-map@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.0.tgz#51794182d877b3ddfd6e6d23920e3fe72f305800"
+ integrity sha512-ZBPRGHdPt1rHajWelXdqygIDpJx8u3xOoLyUBWRW28r3tagrgoepPrzAozW7kW9HrQfhvmiv1tncsxqHJO1onQ==
dependencies:
+ "@jest/types" "^24.8.0"
+ anymatch "^2.0.0"
fb-watchman "^2.0.0"
graceful-fs "^4.1.15"
invariant "^2.2.4"
- jest-serializer "^24.0.0"
- jest-util "^24.0.0"
- jest-worker "^24.0.0"
+ jest-serializer "^24.4.0"
+ jest-util "^24.8.0"
+ jest-worker "^24.6.0"
micromatch "^3.1.10"
- sane "^3.0.0"
+ sane "^4.0.3"
+ walker "^1.0.7"
+ optionalDependencies:
+ fsevents "^1.2.7"
-jest-jasmine2@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.1.0.tgz#8377324b967037c440f0a549ee0bbd9912055db6"
- integrity sha512-H+o76SdSNyCh9fM5K8upK45YTo/DiFx5w2YAzblQebSQmukDcoVBVeXynyr7DDnxh+0NTHYRCLwJVf3tC518wg==
+jest-jasmine2@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz#a9c7e14c83dd77d8b15e820549ce8987cc8cd898"
+ integrity sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong==
dependencies:
"@babel/traverse" "^7.1.0"
+ "@jest/environment" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
chalk "^2.0.1"
co "^4.6.0"
- expect "^24.1.0"
+ expect "^24.8.0"
is-generator-fn "^2.0.0"
- jest-each "^24.0.0"
- jest-matcher-utils "^24.0.0"
- jest-message-util "^24.0.0"
- jest-snapshot "^24.1.0"
- jest-util "^24.0.0"
- pretty-format "^24.0.0"
+ jest-each "^24.8.0"
+ jest-matcher-utils "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-runtime "^24.8.0"
+ jest-snapshot "^24.8.0"
+ jest-util "^24.8.0"
+ pretty-format "^24.8.0"
throat "^4.0.0"
jest-junit@^6.3.0:
@@ -6229,128 +6332,149 @@ jest-junit@^6.3.0:
strip-ansi "^4.0.0"
xml "^1.0.1"
-jest-leak-detector@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.0.0.tgz#78280119fd05ee98317daee62cddb3aa537a31c6"
- integrity sha512-ZYHJYFeibxfsDSKowjDP332pStuiFT2xfc5R67Rjm/l+HFJWJgNIOCOlQGeXLCtyUn3A23+VVDdiCcnB6dTTrg==
+jest-leak-detector@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz#c0086384e1f650c2d8348095df769f29b48e6980"
+ integrity sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g==
dependencies:
- pretty-format "^24.0.0"
+ pretty-format "^24.8.0"
-jest-matcher-utils@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.0.0.tgz#fc9c41cfc49b2c3ec14e576f53d519c37729d579"
- integrity sha512-LQTDmO+aWRz1Tf9HJg+HlPHhDh1E1c65kVwRFo5mwCVp5aQDzlkz4+vCvXhOKFjitV2f0kMdHxnODrXVoi+rlA==
+jest-matcher-utils@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495"
+ integrity sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw==
dependencies:
chalk "^2.0.1"
- jest-diff "^24.0.0"
- jest-get-type "^24.0.0"
- pretty-format "^24.0.0"
+ jest-diff "^24.8.0"
+ jest-get-type "^24.8.0"
+ pretty-format "^24.8.0"
-jest-message-util@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.0.0.tgz#a07a141433b2c992dbaec68d4cbfe470ba289619"
- integrity sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==
+jest-message-util@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.8.0.tgz#0d6891e72a4beacc0292b638685df42e28d6218b"
+ integrity sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g==
dependencies:
"@babel/code-frame" "^7.0.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ "@types/stack-utils" "^1.0.1"
chalk "^2.0.1"
micromatch "^3.1.10"
slash "^2.0.0"
stack-utils "^1.0.1"
-jest-mock@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.0.0.tgz#9a4b53e01d66a0e780f7d857462d063e024c617d"
- integrity sha512-sQp0Hu5fcf5NZEh1U9eIW2qD0BwJZjb63Yqd98PQJFvf/zzUTBoUAwv/Dc/HFeNHIw1f3hl/48vNn+j3STaI7A==
+jest-mock@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56"
+ integrity sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A==
+ dependencies:
+ "@jest/types" "^24.8.0"
-jest-regex-util@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.0.0.tgz#4feee8ec4a358f5bee0a654e94eb26163cb9089a"
- integrity sha512-Jv/uOTCuC+PY7WpJl2mpoI+WbY2ut73qwwO9ByJJNwOCwr1qWhEW2Lyi2S9ZewUdJqeVpEBisdEVZSI+Zxo58Q==
+jest-pnp-resolver@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a"
+ integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==
+
+jest-regex-util@^24.3.0:
+ version "24.3.0"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36"
+ integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==
-jest-resolve-dependencies@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.1.0.tgz#78f738a2ec59ff4d00751d9da56f176e3f589f6c"
- integrity sha512-2VwPsjd3kRPu7qe2cpytAgowCObk5AKeizfXuuiwgm1a9sijJDZe8Kh1sFj6FKvSaNEfCPlBVkZEJa2482m/Uw==
+jest-resolve-dependencies@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz#19eec3241f2045d3f990dba331d0d7526acff8e0"
+ integrity sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw==
dependencies:
- jest-regex-util "^24.0.0"
- jest-snapshot "^24.1.0"
+ "@jest/types" "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-snapshot "^24.8.0"
-jest-resolve@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.1.0.tgz#42ff0169b0ea47bfdbd0c52a0067ca7d022c7688"
- integrity sha512-TPiAIVp3TG6zAxH28u/6eogbwrvZjBMWroSLBDkwkHKrqxB/RIdwkWDye4uqPlZIXWIaHtifY3L0/eO5Z0f2wg==
+jest-resolve@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.8.0.tgz#84b8e5408c1f6a11539793e2b5feb1b6e722439f"
+ integrity sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==
dependencies:
+ "@jest/types" "^24.8.0"
browser-resolve "^1.11.3"
chalk "^2.0.1"
- realpath-native "^1.0.0"
-
-jest-runner@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.1.0.tgz#3686a2bb89ce62800da23d7fdc3da2c32792943b"
- integrity sha512-CDGOkT3AIFl16BLL/OdbtYgYvbAprwJ+ExKuLZmGSCSldwsuU2dEGauqkpvd9nphVdAnJUcP12e/EIlnTX0QXg==
- dependencies:
+ jest-pnp-resolver "^1.2.1"
+ realpath-native "^1.1.0"
+
+jest-runner@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.8.0.tgz#4f9ae07b767db27b740d7deffad0cf67ccb4c5bb"
+ integrity sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow==
+ dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/environment" "^24.8.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
chalk "^2.4.2"
exit "^0.1.2"
graceful-fs "^4.1.15"
- jest-config "^24.1.0"
- jest-docblock "^24.0.0"
- jest-haste-map "^24.0.0"
- jest-jasmine2 "^24.1.0"
- jest-leak-detector "^24.0.0"
- jest-message-util "^24.0.0"
- jest-runtime "^24.1.0"
- jest-util "^24.0.0"
- jest-worker "^24.0.0"
+ jest-config "^24.8.0"
+ jest-docblock "^24.3.0"
+ jest-haste-map "^24.8.0"
+ jest-jasmine2 "^24.8.0"
+ jest-leak-detector "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-resolve "^24.8.0"
+ jest-runtime "^24.8.0"
+ jest-util "^24.8.0"
+ jest-worker "^24.6.0"
source-map-support "^0.5.6"
throat "^4.0.0"
-jest-runtime@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.1.0.tgz#7c157a2e776609e8cf552f956a5a19ec9c985214"
- integrity sha512-59/BY6OCuTXxGeDhEMU7+N33dpMQyXq7MLK07cNSIY/QYt2QZgJ7Tjx+rykBI0skAoigFl0A5tmT8UdwX92YuQ==
- dependencies:
- "@babel/core" "^7.1.0"
- babel-plugin-istanbul "^5.1.0"
+jest-runtime@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.8.0.tgz#05f94d5b05c21f6dc54e427cd2e4980923350620"
+ integrity sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA==
+ dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/environment" "^24.8.0"
+ "@jest/source-map" "^24.3.0"
+ "@jest/transform" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ "@types/yargs" "^12.0.2"
chalk "^2.0.1"
- convert-source-map "^1.4.0"
exit "^0.1.2"
- fast-json-stable-stringify "^2.0.0"
glob "^7.1.3"
graceful-fs "^4.1.15"
- jest-config "^24.1.0"
- jest-haste-map "^24.0.0"
- jest-message-util "^24.0.0"
- jest-regex-util "^24.0.0"
- jest-resolve "^24.1.0"
- jest-snapshot "^24.1.0"
- jest-util "^24.0.0"
- jest-validate "^24.0.0"
- micromatch "^3.1.10"
- realpath-native "^1.0.0"
+ jest-config "^24.8.0"
+ jest-haste-map "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-mock "^24.8.0"
+ jest-regex-util "^24.3.0"
+ jest-resolve "^24.8.0"
+ jest-snapshot "^24.8.0"
+ jest-util "^24.8.0"
+ jest-validate "^24.8.0"
+ realpath-native "^1.1.0"
slash "^2.0.0"
strip-bom "^3.0.0"
- write-file-atomic "2.4.1"
yargs "^12.0.2"
-jest-serializer@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.0.0.tgz#522c44a332cdd194d8c0531eb06a1ee5afb4256b"
- integrity sha512-9FKxQyrFgHtx3ozU+1a8v938ILBE7S8Ko3uiAVjT8Yfi2o91j/fj81jacCQZ/Ihjiff/VsUCXVgQ+iF1XdImOw==
+jest-serializer@^24.4.0:
+ version "24.4.0"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3"
+ integrity sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==
-jest-snapshot@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.1.0.tgz#85e22f810357aa5994ab61f236617dc2205f2f5b"
- integrity sha512-th6TDfFqEmXvuViacU1ikD7xFb7lQsPn2rJl7OEmnfIVpnrx3QNY2t3PE88meeg0u/mQ0nkyvmC05PBqO4USFA==
+jest-snapshot@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.8.0.tgz#3bec6a59da2ff7bc7d097a853fb67f9d415cb7c6"
+ integrity sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg==
dependencies:
"@babel/types" "^7.0.0"
+ "@jest/types" "^24.8.0"
chalk "^2.0.1"
- jest-diff "^24.0.0"
- jest-matcher-utils "^24.0.0"
- jest-message-util "^24.0.0"
- jest-resolve "^24.1.0"
+ expect "^24.8.0"
+ jest-diff "^24.8.0"
+ jest-matcher-utils "^24.8.0"
+ jest-message-util "^24.8.0"
+ jest-resolve "^24.8.0"
mkdirp "^0.5.1"
natural-compare "^1.4.0"
- pretty-format "^24.0.0"
+ pretty-format "^24.8.0"
semver "^5.5.0"
jest-transform-graphql@^2.1.0:
@@ -6358,56 +6482,64 @@ jest-transform-graphql@^2.1.0:
resolved "https://registry.yarnpkg.com/jest-transform-graphql/-/jest-transform-graphql-2.1.0.tgz#903cb66bb27bc2772fd3e5dd4f7e9b57230f5829"
integrity sha1-kDy2a7J7wncv0+XdT36bVyMPWCk=
-jest-util@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.0.0.tgz#fd38fcafd6dedbd0af2944d7a227c0d91b68f7d6"
- integrity sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==
+jest-util@^24.0.0, jest-util@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1"
+ integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA==
dependencies:
+ "@jest/console" "^24.7.1"
+ "@jest/fake-timers" "^24.8.0"
+ "@jest/source-map" "^24.3.0"
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
callsites "^3.0.0"
chalk "^2.0.1"
graceful-fs "^4.1.15"
is-ci "^2.0.0"
- jest-message-util "^24.0.0"
mkdirp "^0.5.1"
slash "^2.0.0"
source-map "^0.6.0"
-jest-validate@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.0.0.tgz#aa8571a46983a6538328fef20406b4a496b6c020"
- integrity sha512-vMrKrTOP4BBFIeOWsjpsDgVXATxCspC9S1gqvbJ3Tnn/b9ACsJmteYeVx9830UMV28Cob1RX55x96Qq3Tfad4g==
+jest-validate@^24.0.0, jest-validate@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.8.0.tgz#624c41533e6dfe356ffadc6e2423a35c2d3b4849"
+ integrity sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA==
dependencies:
+ "@jest/types" "^24.8.0"
camelcase "^5.0.0"
chalk "^2.0.1"
- jest-get-type "^24.0.0"
+ jest-get-type "^24.8.0"
leven "^2.1.0"
- pretty-format "^24.0.0"
+ pretty-format "^24.8.0"
-jest-watcher@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.0.0.tgz#20d44244d10b0b7312410aefd256c1c1eef68890"
- integrity sha512-GxkW2QrZ4YxmW1GUWER05McjVDunBlKMFfExu+VsGmXJmpej1saTEKvONdx5RJBlVdpPI5x6E3+EDQSIGgl53g==
+jest-watcher@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.8.0.tgz#58d49915ceddd2de85e238f6213cef1c93715de4"
+ integrity sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw==
dependencies:
+ "@jest/test-result" "^24.8.0"
+ "@jest/types" "^24.8.0"
+ "@types/yargs" "^12.0.9"
ansi-escapes "^3.0.0"
chalk "^2.0.1"
- jest-util "^24.0.0"
+ jest-util "^24.8.0"
string-length "^2.0.0"
-jest-worker@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.0.0.tgz#3d3483b077bf04f412f47654a27bba7e947f8b6d"
- integrity sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg==
+jest-worker@^24.6.0:
+ version "24.6.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.6.0.tgz#7f81ceae34b7cde0c9827a6980c35b7cdc0161b3"
+ integrity sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==
dependencies:
merge-stream "^1.0.1"
supports-color "^6.1.0"
jest@^24.1.0:
- version "24.1.0"
- resolved "https://registry.yarnpkg.com/jest/-/jest-24.1.0.tgz#b1e1135caefcf2397950ecf7f90e395fde866fd2"
- integrity sha512-+q91L65kypqklvlRFfXfdzUKyngQLOcwGhXQaLmVHv+d09LkNXuBuGxlofTFW42XMzu3giIcChchTsCNUjQ78A==
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-24.8.0.tgz#d5dff1984d0d1002196e9b7f12f75af1b2809081"
+ integrity sha512-o0HM90RKFRNWmAWvlyV8i5jGZ97pFwkeVoGvPW1EtLTgJc2+jcuqcbbqcSZLE/3f2S5pt0y2ZBETuhpWNl1Reg==
dependencies:
import-local "^2.0.0"
- jest-cli "^24.1.0"
+ jest-cli "^24.8.0"
jquery-ujs@1.2.2:
version "1.2.2"
@@ -6468,9 +6600,9 @@ js-tokens@^3.0.2:
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.9.0:
- version "3.12.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
- integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+ integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
@@ -6765,9 +6897,9 @@ klaw@~2.0.0:
graceful-fs "^4.1.9"
kleur@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.2.tgz#83c7ec858a41098b613d5998a7b653962b504f68"
- integrity sha512-3h7B2WRT5LNXOtQiAaWonilegHcPSf9nLVXlSTci8lu1dZUuui61+EsPEZqSVxY7rXYmB2DVKMQILxaO5WL61Q==
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
+ integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
known-css-properties@^0.11.0:
version "0.11.0"
@@ -6781,13 +6913,6 @@ latest-version@^3.0.0:
dependencies:
package-json "^4.0.0"
-lazy-cache@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
- integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=
- dependencies:
- set-getter "^0.1.0"
-
lcid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
@@ -7053,6 +7178,14 @@ make-dir@^1.0.0, make-dir@^1.3.0:
dependencies:
pify "^3.0.0"
+make-dir@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
+ integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
+ dependencies:
+ pify "^4.0.1"
+ semver "^5.6.0"
+
make-error@1.x:
version "1.3.5"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
@@ -7066,9 +7199,9 @@ makeerror@1.0.x:
tmpl "1.0.x"
map-age-cleaner@^0.1.1:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz#098fb15538fd3dbe461f12745b0ca8568d4e3f74"
- integrity sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
+ integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
dependencies:
p-defer "^1.0.0"
@@ -7167,13 +7300,13 @@ mem@^1.1.0:
mimic-fn "^1.0.0"
mem@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/mem/-/mem-4.0.0.tgz#6437690d9471678f6cc83659c00cbafcd6b0cdaf"
- integrity sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
+ integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
dependencies:
map-age-cleaner "^0.1.1"
- mimic-fn "^1.0.0"
- p-is-promise "^1.1.0"
+ mimic-fn "^2.0.0"
+ p-is-promise "^2.0.0"
memory-fs@^0.2.0:
version "0.2.0"
@@ -7243,11 +7376,6 @@ merge2@^1.2.3:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5"
integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==
-merge@^1.2.0:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145"
- integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==
-
mermaid@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.0.0.tgz#8f6c75017e788a8c3997e20c5e5046c2b88d1a8f"
@@ -7317,9 +7445,14 @@ mime@^2.0.3, mime@^2.3.1:
integrity sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==
mimic-fn@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
- integrity sha1-5md4PZLonb00KBi1IwudYqZyrRg=
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
+ integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
+
+mimic-fn@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+ integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-response@^1.0.0:
version "1.0.0"
@@ -7353,7 +7486,7 @@ minimist-options@^3.0.1:
minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
- resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
minimist@1.1.x:
@@ -7366,18 +7499,18 @@ minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
-minipass@^2.2.1, minipass@^2.3.3:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233"
- integrity sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==
+minipass@^2.2.1, minipass@^2.3.4:
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
+ integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
dependencies:
safe-buffer "^5.1.2"
yallist "^3.0.0"
-minizlib@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
- integrity sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==
+minizlib@^1.1.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614"
+ integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==
dependencies:
minipass "^2.2.1"
@@ -7474,27 +7607,21 @@ mute-stream@0.0.7:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
-nan@^2.12.1:
+nan@^2.12.1, nan@^2.13.2:
version "2.14.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
-nan@^2.13.2:
- version "2.13.2"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
- integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==
-
nanomatch@^1.2.9:
- version "1.2.9"
- resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2"
- integrity sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==
+ version "1.2.13"
+ resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
+ integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==
dependencies:
arr-diff "^4.0.0"
array-unique "^0.3.2"
define-property "^2.0.2"
extend-shallow "^3.0.2"
fragment-cache "^0.2.1"
- is-odd "^2.0.0"
is-windows "^1.0.2"
kind-of "^6.0.2"
object.pick "^1.3.0"
@@ -7508,11 +7635,11 @@ natural-compare@^1.4.0:
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
needle@^2.2.1:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e"
- integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
+ integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
dependencies:
- debug "^2.1.2"
+ debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"
@@ -7527,9 +7654,9 @@ neo-async@^2.5.0, neo-async@^2.6.0:
integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
nice-try@^1.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
- integrity sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
+ integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-ensure@^0.0.0:
version "0.0.0"
@@ -7607,11 +7734,12 @@ node-modules-regexp@^1.0.0:
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
node-notifier@^5.2.1:
- version "5.3.0"
- resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.3.0.tgz#c77a4a7b84038733d5fb351aafd8a268bfe19a01"
- integrity sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q==
+ version "5.4.0"
+ resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.0.tgz#7b455fdce9f7de0c63538297354f3db468426e6a"
+ integrity sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==
dependencies:
growly "^1.3.0"
+ is-wsl "^1.1.0"
semver "^5.5.0"
shellwords "^0.1.1"
which "^1.3.0"
@@ -7639,13 +7767,6 @@ node-releases@^1.1.19:
dependencies:
semver "^5.3.0"
-node-releases@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.3.tgz#aad9ce0dcb98129c753f772c0aa01360fb90fbd2"
- integrity sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==
- dependencies:
- semver "^5.3.0"
-
node-sass@^4.12.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017"
@@ -7749,14 +7870,14 @@ normalize-url@2.0.1:
sort-keys "^2.0.0"
npm-bundled@^1.0.1:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308"
- integrity sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
+ integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
npm-packlist@^1.1.6:
- version "1.1.10"
- resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a"
- integrity sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc"
+ integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==
dependencies:
ignore-walk "^3.0.1"
npm-bundled "^1.0.1"
@@ -7823,9 +7944,9 @@ object-copy@^0.1.0:
kind-of "^3.0.3"
object-keys@^1.0.11, object-keys@^1.0.12:
- version "1.0.12"
- resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
- integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object-visit@^1.0.0:
version "1.0.1"
@@ -7998,11 +8119,11 @@ os-locale@^2.0.0:
mem "^1.1.0"
os-locale@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.0.1.tgz#3b014fbf01d87f60a1e5348d80fe870dc82c4620"
- integrity sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
+ integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
dependencies:
- execa "^0.10.0"
+ execa "^1.0.0"
lcid "^2.0.0"
mem "^4.0.0"
@@ -8046,6 +8167,11 @@ p-is-promise@^1.1.0:
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=
+p-is-promise@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
+ integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
+
p-limit@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
@@ -8054,9 +8180,9 @@ p-limit@^1.1.0:
p-try "^1.0.0"
p-limit@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec"
- integrity sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
+ integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
dependencies:
p-try "^2.0.0"
@@ -8097,9 +8223,9 @@ p-try@^1.0.0:
integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
p-try@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
- integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+ integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
package-json@^4.0.0:
version "4.0.1"
@@ -8330,7 +8456,7 @@ pinkie@^2.0.0:
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
-pirates@^4.0.0:
+pirates@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==
@@ -8570,13 +8696,15 @@ prettier@1.16.4:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
-pretty-format@^24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.0.0.tgz#cb6599fd73ac088e37ed682f61291e4678f48591"
- integrity sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==
+pretty-format@^24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2"
+ integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==
dependencies:
+ "@jest/types" "^24.8.0"
ansi-regex "^4.0.0"
ansi-styles "^3.2.0"
+ react-is "^16.8.4"
prismjs@^1.6.0:
version "1.6.0"
@@ -8616,9 +8744,9 @@ promise-inflight@^1.0.1:
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
prompts@^2.0.1:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.3.tgz#c5ccb324010b2e8f74752aadceeb57134c1d2522"
- integrity sha512-H8oWEoRZpybm6NV4to9/1limhttEo13xK62pNvn2JzY0MA03p7s0OjtmhXyon3uJmxiJJVSuUwEJFFssI3eBiQ==
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.1.0.tgz#bf90bc71f6065d255ea2bdc0fe6520485c1b45db"
+ integrity sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==
dependencies:
kleur "^3.0.2"
sisteransi "^1.0.0"
@@ -8927,6 +9055,11 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
+react-is@^16.8.4:
+ version "16.8.6"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
+ integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
+
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@@ -9030,7 +9163,7 @@ readdirp@^2.0.0:
readable-stream "^2.0.2"
set-immediate-shim "^1.0.1"
-realpath-native@^1.0.0, realpath-native@^1.0.2:
+realpath-native@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==
@@ -9220,9 +9353,9 @@ remove-trailing-separator@^1.0.1:
integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
repeat-element@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a"
- integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
+ integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
repeat-string@^0.2.2:
version "0.2.2"
@@ -9358,14 +9491,7 @@ resolve@1.1.7, resolve@1.1.x:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
-resolve@1.x, resolve@^1.10.0, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.9.0:
- version "1.10.0"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
- integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
- dependencies:
- path-parse "^1.0.6"
-
-resolve@^1.3.2:
+resolve@1.x, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.9.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232"
integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==
@@ -9397,7 +9523,7 @@ rfdc@^1.1.2:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==
-rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
+rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -9417,10 +9543,10 @@ rope-sequence@^1.2.0:
resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.2.2.tgz#49c4e5c2f54a48e990b050926771e2871bcb31ce"
integrity sha1-ScTlwvVKSOmQsFCSZ3HihxvLMc4=
-rsvp@^3.3.3:
- version "3.6.2"
- resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"
- integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==
+rsvp@^4.8.4:
+ version "4.8.4"
+ resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.4.tgz#b50e6b34583f3dd89329a2f23a8a2be072845911"
+ integrity sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==
run-async@^2.2.0:
version "2.3.0"
@@ -9475,22 +9601,20 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sane@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/sane/-/sane-3.1.0.tgz#995193b7dc1445ef1fe41ddfca2faf9f111854c6"
- integrity sha512-G5GClRRxT1cELXfdAq7UKtUsv8q/ZC5k8lQGmjEm4HcAl3HzBy68iglyNCmw4+0tiXPCBZntslHlRhbnsSws+Q==
+sane@^4.0.3:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded"
+ integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==
dependencies:
+ "@cnakazawa/watch" "^1.0.3"
anymatch "^2.0.0"
- capture-exit "^1.2.0"
- exec-sh "^0.2.0"
+ capture-exit "^2.0.0"
+ exec-sh "^0.3.2"
execa "^1.0.0"
fb-watchman "^2.0.0"
micromatch "^3.1.4"
minimist "^1.1.1"
walker "~1.0.5"
- watch "~0.18.0"
- optionalDependencies:
- fsevents "^1.2.3"
sanitize-html@^1.16.1:
version "1.16.3"
@@ -9583,12 +9707,7 @@ semver-diff@^2.0.0:
dependencies:
semver "^5.0.3"
-"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
- version "5.6.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
- integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
-
-semver@^5.4.1:
+"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0:
version "5.7.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
@@ -9655,13 +9774,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
-set-getter@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376"
- integrity sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=
- dependencies:
- to-object-path "^0.3.0"
-
set-immediate-shim@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
@@ -9795,9 +9907,9 @@ snapdragon-util@^3.0.1:
kind-of "^3.2.0"
snapdragon@^0.8.1:
- version "0.8.1"
- resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370"
- integrity sha1-4StUh/re0+PeoKyR6UAL91tAE3A=
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d"
+ integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==
dependencies:
base "^0.11.1"
debug "^2.2.0"
@@ -9806,7 +9918,7 @@ snapdragon@^0.8.1:
map-cache "^0.2.2"
source-map "^0.5.6"
source-map-resolve "^0.5.0"
- use "^2.0.0"
+ use "^3.1.0"
socket.io-adapter@~1.1.0:
version "1.1.1"
@@ -9903,9 +10015,9 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
urix "^0.1.0"
source-map-support@^0.5.6, source-map-support@~0.5.6:
- version "0.5.9"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f"
- integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==
+ version "0.5.12"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
+ integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
@@ -10145,7 +10257,7 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
-string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
+"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
@@ -10199,11 +10311,11 @@ strip-ansi@^4.0.0:
ansi-regex "^3.0.0"
strip-ansi@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f"
- integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+ integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
- ansi-regex "^4.0.0"
+ ansi-regex "^4.1.0"
strip-bom@^2.0.0:
version "2.0.0"
@@ -10359,7 +10471,7 @@ supports-color@^5.1.0, supports-color@^5.2.0, supports-color@^5.3.0, supports-co
dependencies:
has-flag "^3.0.0"
-supports-color@^6.0.0, supports-color@^6.1.0:
+supports-color@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
@@ -10421,14 +10533,14 @@ tar@^2.0.0:
inherits "2"
tar@^4:
- version "4.4.4"
- resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd"
- integrity sha512-mq9ixIYfNF9SK0IS/h2HKMu8Q2iaCuhDDsZhdEag/FHv8fOaYld4vN7ouMgcSSt5WKZzPs8atclTcJm36OTh4w==
+ version "4.4.8"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
+ integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==
dependencies:
- chownr "^1.0.1"
+ chownr "^1.1.1"
fs-minipass "^1.2.5"
- minipass "^2.3.3"
- minizlib "^1.1.0"
+ minipass "^2.3.4"
+ minizlib "^1.1.1"
mkdirp "^0.5.0"
safe-buffer "^5.1.2"
yallist "^3.0.2"
@@ -10804,11 +10916,11 @@ uc.micro@^1.0.1, uc.micro@^1.0.5:
integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==
uglify-js@^3.1.4:
- version "3.4.9"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
- integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==
+ version "3.5.15"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.15.tgz#fe2b5378fd0b09e116864041437bff889105ce24"
+ integrity sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==
dependencies:
- commander "~2.17.1"
+ commander "~2.20.0"
source-map "~0.6.1"
ultron@~1.1.0:
@@ -11064,14 +11176,10 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
-use@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8"
- integrity sha1-riig1y+TvyJCKhii43mZMRLeyOg=
- dependencies:
- define-property "^0.2.5"
- isobject "^3.0.0"
- lazy-cache "^2.0.2"
+use@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
+ integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
useragent@2.2.1:
version "2.2.1"
@@ -11268,7 +11376,7 @@ vue-virtual-scroll-list@^1.3.1:
resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.3.1.tgz#efcb83d3a3dcc69cd886fa4de1130a65493e8f76"
integrity sha512-PMTxiK9/P1LtgoWWw4n1QnmDDkYqIdWWCNdt1L4JD9g6rwDgnsGsSV10bAnd5n7DQLHGWHjRex+zAbjXWT8t0g==
-vue@^2.5.21, vue@^2.6.10:
+vue@^2.6.10:
version "2.6.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
@@ -11290,21 +11398,13 @@ w3c-keyname@^1.1.8:
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-1.1.8.tgz#4e2219663760fd6535b7a1550f1552d71fc9372c"
integrity sha512-2HAdug8GTiu3b4NYhssdtY8PXRue3ICnh1IlxvZYl+hiINRq0GfNWei3XOPDg8L0PsxbmYjWVLuLj6BMRR/9vA==
-walker@~1.0.5:
+walker@^1.0.7, walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=
dependencies:
makeerror "1.0.x"
-watch@~0.18.0:
- version "0.18.0"
- resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986"
- integrity sha1-KAlUdsbffJDJYxOJkMClQj60uYY=
- dependencies:
- exec-sh "^0.2.0"
- minimist "^1.2.0"
-
watchpack@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.5.0.tgz#231e783af830a22f8966f65c4c4bacc814072eed"
@@ -11512,7 +11612,7 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
-which@1, which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
+which@1, which@^1.1.1, which@^1.2.1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -11520,11 +11620,11 @@ which@1, which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9,
isexe "^2.0.0"
wide-align@^1.1.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
- integrity sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
+ integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
dependencies:
- string-width "^1.0.2"
+ string-width "^1.0.2 || 2"
widest-line@^2.0.0:
version "2.0.0"
@@ -11689,9 +11789,9 @@ yallist@^2.1.2:
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
yallist@^3.0.0, yallist@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
- integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
+ integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
yargs-parser@10.x, yargs-parser@^10.0.0, yargs-parser@^10.1.0:
version "10.1.0"