summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.babelrc.js1
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml93
-rw-r--r--.gitlab/issue_templates/Add style proposal.md16
-rw-r--r--.gitlab/issue_templates/Feature proposal.md31
-rw-r--r--.gitlab/issue_templates/Security Release.md69
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md2
-rw-r--r--.rubocop.yml1
-rw-r--r--.rubocop_todo.yml17
-rw-r--r--CHANGELOG.md211
-rw-r--r--CONTRIBUTING.md45
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile20
-rw-r--r--Gemfile.lock63
-rw-r--r--PROCESS.md13
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/badges/components/badge_list_row.vue4
-rw-r--r--app/assets/javascripts/behaviors/markdown/copy_as_gfm.js400
-rw-r--r--app/assets/javascripts/behaviors/markdown/editor_extensions.js106
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/bold.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/code.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/inline_diff.js41
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/inline_html.js46
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/italic.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/link.js21
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/math.js41
-rw-r--r--app/assets/javascripts/behaviors/markdown/marks/strike.js15
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/blockquote.js13
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/code_block.js79
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/description_details.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/description_list.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/description_term.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/details.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/doc.js15
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/emoji.js41
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/hard_break.js10
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/heading.js13
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/image.js52
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/list_item.js11
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js10
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/paragraph.js24
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/reference.js52
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/summary.js27
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table.js25
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_body.js24
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_cell.js35
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_head.js24
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js43
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js33
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/table_row.js38
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/task_list.js28
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js49
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/text.js20
-rw-r--r--app/assets/javascripts/behaviors/markdown/nodes/video.js54
-rw-r--r--app/assets/javascripts/behaviors/markdown/schema.js24
-rw-r--r--app/assets/javascripts/behaviors/markdown/serializer.js24
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js11
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js9
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js20
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue2
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.vue4
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue2
-rw-r--r--app/assets/javascripts/boards/models/list.js1
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js13
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue19
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js6
-rw-r--r--app/assets/javascripts/contextual_sidebar.js24
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue2
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue33
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue67
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions_dropdown.vue16
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussions.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_gutter_content.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue6
-rw-r--r--app/assets/javascripts/diffs/components/image_diff_overlay.vue4
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue1
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue1
-rw-r--r--app/assets/javascripts/diffs/components/settings_dropdown.vue92
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue81
-rw-r--r--app/assets/javascripts/diffs/constants.js4
-rw-r--r--app/assets/javascripts/diffs/index.js15
-rw-r--r--app/assets/javascripts/diffs/store/actions.js35
-rw-r--r--app/assets/javascripts/diffs/store/getters.js19
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js4
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js15
-rw-r--r--app/assets/javascripts/diffs/store/utils.js75
-rw-r--r--app/assets/javascripts/diffs/workers/tree_worker.js14
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_form.js7
-rw-r--r--app/assets/javascripts/dismissable_callout.js27
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.vue2
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue6
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue6
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js8
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue118
-rw-r--r--app/assets/javascripts/error_tracking/index.js31
-rw-r--r--app/assets/javascripts/error_tracking/services/index.js7
-rw-r--r--app/assets/javascripts/error_tracking/store/actions.js31
-rw-r--r--app/assets/javascripts/error_tracking/store/index.js19
-rw-r--r--app/assets/javascripts/error_tracking/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/error_tracking/store/mutations.js14
-rw-r--r--app/assets/javascripts/feature_highlight/feature_highlight.js4
-rw-r--r--app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue4
-rw-r--r--app/assets/javascripts/fly_out_nav.js5
-rw-r--r--app/assets/javascripts/frequent_items/components/app.vue6
-rw-r--r--app/assets/javascripts/frequent_items/index.js6
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js24
-rw-r--r--app/assets/javascripts/gl_form.js2
-rw-r--r--app/assets/javascripts/header.js6
-rw-r--r--app/assets/javascripts/ide/components/activity_bar.vue6
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/message_field.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue6
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue2
-rw-r--r--app/assets/javascripts/ide/components/editor_mode_dropdown.vue4
-rw-r--r--app/assets/javascripts/ide/components/file_finder/index.vue6
-rw-r--r--app/assets/javascripts/ide/components/file_templates/dropdown.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_tree.vue4
-rw-r--r--app/assets/javascripts/ide/components/jobs/detail.vue2
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue4
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue10
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue2
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue4
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue4
-rw-r--r--app/assets/javascripts/ide/components/resizable_panel.vue4
-rw-r--r--app/assets/javascripts/ide/components/shared/tokened_input.vue4
-rw-r--r--app/assets/javascripts/ide/ide_router.js2
-rw-r--r--app/assets/javascripts/ide/index.js9
-rw-r--r--app/assets/javascripts/ide/lib/common/model.js6
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue11
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue70
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue2
-rw-r--r--app/assets/javascripts/jobs/store/getters.js16
-rw-r--r--app/assets/javascripts/layout_nav.js65
-rw-r--r--app/assets/javascripts/lazy_loader.js1
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js26
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js164
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js162
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js23
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js55
-rw-r--r--app/assets/javascripts/locale/sprintf.js4
-rw-r--r--app/assets/javascripts/main.js92
-rw-r--r--app/assets/javascripts/merge_request_tabs.js2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue12
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue5
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue6
-rw-r--r--app/assets/javascripts/notebook/cells/code.vue15
-rw-r--r--app/assets/javascripts/notebook/cells/code/index.vue3
-rw-r--r--app/assets/javascripts/notebook/cells/output/html.vue15
-rw-r--r--app/assets/javascripts/notebook/cells/output/image.vue20
-rw-r--r--app/assets/javascripts/notebook/cells/output/index.vue104
-rw-r--r--app/assets/javascripts/notebook/cells/prompt.vue10
-rw-r--r--app/assets/javascripts/notebook/index.vue4
-rw-r--r--app/assets/javascripts/notes.js18
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue27
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue7
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue25
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue12
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue20
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue19
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue33
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js7
-rw-r--r--app/assets/javascripts/notes/stores/utils.js4
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/show/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/index.js6
-rw-r--r--app/assets/javascripts/pages/groups/clusters/edit/index.js (renamed from app/assets/javascripts/pages/groups/clusters/update/index.js)0
-rw-r--r--app/assets/javascripts/pages/groups/clusters/index/index.js6
-rw-r--r--app/assets/javascripts/pages/groups/index.js10
-rw-r--r--app/assets/javascripts/pages/projects/clusters/edit/index.js (renamed from app/assets/javascripts/pages/projects/clusters/update/index.js)0
-rw-r--r--app/assets/javascripts/pages/projects/clusters/index/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/error_tracking/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue8
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue4
-rw-r--r--app/assets/javascripts/pages/projects/tags/releases/index.js8
-rw-r--r--app/assets/javascripts/pages/sessions/new/index.js5
-rw-r--r--app/assets/javascripts/pages/sessions/new/oauth_remember_me.js5
-rw-r--r--app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js32
-rw-r--r--app/assets/javascripts/pages/users/user_overview_block.js5
-rw-r--r--app/assets/javascripts/persistent_user_callout.js34
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue45
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue34
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue2
-rw-r--r--app/assets/javascripts/pipelines/mixins/graph_component_mixin.js44
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue4
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue4
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue8
-rw-r--r--app/assets/javascripts/releases/components/app.vue6
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue6
-rw-r--r--app/assets/javascripts/reports/components/modal_open_name.vue2
-rw-r--r--app/assets/javascripts/reports/components/test_issue_body.vue2
-rw-r--r--app/assets/javascripts/serverless/components/function_details.vue73
-rw-r--r--app/assets/javascripts/serverless/components/function_row.vue25
-rw-r--r--app/assets/javascripts/serverless/components/functions.vue7
-rw-r--r--app/assets/javascripts/serverless/components/pod_box.vue36
-rw-r--r--app/assets/javascripts/serverless/serverless_bundle.js67
-rw-r--r--app/assets/javascripts/serverless/stores/serverless_details_store.js11
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue4
-rw-r--r--app/assets/javascripts/terminal/terminal.js3
-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_conflicts.vue70
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue29
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue34
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue2
-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.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/bar_chart.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/deprecated_modal.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue48
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row_header.vue25
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue34
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue69
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/navigation_tabs.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue89
-rw-r--r--app/assets/javascripts/vuex_shared/modules/modal/actions.js20
-rw-r--r--app/assets/javascripts/vuex_shared/modules/modal/index.js10
-rw-r--r--app/assets/javascripts/vuex_shared/modules/modal/mutation_types.js4
-rw-r--r--app/assets/javascripts/vuex_shared/modules/modal/mutations.js18
-rw-r--r--app/assets/javascripts/vuex_shared/modules/modal/state.js4
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss2
-rw-r--r--app/assets/stylesheets/components/related_items_list.scss381
-rw-r--r--app/assets/stylesheets/framework/animations.scss10
-rw-r--r--app/assets/stylesheets/framework/awards.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss2
-rw-r--r--app/assets/stylesheets/framework/common.scss21
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/framework/forms.scss11
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss40
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/framework/icons.scss4
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss4
-rw-r--r--app/assets/stylesheets/framework/modal.scss38
-rw-r--r--app/assets/stylesheets/framework/responsive_tables.scss2
-rw-r--r--app/assets/stylesheets/framework/selects.scss18
-rw-r--r--app/assets/stylesheets/framework/stacked_progress_bar.scss8
-rw-r--r--app/assets/stylesheets/framework/tables.scss9
-rw-r--r--app/assets/stylesheets/framework/typography.scss3
-rw-r--r--app/assets/stylesheets/framework/variables.scss45
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss13
-rw-r--r--app/assets/stylesheets/highlight/none.scss28
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss28
-rw-r--r--app/assets/stylesheets/pages/boards.scss23
-rw-r--r--app/assets/stylesheets/pages/builds.scss24
-rw-r--r--app/assets/stylesheets/pages/diff.scss21
-rw-r--r--app/assets/stylesheets/pages/editor.scss4
-rw-r--r--app/assets/stylesheets/pages/environments.scss12
-rw-r--r--app/assets/stylesheets/pages/graph.scss6
-rw-r--r--app/assets/stylesheets/pages/groups.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss2
-rw-r--r--app/assets/stylesheets/pages/issues.scss22
-rw-r--r--app/assets/stylesheets/pages/issues/issue_count_badge.scss6
-rw-r--r--app/assets/stylesheets/pages/labels.scss10
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss35
-rw-r--r--app/assets/stylesheets/pages/milestone.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss10
-rw-r--r--app/assets/stylesheets/pages/notes.scss8
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss112
-rw-r--r--app/assets/stylesheets/pages/profile.scss10
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss16
-rw-r--r--app/assets/stylesheets/pages/reports.scss2
-rw-r--r--app/assets/stylesheets/pages/settings.scss2
-rw-r--r--app/controllers/admin/requests_profiles_controller.rb2
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/concerns/boards_responses.rb10
-rw-r--r--app/controllers/concerns/uploads_actions.rb18
-rw-r--r--app/controllers/dashboard/milestones_controller.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb3
-rw-r--r--app/controllers/explore/projects_controller.rb3
-rw-r--r--app/controllers/groups/children_controller.rb2
-rw-r--r--app/controllers/groups/milestones_controller.rb2
-rw-r--r--app/controllers/import/base_controller.rb2
-rw-r--r--app/controllers/import/bitbucket_server_controller.rb4
-rw-r--r--app/controllers/import/github_controller.rb33
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb13
-rw-r--r--app/controllers/passwords_controller.rb2
-rw-r--r--app/controllers/projects/artifacts_controller.rb9
-rw-r--r--app/controllers/projects/badges_controller.rb13
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/build_artifacts_controller.rb9
-rw-r--r--app/controllers/projects/ci/lints_controller.rb8
-rw-r--r--app/controllers/projects/commit_controller.rb4
-rw-r--r--app/controllers/projects/discussions_controller.rb2
-rw-r--r--app/controllers/projects/error_tracking_controller.rb44
-rw-r--r--app/controllers/projects/git_http_controller.rb4
-rw-r--r--app/controllers/projects/issues_controller.rb16
-rw-r--r--app/controllers/projects/jobs_controller.rb14
-rw-r--r--app/controllers/projects/lfs_locks_api_controller.rb10
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/controllers/projects/milestones_controller.rb4
-rw-r--r--app/controllers/projects/pages_controller.rb2
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb2
-rw-r--r--app/controllers/projects/releases_controller.rb9
-rw-r--r--app/controllers/projects/serverless/functions_controller.rb31
-rw-r--r--app/controllers/projects/settings/operations_controller.rb46
-rw-r--r--app/controllers/projects_controller.rb16
-rw-r--r--app/controllers/uploads_controller.rb4
-rw-r--r--app/finders/issuable_finder.rb20
-rw-r--r--app/finders/milestones_finder.rb21
-rw-r--r--app/finders/projects/serverless/functions_finder.rb31
-rw-r--r--app/graphql/types/permission_types/project.rb2
-rw-r--r--app/helpers/application_helper.rb11
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/auth_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb3
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/environments_helper.rb9
-rw-r--r--app/helpers/issuables_helper.rb2
-rw-r--r--app/helpers/members_helper.rb2
-rw-r--r--app/helpers/notes_helper.rb2
-rw-r--r--app/helpers/projects/error_tracking_helper.rb15
-rw-r--r--app/helpers/projects_helper.rb25
-rw-r--r--app/helpers/release_blog_post_helper.rb7
-rw-r--r--app/helpers/runners_helper.rb10
-rw-r--r--app/helpers/search_helper.rb4
-rw-r--r--app/helpers/user_callouts_helper.rb4
-rw-r--r--app/mailers/emails/issues.rb11
-rw-r--r--app/mailers/previews/notify_preview.rb4
-rw-r--r--app/models/appearance.rb6
-rw-r--r--app/models/blob_viewer/gitlab_ci_yml.rb8
-rw-r--r--app/models/ci/bridge.rb21
-rw-r--r--app/models/ci/build.rb60
-rw-r--r--app/models/ci/build_metadata.rb12
-rw-r--r--app/models/ci/job_artifact.rb2
-rw-r--r--app/models/ci/pipeline.rb25
-rw-r--r--app/models/ci/runner.rb4
-rw-r--r--app/models/ci/stage.rb1
-rw-r--r--app/models/clusters/applications/ingress.rb2
-rw-r--r--app/models/clusters/applications/knative.rb40
-rw-r--r--app/models/clusters/applications/prometheus.rb34
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb4
-rw-r--r--app/models/clusters/concerns/application_status.rb4
-rw-r--r--app/models/clusters/platforms/kubernetes.rb4
-rw-r--r--app/models/commit_collection.rb6
-rw-r--r--app/models/commit_status.rb1
-rw-r--r--app/models/concerns/cache_markdown_field.rb2
-rw-r--r--app/models/concerns/ci/processable.rb27
-rw-r--r--app/models/concerns/has_status.rb10
-rw-r--r--app/models/concerns/manual_inverse_association.rb4
-rw-r--r--app/models/container_repository.rb11
-rw-r--r--app/models/dashboard_group_milestone.rb7
-rw-r--r--app/models/email.rb2
-rw-r--r--app/models/error_tracking/project_error_tracking_setting.rb59
-rw-r--r--app/models/external_issue.rb8
-rw-r--r--app/models/global_milestone.rb1
-rw-r--r--app/models/group.rb6
-rw-r--r--app/models/group_milestone.rb3
-rw-r--r--app/models/issue.rb3
-rw-r--r--app/models/label.rb1
-rw-r--r--app/models/list.rb2
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/merge_request.rb67
-rw-r--r--app/models/milestone.rb59
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/pool_repository.rb6
-rw-r--r--app/models/project.rb163
-rw-r--r--app/models/project_import_data.rb4
-rw-r--r--app/models/project_services/irker_service.rb2
-rw-r--r--app/models/project_services/teamcity_service.rb6
-rw-r--r--app/models/release.rb14
-rw-r--r--app/models/releases/link.rb22
-rw-r--r--app/models/releases/source.rb35
-rw-r--r--app/models/remote_mirror.rb12
-rw-r--r--app/models/repository.rb12
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/ssh_host_key.rb5
-rw-r--r--app/models/storage/hashed_project.rb2
-rw-r--r--app/models/storage/legacy_project.rb11
-rw-r--r--app/models/suggestion.rb12
-rw-r--r--app/models/user.rb2
-rw-r--r--app/policies/base_policy.rb4
-rw-r--r--app/policies/board_policy.rb14
-rw-r--r--app/policies/container_repository_policy.rb5
-rw-r--r--app/policies/personal_snippet_policy.rb2
-rw-r--r--app/policies/project_policy.rb3
-rw-r--r--app/policies/project_snippet_policy.rb11
-rw-r--r--app/presenters/merge_request_presenter.rb4
-rw-r--r--app/presenters/project_presenter.rb16
-rw-r--r--app/serializers/base_serializer.rb10
-rw-r--r--app/serializers/container_repository_entity.rb2
-rw-r--r--app/serializers/container_tag_entity.rb2
-rw-r--r--app/serializers/error_tracking/error_entity.rb10
-rw-r--r--app/serializers/error_tracking/error_serializer.rb7
-rw-r--r--app/serializers/merge_request_diff_entity.rb8
-rw-r--r--app/serializers/merge_request_widget_entity.rb7
-rw-r--r--app/serializers/pipeline_entity.rb2
-rw-r--r--app/serializers/projects/serverless/service_entity.rb36
-rw-r--r--app/services/boards/issues/move_service.rb2
-rw-r--r--app/services/boards/lists/destroy_service.rb2
-rw-r--r--app/services/ci/destroy_expired_job_artifacts_service.rb38
-rw-r--r--app/services/ci/destroy_pipeline_service.rb2
-rw-r--r--app/services/ci/process_pipeline_service.rb16
-rw-r--r--app/services/ci/retry_build_service.rb2
-rw-r--r--app/services/clusters/applications/base_helm_service.rb4
-rw-r--r--app/services/concerns/exclusive_lease_guard.rb13
-rw-r--r--app/services/delete_branch_service.rb2
-rw-r--r--app/services/error_tracking/list_issues_service.rb49
-rw-r--r--app/services/import/base_service.rb35
-rw-r--r--app/services/import/github_service.rb48
-rw-r--r--app/services/issuable_base_service.rb4
-rw-r--r--app/services/issues/import_csv_service.rb53
-rw-r--r--app/services/labels/create_service.rb2
-rw-r--r--app/services/labels/promote_service.rb2
-rw-r--r--app/services/labels/update_service.rb2
-rw-r--r--app/services/lfs/locks_finder_service.rb2
-rw-r--r--app/services/merge_requests/create_service.rb16
-rw-r--r--app/services/milestones/promote_service.rb4
-rw-r--r--app/services/notes/quick_actions_service.rb2
-rw-r--r--app/services/projects/after_rename_service.rb29
-rw-r--r--app/services/projects/autocomplete_service.rb2
-rw-r--r--app/services/projects/container_repository/cleanup_tags_service.rb94
-rw-r--r--app/services/projects/create_from_template_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb24
-rw-r--r--app/services/projects/hashed_storage/migrate_repository_service.rb14
-rw-r--r--app/services/projects/operations/update_service.rb19
-rw-r--r--app/services/projects/protect_default_branch_service.rb67
-rw-r--r--app/services/projects/update_service.rb9
-rw-r--r--app/services/quick_actions/interpret_service.rb41
-rw-r--r--app/services/releases/create_service.rb5
-rw-r--r--app/services/suggestions/apply_service.rb34
-rw-r--r--app/services/upload_service.rb2
-rw-r--r--app/services/users/update_service.rb2
-rw-r--r--app/uploaders/personal_file_uploader.rb18
-rw-r--r--app/uploaders/records_uploads.rb20
-rw-r--r--app/validators/url_validator.rb4
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml2
-rw-r--r--app/views/admin/runners/_runner.html.haml7
-rw-r--r--app/views/admin/users/_user.html.haml2
-rw-r--r--app/views/ci/status/_dropdown_graph_badge.html.haml4
-rw-r--r--app/views/ci/variables/_content.html.haml2
-rw-r--r--app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml4
-rw-r--r--app/views/clusters/clusters/_integration_form.html.haml20
-rw-r--r--app/views/clusters/clusters/show.html.haml1
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/dashboard/activity.html.haml3
-rw-r--r--app/views/dashboard/groups/index.html.haml4
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/dashboard/milestones/index.html.haml2
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/dashboard/projects/starred.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/devise/passwords/edit.html.haml2
-rw-r--r--app/views/devise/sessions/new.html.haml2
-rw-r--r--app/views/devise/sessions/two_factor.html.haml2
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml2
-rw-r--r--app/views/events/event/_note.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/projects/index.html.haml2
-rw-r--r--app/views/explore/projects/starred.html.haml2
-rw-r--r--app/views/explore/projects/trending.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml2
-rw-r--r--app/views/groups/labels/index.html.haml7
-rw-r--r--app/views/groups/merge_requests.html.haml2
-rw-r--r--app/views/groups/milestones/_form.html.haml3
-rw-r--r--app/views/groups/milestones/index.html.haml1
-rw-r--r--app/views/groups/runners/_group_runners.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml28
-rw-r--r--app/views/layouts/_init_client_detection_flags.html.haml7
-rw-r--r--app/views/layouts/application.html.haml3
-rw-r--r--app/views/layouts/group.html.haml2
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml7
-rw-r--r--app/views/layouts/header/_new_dropdown.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml27
-rw-r--r--app/views/layouts/snippets.html.haml2
-rw-r--r--app/views/notify/import_issues_csv_email.html.haml18
-rw-r--r--app/views/notify/import_issues_csv_email.text.erb11
-rw-r--r--app/views/profiles/accounts/show.html.haml34
-rw-r--r--app/views/projects/_activity.html.haml2
-rw-r--r--app/views/projects/_export.html.haml7
-rw-r--r--app/views/projects/_home_panel.html.haml9
-rw-r--r--app/views/projects/_issuable_by_email.html.haml9
-rw-r--r--app/views/projects/_md_preview.html.haml12
-rw-r--r--app/views/projects/_new_project_fields.html.haml2
-rw-r--r--app/views/projects/artifacts/_tree_file.html.haml2
-rw-r--r--app/views/projects/badges/badge_flat-square.svg.erb17
-rw-r--r--app/views/projects/blob/_editor.html.haml4
-rw-r--r--app/views/projects/blob/_markdown_buttons.html.haml13
-rw-r--r--app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml4
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/branches/_panel.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml4
-rw-r--r--app/views/projects/buttons/_notifications.html.haml4
-rw-r--r--app/views/projects/ci/builds/_build.html.haml22
-rw-r--r--app/views/projects/ci/lints/_create.html.haml35
-rw-r--r--app/views/projects/ci/lints/show.html.haml12
-rw-r--r--app/views/projects/cleanup/_show.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml2
-rw-r--r--app/views/projects/diffs/_warning.html.haml2
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/projects/environments/folder.html.haml5
-rw-r--r--app/views/projects/error_tracking/index.html.haml3
-rw-r--r--app/views/projects/issues/_import_export.svg1
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml66
-rw-r--r--app/views/projects/issues/_merge_requests_status.html.haml22
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml41
-rw-r--r--app/views/projects/issues/_new_branch.html.haml2
-rw-r--r--app/views/projects/issues/import_csv/_button.html.haml9
-rw-r--r--app/views/projects/issues/import_csv/_modal.html.haml24
-rw-r--r--app/views/projects/issues/index.html.haml5
-rw-r--r--app/views/projects/jobs/index.html.haml4
-rw-r--r--app/views/projects/labels/index.html.haml7
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml1
-rw-r--r--app/views/projects/milestones/_form.html.haml4
-rw-r--r--app/views/projects/milestones/index.html.haml1
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml2
-rw-r--r--app/views/projects/project_members/_groups.html.haml3
-rw-r--r--app/views/projects/project_members/_new_project_group.html.haml5
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml15
-rw-r--r--app/views/projects/project_members/_team.html.haml9
-rw-r--r--app/views/projects/project_members/import.html.haml12
-rw-r--r--app/views/projects/project_members/index.html.haml23
-rw-r--r--app/views/projects/project_templates/_built_in_templates.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_protected_branch.html.haml2
-rw-r--r--app/views/projects/releases/index.html.haml2
-rw-r--r--app/views/projects/runners/_group_runners.html.haml2
-rw-r--r--app/views/projects/serverless/functions/index.html.haml2
-rw-r--r--app/views/projects/serverless/functions/show.html.haml16
-rw-r--r--app/views/projects/settings/operations/_error_tracking.html.haml30
-rw-r--r--app/views/projects/settings/operations/show.html.haml5
-rw-r--r--app/views/projects/tags/_tag.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml21
-rw-r--r--app/views/search/_category.html.haml26
-rw-r--r--app/views/search/_filter.html.haml20
-rw-r--r--app/views/search/_form.html.haml6
-rw-r--r--app/views/search/_results.html.haml8
-rw-r--r--app/views/search/results/_empty.html.haml2
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml4
-rw-r--r--app/views/search/results/_note.html.haml8
-rw-r--r--app/views/search/results/_snippet_blob.html.haml4
-rw-r--r--app/views/search/results/_snippet_title.html.haml2
-rw-r--r--app/views/search/show.html.haml2
-rw-r--r--app/views/sent_notifications/unsubscribe.html.haml13
-rw-r--r--app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml2
-rw-r--r--app/views/shared/_flash_user_callout.html.haml11
-rw-r--r--app/views/shared/_mini_pipeline_graph.html.haml1
-rw-r--r--app/views/shared/_personal_access_tokens_created_container.html.haml2
-rw-r--r--app/views/shared/_personal_access_tokens_form.html.haml4
-rw-r--r--app/views/shared/_personal_access_tokens_table.html.haml2
-rw-r--r--app/views/shared/boards/components/_board.html.haml2
-rw-r--r--app/views/shared/boards/components/_sidebar.html.haml2
-rw-r--r--app/views/shared/empty_states/_issues.html.haml31
-rw-r--r--app/views/shared/empty_states/_labels.html.haml2
-rw-r--r--app/views/shared/empty_states/_merge_requests.html.haml19
-rw-r--r--app/views/shared/issuable/_board_create_list_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml3
-rw-r--r--app/views/shared/issuable/_feed_buttons.html.haml4
-rw-r--r--app/views/shared/issuable/_filter.html.haml32
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml2
-rw-r--r--app/views/shared/issuable/form/_merge_params.html.haml4
-rw-r--r--app/views/shared/labels/_nav.html.haml4
-rw-r--r--app/views/shared/members/_member.html.haml2
-rw-r--r--app/views/shared/milestones/_search_form.html.haml8
-rw-r--r--app/views/shared/notes/_comment_button.html.haml19
-rw-r--r--app/views/shared/notes/_edit.html.haml2
-rw-r--r--app/views/shared/notes/_edit_form.html.haml8
-rw-r--r--app/views/shared/notes/_form.html.haml6
-rw-r--r--app/views/shared/notes/_hints.html.haml12
-rw-r--r--app/views/shared/notes/_note.html.haml4
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml14
-rw-r--r--app/views/shared/projects/_list.html.haml7
-rw-r--r--app/views/shared/snippets/_snippet.html.haml2
-rw-r--r--app/views/shared/tokens/_scopes_form.html.haml2
-rw-r--r--app/views/users/_overview.html.haml30
-rw-r--r--app/views/users/show.html.haml4
-rw-r--r--app/workers/all_queues.yml5
-rw-r--r--app/workers/build_finished_worker.rb26
-rw-r--r--app/workers/cleanup_container_repository_worker.rb53
-rw-r--r--app/workers/delete_container_repository_worker.rb2
-rw-r--r--app/workers/expire_build_artifacts_worker.rb14
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb28
-rw-r--r--app/workers/git_garbage_collect_worker.rb1
-rw-r--r--app/workers/import_issues_csv_worker.rb20
-rw-r--r--app/workers/namespaceless_project_destroy_worker.rb2
-rw-r--r--app/workers/object_pool/join_worker.rb11
-rw-r--r--app/workers/reactive_caching_worker.rb2
-rw-r--r--app/workers/remote_mirror_notification_worker.rb3
-rw-r--r--app/workers/update_head_pipeline_for_merge_request_worker.rb21
-rwxr-xr-xbin/changelog10
-rwxr-xr-xbin/secpick190
-rw-r--r--changelogs/unreleased/18667-handle-push-opts.yml5
-rw-r--r--changelogs/unreleased/23367-clarify-docs-allow-failure.yml5
-rw-r--r--changelogs/unreleased/25043-empty-states.yml5
-rw-r--r--changelogs/unreleased/25341-add-what-s-new-menu-item-in-top-navigation.yml5
-rw-r--r--changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml5
-rw-r--r--changelogs/unreleased/26375-markdown-footnotes-not-working.yml5
-rw-r--r--changelogs/unreleased/30120-add-flat-square-badge-style.yml5
-rw-r--r--changelogs/unreleased/34758-extend-can-create-cluster-logic.yml5
-rw-r--r--changelogs/unreleased/34758-list-ancestor-clusters.yml5
-rw-r--r--changelogs/unreleased/37990-task-list-bracket.yml5
-rw-r--r--changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml5
-rw-r--r--changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml5
-rw-r--r--changelogs/unreleased/41766-vue-component.yml5
-rw-r--r--changelogs/unreleased/41766-vuex-store.yml5
-rw-r--r--changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml5
-rw-r--r--changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml5
-rw-r--r--changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml5
-rw-r--r--changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml5
-rw-r--r--changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml5
-rw-r--r--changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml5
-rw-r--r--changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml5
-rw-r--r--changelogs/unreleased/50013-add-browser-platform-flags.yml5
-rw-r--r--changelogs/unreleased/51485-new-issue-labels-note.yml5
-rw-r--r--changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml5
-rw-r--r--changelogs/unreleased/51754-admin-view-private-personal-snippets.yml5
-rw-r--r--changelogs/unreleased/51944-redesign-project-lists-ui.yml5
-rw-r--r--changelogs/unreleased/51970-correct-ordering-of-metrics.yml5
-rw-r--r--changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml5
-rw-r--r--changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml5
-rw-r--r--changelogs/unreleased/52278-squash-checkbox-fix.yml5
-rw-r--r--changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml5
-rw-r--r--changelogs/unreleased/52620-fix-loader-animation-alignment.yml5
-rw-r--r--changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml5
-rw-r--r--changelogs/unreleased/52888-status-emoji-should-not-be-added-to-awards-section-on-issue-page-2.yml5
-rw-r--r--changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml5
-rw-r--r--changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml5
-rw-r--r--changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml5
-rw-r--r--changelogs/unreleased/53493-list-id-email-header.yml5
-rw-r--r--changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml5
-rw-r--r--changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml5
-rw-r--r--changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml5
-rw-r--r--changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml6
-rw-r--r--changelogs/unreleased/53907-improve-milestone-links.yml5
-rw-r--r--changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml5
-rw-r--r--changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml5
-rw-r--r--changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml5
-rw-r--r--changelogs/unreleased/54146-fix-calendar-query.yml5
-rw-r--r--changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml5
-rw-r--r--changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml5
-rw-r--r--changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml5
-rw-r--r--changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml5
-rw-r--r--changelogs/unreleased/54427-label-xss.yml5
-rw-r--r--changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml5
-rw-r--r--changelogs/unreleased/54736-sign-in-bottom-margin.yml5
-rw-r--r--changelogs/unreleased/54786-mr-empty-file-display.yml5
-rw-r--r--changelogs/unreleased/54814-sidebar-styling-updates.yml5
-rw-r--r--changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml5
-rw-r--r--changelogs/unreleased/54905-milestone-search.yml5
-rw-r--r--changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml5
-rw-r--r--changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml5
-rw-r--r--changelogs/unreleased/55191-update-workhorse.yml5
-rw-r--r--changelogs/unreleased/55242-skeleton-loading-releases.yml5
-rw-r--r--changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml5
-rw-r--r--changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml5
-rw-r--r--changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml5
-rw-r--r--changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml5
-rw-r--r--changelogs/unreleased/55484-fix-edit-button.yml4
-rw-r--r--changelogs/unreleased/55495-teamcity-use-revision-in-query.yml5
-rw-r--r--changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml5
-rw-r--r--changelogs/unreleased/55669-redesign-project-lists-ui-further-improvements.yml5
-rw-r--r--changelogs/unreleased/55716-update-cert-manager-chart-from-v0-5-0-to-v0-5-2.yml5
-rw-r--r--changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml5
-rw-r--r--changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml5
-rw-r--r--changelogs/unreleased/55838-remove-gem-install-bundler-from-docker-based-ruby-environments.yml5
-rw-r--r--changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml5
-rw-r--r--changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml5
-rw-r--r--changelogs/unreleased/55945-suggested-change-preview-highlight.yml5
-rw-r--r--changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml5
-rw-r--r--changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml5
-rw-r--r--changelogs/unreleased/56019-archived-stuck.yml5
-rw-r--r--changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml4
-rw-r--r--changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml5
-rw-r--r--changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml5
-rw-r--r--changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml5
-rw-r--r--changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml6
-rw-r--r--changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml5
-rw-r--r--changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml5
-rw-r--r--changelogs/unreleased/56417-update-helm-to-2-12-2.yml5
-rw-r--r--changelogs/unreleased/56424-fix-gl-form-init-tag-editing.yml5
-rw-r--r--changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml5
-rw-r--r--changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml5
-rw-r--r--changelogs/unreleased/56556-fix-markdown-table-border.yml5
-rw-r--r--changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml5
-rw-r--r--changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml5
-rw-r--r--changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml5
-rw-r--r--changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml5
-rw-r--r--changelogs/unreleased/ac-pages-subgroups.yml5
-rw-r--r--changelogs/unreleased/ac-releases-api.yml5
-rw-r--r--changelogs/unreleased/ac-releases-name-sha-author.yml5
-rw-r--r--changelogs/unreleased/actioncontroller-parameters-deprecations.yml5
-rw-r--r--changelogs/unreleased/add-badge-count-to-projects-and-groups.yml5
-rw-r--r--changelogs/unreleased/add-new-nginx-metrics.yml5
-rw-r--r--changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml5
-rw-r--r--changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml5
-rw-r--r--changelogs/unreleased/allow_collaboration_status_work.yml5
-rw-r--r--changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml5
-rw-r--r--changelogs/unreleased/an-gilab-process-name.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-active-record-tracing.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-factory.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-propagation.yml5
-rw-r--r--changelogs/unreleased/api-nested-group-permission.yml5
-rw-r--r--changelogs/unreleased/api-tags-search.yml5
-rw-r--r--changelogs/unreleased/api-wiki-dot-slug.yml5
-rw-r--r--changelogs/unreleased/auto-devops-custom-domains.yml5
-rw-r--r--changelogs/unreleased/auto-devops-kubectl-1-11-6.yml5
-rw-r--r--changelogs/unreleased/backup_aws_sse-c.yml5
-rw-r--r--changelogs/unreleased/backup_restore_fix_issue_46891.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml5
-rw-r--r--changelogs/unreleased/bump-ingress-chart-112.yml5
-rw-r--r--changelogs/unreleased/bvl-hide-confidential-events-take2.yml5
-rw-r--r--changelogs/unreleased/ccr-49289_milestone_link.yml5
-rw-r--r--changelogs/unreleased/ci-dropdown-hidden-bug.yml5
-rw-r--r--changelogs/unreleased/cleanup-leagcy-artifact-migration.yml5
-rw-r--r--changelogs/unreleased/container-repository-cleanup-api.yml5
-rw-r--r--changelogs/unreleased/custom-helm-chart-repo.yml5
-rw-r--r--changelogs/unreleased/depracated-migration-inheritance.yml5
-rw-r--r--changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml5
-rw-r--r--changelogs/unreleased/deprecated-alias-method-chain.yml6
-rw-r--r--changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml6
-rw-r--r--changelogs/unreleased/deprecated-delete-all-params.yml5
-rw-r--r--changelogs/unreleased/deprecated-directly-inheriting-migration.yml5
-rw-r--r--changelogs/unreleased/deprecated-force-reload.yml6
-rw-r--r--changelogs/unreleased/deprecated-insert-sql.yml5
-rw-r--r--changelogs/unreleased/deprecated-passing-activerecord-objects.yml5
-rw-r--r--changelogs/unreleased/deprecated-positional-seperator-parameter.yml5
-rw-r--r--changelogs/unreleased/deprecated-positional-spec-arguments.yml5
-rw-r--r--changelogs/unreleased/deprecated-redirect-back.yml5
-rw-r--r--changelogs/unreleased/diff-empty-state-fixes.yml5
-rw-r--r--changelogs/unreleased/diff-tree-collapse-directories.yml5
-rw-r--r--changelogs/unreleased/dm-note-email-image-diff-discussion.yml5
-rw-r--r--changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml5
-rw-r--r--changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml5
-rw-r--r--changelogs/unreleased/expire-job-artifacts-worker.yml5
-rw-r--r--changelogs/unreleased/feature-option-to-make-variables-protected.yml5
-rw-r--r--changelogs/unreleased/features-document-graphicsmagick-source-installation.yml5
-rw-r--r--changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml5
-rw-r--r--changelogs/unreleased/fix-55448.yml5
-rw-r--r--changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml5
-rw-r--r--changelogs/unreleased/fix-56558-move-primary-button.yml5
-rw-r--r--changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml5
-rw-r--r--changelogs/unreleased/fix-calendar-events-fetching-error.yml5
-rw-r--r--changelogs/unreleased/fix-n-plus-1-queries-projects.yml6
-rw-r--r--changelogs/unreleased/fj-44679-skip-per-commit-validations.yml5
-rw-r--r--changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml5
-rw-r--r--changelogs/unreleased/fj-fix-lfs-image-comments-diffs.yml5
-rw-r--r--changelogs/unreleased/force-redeploy-on-updated-secrets.yml5
-rw-r--r--changelogs/unreleased/force-reload-arguments-2.yml5
-rw-r--r--changelogs/unreleased/gitaly-update-1-13-0.yml5
-rw-r--r--changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-ci.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml5
-rw-r--r--changelogs/unreleased/gt-remove-unnecessary-line-before-reply-holder.yml5
-rw-r--r--changelogs/unreleased/gt-remove-unused-button-class.yml5
-rw-r--r--changelogs/unreleased/gt-rename-gray-theme-color-variables.yml5
-rw-r--r--changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml5
-rw-r--r--changelogs/unreleased/gt-update-environment-breadcrumb.yml5
-rw-r--r--changelogs/unreleased/gt-update-navigation-theme-colors.yml5
-rw-r--r--changelogs/unreleased/gt-update-string-struture-for-group-runners.yml5
-rw-r--r--changelogs/unreleased/homepage-proj-descr-cutoff.yml5
-rw-r--r--changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml6
-rw-r--r--changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml5
-rw-r--r--changelogs/unreleased/knative-show-page.yml5
-rw-r--r--changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml5
-rw-r--r--changelogs/unreleased/mk-avoid-read-only-error.yml5
-rw-r--r--changelogs/unreleased/monospace-registry-tags.yml5
-rw-r--r--changelogs/unreleased/move-job-cancel-btn.yml5
-rw-r--r--changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml5
-rw-r--r--changelogs/unreleased/mr-rebase-failing-tests.yml5
-rw-r--r--changelogs/unreleased/none-syntax-highlighting.yml5
-rw-r--r--changelogs/unreleased/notebook-multiple-outputs.yml5
-rw-r--r--changelogs/unreleased/notes-awards-double-tooltip-fix.yml5
-rw-r--r--changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml6
-rw-r--r--changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml5
-rw-r--r--changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml5
-rw-r--r--changelogs/unreleased/profile-project-empty-state.yml5
-rw-r--r--changelogs/unreleased/raise-on-unfiltered-params.yml5
-rw-r--r--changelogs/unreleased/remote-mirror-update-failed-notification.yml5
-rw-r--r--changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml5
-rw-r--r--changelogs/unreleased/remove-diff-coloring.yml5
-rw-r--r--changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml5
-rw-r--r--changelogs/unreleased/remove-rails4-specific-code.yml5
-rw-r--r--changelogs/unreleased/remove-rails4-support.yml5
-rw-r--r--changelogs/unreleased/s3-directories-get.yml6
-rw-r--r--changelogs/unreleased/security-2770-verify-bundle-import-files.yml5
-rw-r--r--changelogs/unreleased/security-48259-private-snippet.yml5
-rw-r--r--changelogs/unreleased/security-53543-user-keeps-access-to-mr-issue-when-removed-from-team.yml5
-rw-r--r--changelogs/unreleased/security-54377-label-milestone-name-xss.yml5
-rw-r--r--changelogs/unreleased/security-bvl-fix-cross-project-mr-exposure.yml5
-rw-r--r--changelogs/unreleased/security-fix-ssrf-import-url-remote-mirror.yml5
-rw-r--r--changelogs/unreleased/security-master-group-cicd-settings-accessible-to-maintainer.yml5
-rw-r--r--changelogs/unreleased/security-master-guests-jobs-api.yml5
-rw-r--r--changelogs/unreleased/security-master-secret-ci-variables-exposed.yml5
-rw-r--r--changelogs/unreleased/security-master-url-rel.yml5
-rw-r--r--changelogs/unreleased/security-refs-available-to-project-guest.yml5
-rw-r--r--changelogs/unreleased/security-todos_not_redacted_for_guests.yml5
-rw-r--r--changelogs/unreleased/sh-bump-omniauth-google-gem.yml5
-rw-r--r--changelogs/unreleased/sh-cache-avatar-paths.yml5
-rw-r--r--changelogs/unreleased/sh-carrierwave-patch-google-acl.yml5
-rw-r--r--changelogs/unreleased/sh-drop-webhooks-project-export.yml5
-rw-r--r--changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml5
-rw-r--r--changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml5
-rw-r--r--changelogs/unreleased/sh-fix-branches-api-timeout.yml5
-rw-r--r--changelogs/unreleased/sh-fix-github-import-without-oauth2-config.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-55822.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-9357.yml5
-rw-r--r--changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml5
-rw-r--r--changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml5
-rw-r--r--changelogs/unreleased/sh-preload-associations-for-group-api.yml5
-rw-r--r--changelogs/unreleased/sh-remove-bitbucket-mirror-constant.yml5
-rw-r--r--changelogs/unreleased/shared_with_group_path.yml5
-rw-r--r--changelogs/unreleased/spec-positional-arguments.yml5
-rw-r--r--changelogs/unreleased/specs-positional-arguments.yml5
-rw-r--r--changelogs/unreleased/support-gitaly-tls.yml5
-rw-r--r--changelogs/unreleased/triggermesh-knative-version.yml5
-rw-r--r--changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-styles.yml5
-rw-r--r--changelogs/unreleased/update-sidekiq-cron.yml6
-rw-r--r--changelogs/unreleased/update-smooshpack.yml5
-rw-r--r--changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml5
-rw-r--r--changelogs/unreleased/winh-add-list-dropdown-height.yml5
-rw-r--r--changelogs/unreleased/winh-dropdown-title-padding.yml5
-rw-r--r--changelogs/unreleased/winh-merge-request-commit-context.yml5
-rw-r--r--changelogs/unreleased/winh-princess-mononospace.yml5
-rw-r--r--changelogs/unreleased/winh-upgrade-gitlab-ui.yml5
-rw-r--r--changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml5
-rw-r--r--changelogs/unreleased/zj-backup-restore-object-pools.yml5
-rw-r--r--changelogs/unreleased/zj-feature-gate-set-project-path.yml5
-rw-r--r--config.ru2
-rw-r--r--config/application.rb3
-rw-r--r--config/gitlab.yml.example4
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/initializers/8_devise.rb2
-rw-r--r--config/initializers/ar_mysql_jsonb_support.rb31
-rw-r--r--config/initializers/doorkeeper_openid_connect.rb2
-rw-r--r--config/initializers/kaminari_active_record_relation_methods_with_limit.rb41
-rw-r--r--config/initializers/kubeclient.rb22
-rw-r--r--config/initializers/new_framework_defaults.rb5
-rw-r--r--config/initializers/postgresql_cte.rb2
-rw-r--r--config/initializers/postgresql_opclasses_support.rb12
-rw-r--r--config/initializers/sentry.rb2
-rw-r--r--config/initializers/tracing.rb36
-rw-r--r--config/jsdocs.config.js14
-rw-r--r--config/routes/project.rb12
-rw-r--r--config/routes/repository.rb2
-rw-r--r--config/sidekiq_queues.yml3
-rw-r--r--danger/commit_messages/Dangerfile12
-rw-r--r--danger/documentation/Dangerfile2
-rw-r--r--db/fixtures/development/04_project.rb200
-rw-r--r--db/fixtures/development/10_merge_requests.rb3
-rw-r--r--db/fixtures/development/14_pipelines.rb5
-rw-r--r--db/migrate/20170927095921_add_ci_builds_index_for_jobscontroller.rb2
-rw-r--r--db/migrate/20181119132520_add_indexes_to_ci_builds_and_pipelines.rb44
-rw-r--r--db/migrate/20181212171634_create_error_tracking_settings.rb17
-rw-r--r--db/migrate/20181219145521_add_options_to_build_metadata.rb15
-rw-r--r--db/migrate/20181228175414_create_releases_link_table.rb19
-rw-r--r--db/migrate/20190103140724_make_legacy_false_default.rb9
-rw-r--r--db/migrate/20190104182041_cleanup_legacy_artifact_migration.rb34
-rw-r--r--db/migrate/20190108192941_remove_partial_index_from_ci_builds_artifacts_file.rb18
-rw-r--r--db/migrate/20190114172110_add_domain_to_cluster.rb9
-rw-r--r--db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb11
-rw-r--r--db/post_migrate/20161221153951_rename_reserved_project_names.rb10
-rw-r--r--db/post_migrate/20170313133418_rename_more_reserved_project_names.rb10
-rw-r--r--db/post_migrate/20170317162059_update_upload_paths_to_system.rb2
-rw-r--r--db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb26
-rw-r--r--db/post_migrate/20190115054215_migrate_delete_container_repository_worker.rb15
-rw-r--r--db/schema.rb30
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md2
-rw-r--r--doc/administration/auth/ldap.md2
-rw-r--r--doc/administration/container_registry.md12
-rw-r--r--doc/administration/gitaly/index.md21
-rw-r--r--doc/administration/high_availability/redis.md8
-rw-r--r--doc/administration/high_availability/redis_source.md8
-rw-r--r--doc/administration/housekeeping.md19
-rw-r--r--doc/administration/incoming_email.md70
-rw-r--r--doc/administration/index.md5
-rw-r--r--doc/administration/integration/terminal.md39
-rw-r--r--doc/administration/issue_closing_pattern.md2
-rw-r--r--doc/administration/monitoring/performance/grafana_configuration.md2
-rw-r--r--doc/administration/operations/unicorn.md12
-rw-r--r--doc/administration/pages/index.md36
-rw-r--r--doc/administration/raketasks/check.md6
-rw-r--r--doc/administration/reply_by_email_postfix_setup.md2
-rw-r--r--doc/administration/repository_storage_types.md4
-rw-r--r--doc/administration/troubleshooting/debug.md6
-rw-r--r--doc/api/README.md35
-rw-r--r--doc/api/access_requests.md16
-rw-r--r--doc/api/applications.md53
-rw-r--r--doc/api/avatar.md38
-rw-r--r--doc/api/award_emoji.md213
-rw-r--r--doc/api/boards.md14
-rw-r--r--doc/api/branches.md213
-rw-r--r--doc/api/broadcast_messages.md10
-rw-r--r--doc/api/commits.md24
-rw-r--r--doc/api/container_registry.md200
-rw-r--r--doc/api/custom_attributes.md8
-rw-r--r--doc/api/deploy_key_multiple_projects.md8
-rw-r--r--doc/api/deploy_keys.md14
-rw-r--r--doc/api/deployments.md4
-rw-r--r--doc/api/discussions.md54
-rw-r--r--doc/api/environments.md10
-rw-r--r--doc/api/events.md6
-rw-r--r--doc/api/features.md9
-rw-r--r--doc/api/graphql/index.md2
-rw-r--r--doc/api/group_badges.md12
-rw-r--r--doc/api/group_boards.md14
-rw-r--r--doc/api/group_level_variables.md10
-rw-r--r--doc/api/group_milestones.md2
-rw-r--r--doc/api/groups.md8
-rw-r--r--doc/api/import.md33
-rw-r--r--doc/api/issues.md58
-rw-r--r--doc/api/jobs.md26
-rw-r--r--doc/api/labels.md12
-rw-r--r--doc/api/lint.md7
-rw-r--r--doc/api/members.md24
-rw-r--r--doc/api/merge_requests.md26
-rw-r--r--doc/api/milestones.md2
-rw-r--r--doc/api/namespaces.md8
-rw-r--r--doc/api/notes.md28
-rw-r--r--doc/api/notification_settings.md12
-rw-r--r--doc/api/oauth2.md204
-rw-r--r--doc/api/pages_domains.md18
-rw-r--r--doc/api/pipeline_triggers.md12
-rw-r--r--doc/api/pipelines.md12
-rw-r--r--doc/api/project_badges.md12
-rw-r--r--doc/api/project_clusters.md346
-rw-r--r--doc/api/project_import_export.md12
-rw-r--r--doc/api/project_level_variables.md10
-rw-r--r--doc/api/project_snippets.md2
-rw-r--r--doc/api/projects.md33
-rw-r--r--doc/api/protected_branches.md8
-rw-r--r--doc/api/protected_tags.md8
-rw-r--r--doc/api/releases/index.md482
-rw-r--r--doc/api/releases/links.md177
-rw-r--r--doc/api/repositories.md4
-rw-r--r--doc/api/repository_files.md12
-rw-r--r--doc/api/repository_submodules.md2
-rw-r--r--doc/api/resource_label_events.md8
-rw-r--r--doc/api/runners.md18
-rw-r--r--doc/api/search.md34
-rw-r--r--doc/api/services.md2
-rw-r--r--doc/api/settings.md6
-rw-r--r--doc/api/sidekiq_metrics.md8
-rw-r--r--doc/api/snippets.md10
-rw-r--r--doc/api/suggestions.md2
-rw-r--r--doc/api/system_hooks.md8
-rw-r--r--doc/api/tags.md5
-rw-r--r--doc/api/templates/licenses.md2
-rw-r--r--doc/api/todos.md7
-rw-r--r--doc/api/users.md30
-rw-r--r--doc/api/version.md2
-rw-r--r--doc/api/wikis.md12
-rw-r--r--doc/ci/autodeploy/img/auto_deploy_btn.pngbin16779 -> 0 bytes
-rw-r--r--doc/ci/autodeploy/img/auto_deploy_button.pngbin13321 -> 0 bytes
-rw-r--r--doc/ci/autodeploy/img/auto_deploy_dropdown.pngbin28357 -> 0 bytes
-rw-r--r--doc/ci/autodeploy/img/auto_monitoring.pngbin56765 -> 0 bytes
-rw-r--r--doc/ci/autodeploy/img/guide_connect_cluster.pngbin15225 -> 0 bytes
-rw-r--r--doc/ci/autodeploy/img/guide_integration.pngbin15042 -> 0 bytes
-rw-r--r--doc/ci/autodeploy/img/guide_secret.pngbin4803 -> 0 bytes
-rw-r--r--doc/ci/autodeploy/quick_start_guide.md96
-rw-r--r--doc/ci/docker/using_docker_build.md8
-rw-r--r--doc/ci/environments.md84
-rw-r--r--doc/ci/examples/artifactory_and_gitlab/index.md4
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md2
-rw-r--r--doc/ci/examples/deployment/README.md4
-rw-r--r--doc/ci/interactive_web_terminal/index.md5
-rw-r--r--doc/ci/junit_test_reports.md6
-rw-r--r--doc/ci/pipelines.md6
-rw-r--r--doc/ci/review_apps/img/view_on_env_blob.png (renamed from doc/ci/img/view_on_env_blob.png)bin11889 -> 11889 bytes
-rw-r--r--doc/ci/review_apps/img/view_on_env_mr.png (renamed from doc/ci/img/view_on_env_mr.png)bin295181 -> 295181 bytes
-rw-r--r--doc/ci/review_apps/img/view_on_mr_widget.png (renamed from doc/ci/img/view_on_mr_widget.png)bin21942 -> 21942 bytes
-rw-r--r--doc/ci/review_apps/index.md85
-rw-r--r--doc/ci/runners/README.md8
-rw-r--r--doc/ci/variables/README.md24
-rw-r--r--doc/ci/yaml/README.md524
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/adding_database_indexes.md4
-rw-r--r--doc/development/api_styleguide.md6
-rw-r--r--doc/development/automatic_ce_ee_merge.md4
-rw-r--r--doc/development/background_migrations.md6
-rw-r--r--doc/development/contributing/index.md34
-rw-r--r--doc/development/contributing/issue_workflow.md18
-rw-r--r--doc/development/contributing/merge_request_workflow.md16
-rw-r--r--doc/development/documentation/index.md4
-rw-r--r--doc/development/documentation/site_architecture/index.md2
-rw-r--r--doc/development/documentation/styleguide.md284
-rw-r--r--doc/development/emails.md30
-rw-r--r--doc/development/fe_guide/components.md7
-rw-r--r--doc/development/fe_guide/droplab/droplab.md36
-rw-r--r--doc/development/fe_guide/droplab/plugins/ajax.md8
-rw-r--r--doc/development/fe_guide/droplab/plugins/filter.md4
-rw-r--r--doc/development/fe_guide/droplab/plugins/input_setter.md6
-rw-r--r--doc/development/fe_guide/index.md12
-rw-r--r--doc/development/fe_guide/performance.md4
-rw-r--r--doc/development/fe_guide/vue.md6
-rw-r--r--doc/development/fe_guide/vuex.md4
-rw-r--r--doc/development/file_storage.md16
-rw-r--r--doc/development/gitaly.md21
-rw-r--r--doc/development/github_importer.md22
-rw-r--r--doc/development/gotchas.md4
-rw-r--r--doc/development/import_export.md352
-rw-r--r--doc/development/instrumentation.md10
-rw-r--r--doc/development/merge_request_performance_guidelines.md4
-rw-r--r--doc/development/migration_style_guide.md35
-rw-r--r--doc/development/new_fe_guide/development/components.md14
-rw-r--r--doc/development/new_fe_guide/development/performance.md8
-rw-r--r--doc/development/new_fe_guide/development/testing.md22
-rw-r--r--doc/development/new_fe_guide/modules/index.md4
-rw-r--r--doc/development/ordering_table_columns.md7
-rw-r--r--doc/development/performance.md26
-rw-r--r--doc/development/policies.md22
-rw-r--r--doc/development/polymorphic_associations.md12
-rw-r--r--doc/development/post_deployment_migrations.md6
-rw-r--r--doc/development/rake_tasks.md8
-rw-r--r--doc/development/reusing_abstractions.md18
-rw-r--r--doc/development/rolling_out_changes_using_feature_flags.md8
-rw-r--r--doc/development/sidekiq_debugging.md9
-rw-r--r--doc/development/testing_guide/best_practices.md4
-rw-r--r--doc/development/testing_guide/flaky_tests.md73
-rw-r--r--doc/development/testing_guide/frontend_testing.md20
-rw-r--r--doc/development/testing_guide/img/review_apps_cicd_architecture.pngbin0 -> 73240 bytes
-rw-r--r--doc/development/testing_guide/review_apps.md107
-rw-r--r--doc/development/testing_guide/testing_levels.md12
-rw-r--r--doc/development/understanding_explain_plans.md10
-rw-r--r--doc/development/utilities.md14
-rw-r--r--doc/development/verifying_database_capabilities.md6
-rw-r--r--doc/gitlab-basics/create-project.md103
-rw-r--r--doc/gitlab-basics/img/create_new_project_button.pngbin3702 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/create_new_project_info.pngbin71608 -> 0 bytes
-rw-r--r--doc/gitlab-basics/start-using-git.md48
-rw-r--r--doc/install/aws/index.md4
-rw-r--r--doc/install/google_cloud_platform/index.md6
-rw-r--r--doc/install/installation.md526
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md4
-rw-r--r--doc/install/openshift_and_gitlab/index.md6
-rw-r--r--doc/install/structure.md8
-rw-r--r--doc/integration/akismet.md2
-rw-r--r--doc/integration/auth0.md8
-rw-r--r--doc/integration/azure.md8
-rw-r--r--doc/integration/gmail_action_buttons_for_gitlab.md8
-rw-r--r--doc/integration/oauth_provider.md9
-rw-r--r--doc/integration/omniauth.md4
-rw-r--r--doc/integration/saml.md2
-rw-r--r--doc/integration/twitter.md4
-rw-r--r--doc/migrate_ci_to_ce/README.md4
-rw-r--r--doc/policy/maintenance.md50
-rw-r--r--doc/raketasks/backup_restore.md7
-rw-r--r--doc/security/crime_vulnerability.md10
-rw-r--r--doc/security/webhooks.md6
-rw-r--r--doc/ssh/README.md2
-rw-r--r--doc/system_hooks/system_hooks.md6
-rw-r--r--doc/topics/autodevops/index.md109
-rw-r--r--doc/university/glossary/README.md14
-rw-r--r--doc/university/high-availability/aws/README.md26
-rw-r--r--doc/university/training/end-user/README.md23
-rw-r--r--doc/university/training/topics/env_setup.md2
-rw-r--r--doc/university/training/topics/getting_started.md14
-rw-r--r--doc/university/training/topics/git_add.md10
-rw-r--r--doc/university/training/topics/git_intro.md4
-rw-r--r--doc/university/training/topics/git_log.md10
-rw-r--r--doc/university/training/topics/merge_conflicts.md9
-rw-r--r--doc/university/training/topics/rollback_commits.md14
-rw-r--r--doc/university/training/topics/stash.md15
-rw-r--r--doc/university/training/topics/subtree.md18
-rw-r--r--doc/university/training/topics/unstage.md8
-rw-r--r--doc/update/10.0-to-10.1.md2
-rw-r--r--doc/update/10.1-to-10.2.md2
-rw-r--r--doc/update/10.2-to-10.3.md2
-rw-r--r--doc/update/10.3-to-10.4.md2
-rw-r--r--doc/update/10.4-to-10.5.md2
-rw-r--r--doc/update/10.5-to-10.6.md2
-rw-r--r--doc/update/10.6-to-10.7.md2
-rw-r--r--doc/update/10.7-to-10.8.md2
-rw-r--r--doc/update/10.8-to-11.0.md2
-rw-r--r--doc/update/11.0-to-11.1.md2
-rw-r--r--doc/update/11.1-to-11.2.md2
-rw-r--r--doc/update/11.2-to-11.3.md2
-rw-r--r--doc/update/11.3-to-11.4.md2
-rw-r--r--doc/update/11.4-to-11.5.md4
-rw-r--r--doc/update/11.5-to-11.6.md12
-rw-r--r--doc/update/11.6-to-11.7.md390
-rw-r--r--doc/update/11.7-to-11.8.md394
-rw-r--r--doc/update/5.1-to-5.2.md6
-rw-r--r--doc/update/5.1-to-5.4.md4
-rw-r--r--doc/update/5.1-to-6.0.md4
-rw-r--r--doc/update/5.2-to-5.3.md4
-rw-r--r--doc/update/5.3-to-5.4.md4
-rw-r--r--doc/update/5.4-to-6.0.md4
-rw-r--r--doc/update/6.0-to-6.1.md4
-rw-r--r--doc/update/6.1-to-6.2.md6
-rw-r--r--doc/update/6.2-to-6.3.md4
-rw-r--r--doc/update/6.9-to-7.0.md6
-rw-r--r--doc/update/6.x-or-7.x-to-7.14.md18
-rw-r--r--doc/update/7.0-to-7.1.md2
-rw-r--r--doc/update/7.1-to-7.2.md4
-rw-r--r--doc/update/7.2-to-7.3.md4
-rw-r--r--doc/update/7.3-to-7.4.md6
-rw-r--r--doc/update/7.4-to-7.5.md4
-rw-r--r--doc/update/7.5-to-7.6.md4
-rw-r--r--doc/update/7.6-to-7.7.md4
-rw-r--r--doc/update/7.7-to-7.8.md6
-rw-r--r--doc/update/7.8-to-7.9.md6
-rw-r--r--doc/update/7.9-to-7.10.md6
-rw-r--r--doc/update/8.10-to-8.11.md2
-rw-r--r--doc/update/8.11-to-8.12.md2
-rw-r--r--doc/update/8.12-to-8.13.md2
-rw-r--r--doc/update/8.13-to-8.14.md2
-rw-r--r--doc/update/8.14-to-8.15.md2
-rw-r--r--doc/update/8.15-to-8.16.md2
-rw-r--r--doc/update/8.16-to-8.17.md2
-rw-r--r--doc/update/8.17-to-9.0.md2
-rw-r--r--doc/update/9.0-to-9.1.md2
-rw-r--r--doc/update/9.1-to-9.2.md2
-rw-r--r--doc/update/9.2-to-9.3.md2
-rw-r--r--doc/update/9.3-to-9.4.md2
-rw-r--r--doc/update/9.4-to-9.5.md2
-rw-r--r--doc/update/9.5-to-10.0.md2
-rw-r--r--doc/update/upgrading_postgresql_using_slony.md10
-rw-r--r--doc/user/admin_area/broadcast_messages.md51
-rw-r--r--doc/user/admin_area/custom_project_templates.md23
-rw-r--r--doc/user/admin_area/img/broadcast_messages.pngbin0 -> 68535 bytes
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md6
-rw-r--r--doc/user/award_emojis.md32
-rw-r--r--doc/user/discussions/index.md2
-rw-r--r--doc/user/gitlab_com/index.md10
-rw-r--r--doc/user/group/custom_project_templates.md23
-rw-r--r--doc/user/group/subgroups/index.md12
-rw-r--r--doc/user/instance_statistics/user_cohorts.md4
-rw-r--r--doc/user/markdown.md2
-rw-r--r--doc/user/permissions.md5
-rw-r--r--doc/user/profile/account/two_factor_authentication.md12
-rw-r--r--doc/user/profile/index.md2
-rw-r--r--doc/user/project/clusters/index.md7
-rw-r--r--doc/user/project/clusters/serverless/img/serverless-details.pngbin0 -> 63347 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/serverless-page.pngbin194708 -> 62369 bytes
-rw-r--r--doc/user/project/clusters/serverless/index.md33
-rw-r--r--doc/user/project/container_registry.md8
-rw-r--r--doc/user/project/img/issue_boards_multiple.pngbin6086 -> 22623 bytes
-rw-r--r--doc/user/project/img/releases.pngbin43612 -> 0 bytes
-rw-r--r--doc/user/project/import/github.md26
-rw-r--r--doc/user/project/import/index.md7
-rw-r--r--doc/user/project/import/repo_by_url.md2
-rw-r--r--doc/user/project/index.md40
-rw-r--r--doc/user/project/integrations/bamboo.md2
-rw-r--r--doc/user/project/integrations/emails_on_push.md8
-rw-r--r--doc/user/project/integrations/hipchat.md2
-rw-r--r--doc/user/project/integrations/irker.md4
-rw-r--r--doc/user/project/integrations/jira.md24
-rw-r--r--doc/user/project/integrations/jira_cloud_configuration.md2
-rw-r--r--doc/user/project/integrations/mattermost.md4
-rw-r--r--doc/user/project/integrations/prometheus.md15
-rw-r--r--doc/user/project/integrations/prometheus_library/index.md3
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md32
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md58
-rw-r--r--doc/user/project/integrations/slack.md52
-rw-r--r--doc/user/project/issue_board.md1
-rw-r--r--doc/user/project/issues/create_new_issue.md40
-rw-r--r--doc/user/project/issues/csv_import.md63
-rw-r--r--doc/user/project/issues/index.md17
-rw-r--r--doc/user/project/members/index.md2
-rw-r--r--doc/user/project/merge_requests/allow_collaboration.md66
-rw-r--r--doc/user/project/merge_requests/img/allow_collaboration.pngbin21522 -> 11028 bytes
-rw-r--r--doc/user/project/merge_requests/img/allow_collaboration_after_save.pngbin0 -> 5415 bytes
-rw-r--r--doc/user/project/merge_requests/img/checkout_button.pngbin0 -> 5977 bytes
-rw-r--r--doc/user/project/merge_requests/img/create_from_email.pngbin55777 -> 112256 bytes
-rw-r--r--doc/user/project/merge_requests/index.md31
-rw-r--r--doc/user/project/merge_requests/squash_and_merge.md10
-rw-r--r--doc/user/project/operations/error_tracking.md30
-rw-r--r--doc/user/project/operations/img/error_tracking_list.pngbin0 -> 230740 bytes
-rw-r--r--doc/user/project/pages/getting_started_part_one.md10
-rw-r--r--doc/user/project/pages/introduction.md5
-rw-r--r--doc/user/project/pipelines/schedules.md2
-rw-r--r--doc/user/project/pipelines/settings.md91
-rw-r--r--doc/user/project/releases.md13
-rw-r--r--doc/user/project/releases/img/releases.pngbin0 -> 126093 bytes
-rw-r--r--doc/user/project/releases/index.md62
-rw-r--r--doc/user/project/repository/branches/index.md16
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md62
-rw-r--r--doc/user/project/settings/img/general_settings.pngbin35871 -> 154764 bytes
-rw-r--r--doc/user/project/settings/import_export.md2
-rw-r--r--doc/user/project/settings/index.md8
-rw-r--r--doc/user/search/index.md12
-rw-r--r--doc/workflow/lfs/lfs_administration.md22
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md26
-rw-r--r--doc/workflow/releases.md18
-rw-r--r--doc/workflow/repository_mirroring.md13
-rw-r--r--doc/workflow/time_tracking.md16
-rw-r--r--lib/api/api.rb4
-rw-r--r--lib/api/container_registry.rb143
-rw-r--r--lib/api/deployments.rb2
-rw-r--r--lib/api/entities.rb74
-rw-r--r--lib/api/entities/container_registry.rb29
-rw-r--r--lib/api/environments.rb4
-rw-r--r--lib/api/features.rb15
-rw-r--r--lib/api/helpers.rb12
-rw-r--r--lib/api/helpers/pagination.rb19
-rw-r--r--lib/api/helpers/runner.rb2
-rw-r--r--lib/api/helpers/version.rb29
-rw-r--r--lib/api/import_github.rb46
-rw-r--r--lib/api/issues.rb6
-rw-r--r--lib/api/job_artifacts.rb7
-rw-r--r--lib/api/jobs.rb2
-rw-r--r--lib/api/labels.rb2
-rw-r--r--lib/api/lint.rb3
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/pipeline_schedules.rb6
-rw-r--r--lib/api/pipelines.rb6
-rw-r--r--lib/api/project_clusters.rb142
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/projects_relation_builder.rb2
-rw-r--r--lib/api/release/links.rb115
-rw-r--r--lib/api/releases.rb9
-rw-r--r--lib/api/services.rb4
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/api/tags.rb7
-rw-r--r--lib/api/todos.rb2
-rw-r--r--lib/api/triggers.rb10
-rw-r--r--lib/api/variables.rb2
-rw-r--r--lib/api/wikis.rb6
-rw-r--r--lib/backup/files.rb10
-rw-r--r--lib/backup/manager.rb1
-rw-r--r--lib/banzai/filter/emoji_filter.rb1
-rw-r--r--lib/banzai/filter/footnote_filter.rb68
-rw-r--r--lib/banzai/filter/image_lazy_load_filter.rb1
-rw-r--r--lib/banzai/filter/image_link_filter.rb1
-rw-r--r--lib/banzai/filter/inline_diff_filter.rb1
-rw-r--r--lib/banzai/filter/math_filter.rb3
-rw-r--r--lib/banzai/filter/mermaid_filter.rb1
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb4
-rw-r--r--lib/banzai/filter/reference_filter.rb1
-rw-r--r--lib/banzai/filter/relative_link_filter.rb2
-rw-r--r--lib/banzai/filter/sanitization_filter.rb29
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb1
-rw-r--r--lib/banzai/filter/table_of_contents_filter.rb1
-rw-r--r--lib/banzai/filter/task_list_filter.rb4
-rw-r--r--lib/banzai/filter/video_link_filter.rb1
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb11
-rw-r--r--lib/bitbucket_server/client.rb14
-rw-r--r--lib/bitbucket_server/connection.rb17
-rw-r--r--lib/bitbucket_server/paginator.rb12
-rw-r--r--lib/container_registry/tag.rb38
-rw-r--r--lib/feature.rb38
-rw-r--r--lib/gitlab.rb50
-rw-r--r--lib/gitlab/access/branch_protection.rb42
-rw-r--r--lib/gitlab/auth/ldap/person.rb4
-rw-r--r--lib/gitlab/auth/o_auth/user.rb2
-rw-r--r--lib/gitlab/background_migration.rb13
-rw-r--r--lib/gitlab/background_migration/backfill_project_repositories.rb14
-rw-r--r--lib/gitlab/background_migration/migrate_stage_status.rb8
-rw-r--r--lib/gitlab/bitbucket_server_import/importer.rb4
-rw-r--r--lib/gitlab/checks/base_checker.rb6
-rw-r--r--lib/gitlab/checks/change_access.rb5
-rw-r--r--lib/gitlab/checks/diff_check.rb2
-rw-r--r--lib/gitlab/checks/push_check.rb4
-rw-r--r--lib/gitlab/ci/build/step.rb1
-rw-r--r--lib/gitlab/ci/config.rb17
-rw-r--r--lib/gitlab/ci/config/entry/job.rb21
-rw-r--r--lib/gitlab/ci/config/entry/jobs.rb6
-rw-r--r--lib/gitlab/ci/config/entry/policy.rb5
-rw-r--r--lib/gitlab/ci/config/entry/retry.rb3
-rw-r--r--lib/gitlab/ci/config/entry/variables.rb2
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb16
-rw-r--r--lib/gitlab/ci/config/external/file/local.rb9
-rw-r--r--lib/gitlab/ci/config/external/file/project.rb72
-rw-r--r--lib/gitlab/ci/config/external/file/remote.rb6
-rw-r--r--lib/gitlab/ci/config/external/file/template.rb51
-rw-r--r--lib/gitlab/ci/config/external/mapper.rb54
-rw-r--r--lib/gitlab/ci/config/external/processor.rb6
-rw-r--r--lib/gitlab/ci/config/normalizer.rb3
-rw-r--r--lib/gitlab/ci/cron_parser.rb7
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb1
-rw-r--r--lib/gitlab/ci/pipeline/chain/populate.rb4
-rw-r--r--lib/gitlab/ci/pipeline/seed/build.rb10
-rw-r--r--lib/gitlab/ci/pipeline/seed/stage.rb8
-rw-r--r--lib/gitlab/ci/status/bridge/common.rb1
-rw-r--r--lib/gitlab/ci/status/external/common.rb2
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml85
-rw-r--r--lib/gitlab/ci/yaml_processor.rb10
-rw-r--r--lib/gitlab/config/entry/configurable.rb1
-rw-r--r--lib/gitlab/config/entry/factory.rb17
-rw-r--r--lib/gitlab/config/entry/node.rb4
-rw-r--r--lib/gitlab/config/entry/simplifiable.rb7
-rw-r--r--lib/gitlab/diff/lines_unfolder.rb11
-rw-r--r--lib/gitlab/ee_compat_check.rb2
-rw-r--r--lib/gitlab/email/attachment_uploader.rb4
-rw-r--r--lib/gitlab/email/handler/base_handler.rb4
-rw-r--r--lib/gitlab/email/handler/create_issue_handler.rb28
-rw-r--r--lib/gitlab/email/handler/create_merge_request_handler.rb24
-rw-r--r--lib/gitlab/email/handler/create_note_handler.rb2
-rw-r--r--lib/gitlab/email/handler/reply_processing.rb19
-rw-r--r--lib/gitlab/email/handler/unsubscribe_handler.rb24
-rw-r--r--lib/gitlab/error_tracking/error.rb14
-rw-r--r--lib/gitlab/etag_caching/middleware.rb2
-rw-r--r--lib/gitlab/git/bundle_file.rb30
-rw-r--r--lib/gitlab/git/repository.rb5
-rw-r--r--lib/gitlab/git_access.rb50
-rw-r--r--lib/gitlab/git_access_wiki.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb9
-rw-r--r--lib/gitlab/gon_helper.rb15
-rw-r--r--lib/gitlab/import_export/import_export.yml8
-rw-r--r--lib/gitlab/import_export/relation_factory.rb5
-rw-r--r--lib/gitlab/import_export/uploads_manager.rb2
-rw-r--r--lib/gitlab/incoming_email.rb6
-rw-r--r--lib/gitlab/kubernetes/helm.rb2
-rw-r--r--lib/gitlab/kubernetes/kube_client.rb5
-rw-r--r--lib/gitlab/lfs_token.rb4
-rw-r--r--lib/gitlab/loop_helpers.rb24
-rw-r--r--lib/gitlab/middleware/basic_health_check.rb2
-rw-r--r--lib/gitlab/middleware/go.rb2
-rw-r--r--lib/gitlab/middleware/multipart.rb2
-rw-r--r--lib/gitlab/middleware/read_only/controller.rb20
-rw-r--r--lib/gitlab/pages_client.rb2
-rw-r--r--lib/gitlab/release_blog_post.rb40
-rw-r--r--lib/gitlab/repository_cache.rb4
-rw-r--r--lib/gitlab/request_context.rb2
-rw-r--r--lib/gitlab/seeder.rb13
-rw-r--r--lib/gitlab/sentry.rb8
-rw-r--r--lib/gitlab/shell.rb6
-rw-r--r--lib/gitlab/sidekiq_logging/structured_logger.rb17
-rw-r--r--lib/gitlab/sql/union.rb2
-rw-r--r--lib/gitlab/tracing.rb17
-rw-r--r--lib/gitlab/tracing/common.rb69
-rw-r--r--lib/gitlab/tracing/factory.rb61
-rw-r--r--lib/gitlab/tracing/grpc_interceptor.rb54
-rw-r--r--lib/gitlab/tracing/jaeger_factory.rb97
-rw-r--r--lib/gitlab/tracing/rack_middleware.rb46
-rw-r--r--lib/gitlab/tracing/rails/active_record_subscriber.rb38
-rw-r--r--lib/gitlab/tracing/sidekiq/client_middleware.rb26
-rw-r--r--lib/gitlab/tracing/sidekiq/server_middleware.rb26
-rw-r--r--lib/gitlab/tracing/sidekiq/sidekiq_common.rb22
-rw-r--r--lib/gitlab/url_blocker.rb18
-rw-r--r--lib/gitlab/utils.rb10
-rw-r--r--lib/gitlab/version_info.rb8
-rw-r--r--lib/sentry/client.rb104
-rw-r--r--lib/serializers/json.rb34
-rw-r--r--lib/system_check/base_check.rb8
-rw-r--r--lib/tasks/gitlab/bulk_add_permission.rake6
-rw-r--r--locale/gitlab.pot524
-rw-r--r--package.json74
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock8
-rw-r--r--qa/README.md23
-rw-r--r--qa/Rakefile6
-rw-r--r--qa/qa.rb13
-rw-r--r--qa/qa/fixtures/auto_devops_rack/config.ru2
-rw-r--r--qa/qa/git/repository.rb125
-rw-r--r--qa/qa/page/base.rb12
-rw-r--r--qa/qa/page/component/note.rb6
-rw-r--r--qa/qa/page/component/select2.rb6
-rw-r--r--qa/qa/page/label/index.rb17
-rw-r--r--qa/qa/page/merge_request/show.rb28
-rw-r--r--qa/qa/page/profile/personal_access_tokens.rb38
-rw-r--r--qa/qa/page/project/branches/show.rb62
-rw-r--r--qa/qa/page/project/menu.rb153
-rw-r--r--qa/qa/page/project/new.rb5
-rw-r--r--qa/qa/page/project/operations/kubernetes/add_existing.rb4
-rw-r--r--qa/qa/page/project/settings/deploy_keys.rb33
-rw-r--r--qa/qa/page/project/sub_menus/ci_cd.rb25
-rw-r--r--qa/qa/page/project/sub_menus/common.rb23
-rw-r--r--qa/qa/page/project/sub_menus/issues.rb52
-rw-r--r--qa/qa/page/project/sub_menus/operations.rb47
-rw-r--r--qa/qa/page/project/sub_menus/repository.rb44
-rw-r--r--qa/qa/page/project/sub_menus/settings.rb61
-rw-r--r--qa/qa/page/project/web_ide/edit.rb22
-rw-r--r--qa/qa/page/settings/common.rb2
-rw-r--r--qa/qa/resource/base.rb16
-rw-r--r--qa/qa/resource/deploy_key.rb6
-rw-r--r--qa/qa/resource/fork.rb9
-rw-r--r--qa/qa/resource/kubernetes_cluster.rb2
-rw-r--r--qa/qa/resource/merge_request_from_fork.rb2
-rw-r--r--qa/qa/resource/project.rb12
-rw-r--r--qa/qa/resource/repository/push.rb29
-rw-r--r--qa/qa/resource/user.rb19
-rw-r--r--qa/qa/runtime/browser.rb62
-rw-r--r--qa/qa/runtime/env.rb38
-rw-r--r--qa/qa/runtime/user.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb10
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb92
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb10
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb85
-rw-r--r--qa/qa/support/page/logging.rb16
-rw-r--r--qa/qa/tools/revoke_all_personal_access_tokens.rb44
-rw-r--r--qa/spec/page/logging_spec.rb14
-rw-r--r--qa/spec/resource/base_spec.rb40
-rw-r--r--qa/spec/resource/user_spec.rb118
-rw-r--r--qa/spec/runtime/env_spec.rb59
-rw-r--r--qa/spec/spec_helper.rb34
-rw-r--r--qa/spec/spec_helper_spec.rb264
-rw-r--r--rubocop/cop/inject_enterprise_edition_module.rb10
-rw-r--r--rubocop/spec_helpers.rb11
-rwxr-xr-xscripts/review_apps/review-apps.sh41
-rwxr-xr-xscripts/trigger-build5
-rw-r--r--spec/bin/changelog_spec.rb2
-rw-r--r--spec/controllers/admin/requests_profiles_controller_spec.rb47
-rw-r--r--spec/controllers/application_controller_spec.rb4
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb12
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb7
-rw-r--r--spec/controllers/concerns/checks_collaboration_spec.rb2
-rw-r--r--spec/controllers/dashboard/milestones_controller_spec.rb18
-rw-r--r--spec/controllers/groups/children_controller_spec.rb6
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb31
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb2
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb34
-rw-r--r--spec/controllers/profiles/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb41
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb6
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb2
-rw-r--r--spec/controllers/projects/error_tracking_controller_spec.rb130
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb54
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb44
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb7
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb12
-rw-r--r--spec/controllers/projects/pages_controller_spec.rb4
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb2
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb2
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb16
-rw-r--r--spec/controllers/projects/serverless/functions_controller_spec.rb40
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb157
-rw-r--r--spec/controllers/projects_controller_spec.rb55
-rw-r--r--spec/controllers/search_controller_spec.rb2
-rw-r--r--spec/controllers/uploads_controller_spec.rb16
-rw-r--r--spec/db/schema_spec.rb7
-rw-r--r--spec/factories/ci/bridge.rb8
-rw-r--r--spec/factories/ci/builds.rb58
-rw-r--r--spec/factories/clusters/clusters.rb8
-rw-r--r--spec/factories/clusters/platforms/kubernetes.rb4
-rw-r--r--spec/factories/container_repositories.rb2
-rw-r--r--spec/factories/error_tracking/error.rb24
-rw-r--r--spec/factories/group_members.rb2
-rw-r--r--spec/factories/project_error_tracking_settings.rb10
-rw-r--r--spec/factories/releases/link.rb9
-rw-r--r--spec/factories/wiki_pages.rb2
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb2
-rw-r--r--spec/features/admin/admin_projects_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb2
-rw-r--r--spec/features/atom/dashboard_spec.rb2
-rw-r--r--spec/features/atom/issues_spec.rb2
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb8
-rw-r--r--spec/features/boards/new_issue_spec.rb21
-rw-r--r--spec/features/container_registry_spec.rb2
-rw-r--r--spec/features/cycle_analytics_spec.rb2
-rw-r--r--spec/features/dashboard/help_spec.rb22
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb1
-rw-r--r--spec/features/dashboard/projects_spec.rb3
-rw-r--r--spec/features/dashboard/root_explore_spec.rb33
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb12
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb2
-rw-r--r--spec/features/explore/user_explores_projects_spec.rb18
-rw-r--r--spec/features/groups/empty_states_spec.rb44
-rw-r--r--spec/features/groups_spec.rb6
-rw-r--r--spec/features/ics/dashboard_issues_spec.rb2
-rw-r--r--spec/features/ics/group_issues_spec.rb2
-rw-r--r--spec/features/ics/project_issues_spec.rb2
-rw-r--r--spec/features/issuables/markdown_references/internal_references_spec.rb4
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb2
-rw-r--r--spec/features/issues/resource_label_events_spec.rb2
-rw-r--r--spec/features/issues/user_creates_branch_and_merge_request_spec.rb2
-rw-r--r--spec/features/labels_hierarchy_spec.rb4
-rw-r--r--spec/features/markdown/copy_as_gfm_spec.rb184
-rw-r--r--spec/features/markdown/math_spec.rb4
-rw-r--r--spec/features/merge_request/user_accepts_merge_request_spec.rb4
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb1
-rw-r--r--spec/features/merge_request/user_creates_mr_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_immediately_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb10
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb1
-rw-r--r--spec/features/merge_request/user_sees_discussions_spec.rb12
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb48
-rw-r--r--spec/features/merge_request/user_toggles_whitespace_changes_spec.rb12
-rw-r--r--spec/features/merge_request/user_views_diffs_spec.rb4
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb2
-rw-r--r--spec/features/merge_requests/user_squashes_merge_request_spec.rb2
-rw-r--r--spec/features/milestone_spec.rb2
-rw-r--r--spec/features/projects/artifacts/user_browses_artifacts_spec.rb4
-rw-r--r--spec/features/projects/clusters/applications_spec.rb28
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb56
-rw-r--r--spec/features/projects/clusters/user_spec.rb36
-rw-r--r--spec/features/projects/commit/builds_spec.rb2
-rw-r--r--spec/features/projects/commit/cherry_pick_spec.rb4
-rw-r--r--spec/features/projects/compare_spec.rb15
-rw-r--r--spec/features/projects/environments/environments_spec.rb6
-rw-r--r--spec/features/projects/files/user_browses_lfs_files_spec.rb4
-rw-r--r--spec/features/projects/jobs_spec.rb23
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb52
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb17
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb46
-rw-r--r--spec/features/projects/settings/operations_settings_spec.rb23
-rw-r--r--spec/features/projects/settings/user_tags_project_spec.rb6
-rw-r--r--spec/features/projects/tags/user_edits_tags_spec.rb82
-rw-r--r--spec/features/projects_spec.rb28
-rw-r--r--spec/features/protected_branches_spec.rb2
-rw-r--r--spec/features/runners_spec.rb4
-rw-r--r--spec/features/security/admin_access_spec.rb2
-rw-r--r--spec/features/security/dashboard_access_spec.rb2
-rw-r--r--spec/features/security/profile_access_spec.rb2
-rw-r--r--spec/features/security/project/internal_access_spec.rb2
-rw-r--r--spec/features/security/project/private_access_spec.rb2
-rw-r--r--spec/features/security/project/public_access_spec.rb2
-rw-r--r--spec/features/security/project/snippet/internal_access_spec.rb2
-rw-r--r--spec/features/security/project/snippet/private_access_spec.rb4
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb2
-rw-r--r--spec/features/users/overview_spec.rb2
-rw-r--r--spec/features/users/terms_spec.rb2
-rw-r--r--spec/finders/groups_finder_spec.rb20
-rw-r--r--spec/finders/issues_finder_spec.rb19
-rw-r--r--spec/finders/merge_request_target_project_finder_spec.rb2
-rw-r--r--spec/finders/milestones_finder_spec.rb6
-rw-r--r--spec/finders/notes_finder_spec.rb2
-rw-r--r--spec/finders/projects/serverless/functions_finder_spec.rb25
-rw-r--r--spec/finders/projects_finder_spec.rb2
-rw-r--r--spec/finders/snippets_finder_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json4
-rw-r--r--spec/fixtures/api/schemas/error_tracking/error.json21
-rw-r--r--spec/fixtures/api/schemas/error_tracking/index.json15
-rw-r--r--spec/fixtures/api/schemas/registry/repository.json9
-rw-r--r--spec/fixtures/api/schemas/registry/tag.json7
-rw-r--r--spec/fixtures/api/schemas/release.json11
-rw-r--r--spec/fixtures/api/schemas/release/link.json11
-rw-r--r--spec/fixtures/api/schemas/release/links.json4
-rw-r--r--spec/fixtures/csv_comma.csv4
-rw-r--r--spec/fixtures/csv_semicolon.csv5
-rw-r--r--spec/fixtures/csv_tab.csv4
-rw-r--r--spec/fixtures/emails/merge_request_multiple_patches.eml2
-rw-r--r--spec/fixtures/emails/merge_request_with_conflicting_patch.eml2
-rw-r--r--spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml2
-rw-r--r--spec/fixtures/emails/valid_merge_request_with_patch.eml2
-rw-r--r--spec/fixtures/emails/valid_new_issue.eml4
-rw-r--r--spec/fixtures/emails/valid_new_issue_empty.eml4
-rw-r--r--spec/fixtures/emails/valid_new_issue_legacy.eml23
-rw-r--r--spec/fixtures/emails/valid_new_issue_with_quote.eml4
-rw-r--r--spec/fixtures/emails/valid_new_merge_request.eml6
-rw-r--r--spec/fixtures/emails/valid_new_merge_request_legacy.eml20
-rw-r--r--spec/fixtures/emails/valid_new_merge_request_no_description.eml4
-rw-r--r--spec/fixtures/emails/valid_new_merge_request_no_subject.eml4
-rw-r--r--spec/fixtures/emails/wrong_issue_incoming_email_token.eml (renamed from spec/fixtures/emails/wrong_incoming_email_token.eml)4
-rw-r--r--spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml18
-rw-r--r--spec/fixtures/malicious.bundle1
-rw-r--r--spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json178
-rw-r--r--spec/fixtures/security-reports/deprecated/gl-sast-report.json944
-rw-r--r--spec/fixtures/security-reports/feature-branch.zipbin7163 -> 7140 bytes
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json30
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json337
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-license-management-report.json222
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-sast-report.json1825
-rw-r--r--spec/fixtures/security-reports/master.zipbin6710 -> 9413 bytes
-rw-r--r--spec/fixtures/security-reports/master/gl-container-scanning-report.json106
-rw-r--r--spec/fixtures/security-reports/master/gl-dependency-scanning-report.json337
-rw-r--r--spec/fixtures/security-reports/master/gl-license-management-report.json705
-rw-r--r--spec/fixtures/security-reports/master/gl-sast-report.json1825
-rw-r--r--spec/fixtures/sentry/issues_sample_response.json42
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap37
-rw-r--r--spec/frontend/vue_shared/components/file_row_header_spec.js36
-rw-r--r--spec/graphql/resolvers/project_pipelines_resolver_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/base_permission_type_spec.rb2
-rw-r--r--spec/graphql/types/permission_types/project_spec.rb2
-rw-r--r--spec/graphql/types/project_type_spec.rb2
-rw-r--r--spec/helpers/application_helper_spec.rb15
-rw-r--r--spec/helpers/application_settings_helper_spec.rb39
-rw-r--r--spec/helpers/commits_helper_spec.rb2
-rw-r--r--spec/helpers/issuables_helper_spec.rb6
-rw-r--r--spec/helpers/issues_helper_spec.rb2
-rw-r--r--spec/helpers/notes_helper_spec.rb4
-rw-r--r--spec/helpers/projects/error_tracking_helper_spec.rb58
-rw-r--r--spec/helpers/projects_helper_spec.rb99
-rw-r--r--spec/helpers/runners_helper_spec.rb36
-rw-r--r--spec/helpers/search_helper_spec.rb2
-rw-r--r--spec/helpers/user_callouts_helper_spec.rb9
-rw-r--r--spec/javascripts/behaviors/copy_as_gfm_spec.js4
-rw-r--r--spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js2
-rw-r--r--spec/javascripts/blob_edit/blob_bundle_spec.js13
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js1
-rw-r--r--spec/javascripts/diffs/components/commit_item_spec.js2
-rw-r--r--spec/javascripts/diffs/components/compare_versions_dropdown_spec.js33
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js37
-rw-r--r--spec/javascripts/diffs/components/settings_dropdown_spec.js167
-rw-r--r--spec/javascripts/diffs/components/tree_list_spec.js57
-rw-r--r--spec/javascripts/diffs/mock_data/merge_request_diffs.js52
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js53
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js17
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js46
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js184
-rw-r--r--spec/javascripts/dirty_submit/dirty_submit_collection_spec.js6
-rw-r--r--spec/javascripts/dirty_submit/dirty_submit_form_spec.js28
-rw-r--r--spec/javascripts/dirty_submit/helper.js29
-rw-r--r--spec/javascripts/error_tracking/components/error_tracking_list_spec.js100
-rw-r--r--spec/javascripts/error_tracking/store/mutation_spec.js36
-rw-r--r--spec/javascripts/fixtures/jobs.rb3
-rw-r--r--spec/javascripts/fixtures/oauth_remember_me.html.haml1
-rw-r--r--spec/javascripts/fixtures/sessions.rb26
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js36
-rw-r--r--spec/javascripts/ide/components/file_finder/index_spec.js1
-rw-r--r--spec/javascripts/ide/lib/decorations/controller_spec.js4
-rw-r--r--spec/javascripts/jobs/components/sidebar_spec.js4
-rw-r--r--spec/javascripts/jobs/store/getters_spec.js24
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js84
-rw-r--r--spec/javascripts/lib/utils/datetime_utility_spec.js40
-rw-r--r--spec/javascripts/lib/utils/text_markdown_spec.js387
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js16
-rw-r--r--spec/javascripts/lib/utils/url_utility_spec.js81
-rw-r--r--spec/javascripts/matchers.js39
-rw-r--r--spec/javascripts/notebook/cells/output/html_spec.js2
-rw-r--r--spec/javascripts/notebook/cells/output/index_spec.js27
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js1
-rw-r--r--spec/javascripts/notes/components/discussion_counter_spec.js2
-rw-r--r--spec/javascripts/notes/components/discussion_filter_spec.js46
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js52
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js16
-rw-r--r--spec/javascripts/oauth_remember_me_spec.js7
-rw-r--r--spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js61
-rw-r--r--spec/javascripts/releases/components/release_block_spec.js16
-rw-r--r--spec/javascripts/test_bundle.js11
-rw-r--r--spec/javascripts/user_popovers_spec.js25
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js131
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merge_when_pipeline_succeeds_spec.js14
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js20
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js66
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js100
-rw-r--r--spec/javascripts/vue_mr_widget/mr_widget_options_spec.js28
-rw-r--r--spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js24
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js43
-rw-r--r--spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js99
-rw-r--r--spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js151
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestions_spec.js6
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js156
-rw-r--r--spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js9
-rw-r--r--spec/javascripts/vuex_shared/modules/modal/actions_spec.js31
-rw-r--r--spec/javascripts/vuex_shared/modules/modal/mutations_spec.js49
-rw-r--r--spec/lib/api/api_spec.rb21
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb97
-rw-r--r--spec/lib/api/helpers/version_spec.rb26
-rw-r--r--spec/lib/api/helpers_spec.rb32
-rw-r--r--spec/lib/backup/manager_spec.rb1
-rw-r--r--spec/lib/banzai/color_parser_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/footnote_filter_spec.rb48
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb54
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb45
-rw-r--r--spec/lib/banzai/pipeline/full_pipeline_spec.rb32
-rw-r--r--spec/lib/bitbucket_server/client_spec.rb6
-rw-r--r--spec/lib/bitbucket_server/connection_spec.rb18
-rw-r--r--spec/lib/bitbucket_server/paginator_spec.rb11
-rw-r--r--spec/lib/feature_spec.rb14
-rw-r--r--spec/lib/gitlab/access/branch_protection_spec.rb54
-rw-r--r--spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb2
-rw-r--r--spec/lib/gitlab/auth_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb18
-rw-r--r--spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/delete_diff_files_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb63
-rw-r--r--spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb7
-rw-r--r--spec/lib/gitlab/background_migration/populate_import_state_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/rollback_import_state_data_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb44
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_server_import/importer_spec.rb17
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/push_check_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/step_spec.rb9
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb36
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb151
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/config/external/file/template_spec.rb93
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb117
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/config/normalizer_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb29
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb47
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/status/external/common_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb61
-rw-r--r--spec/lib/gitlab/config/entry/configurable_spec.rb10
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb115
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb18
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb65
-rw-r--r--spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb75
-rw-r--r--spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb38
-rw-r--r--spec/lib/gitlab/email/handler_spec.rb3
-rw-r--r--spec/lib/gitlab/git/bundle_file_spec.rb26
-rw-r--r--spec/lib/gitlab/git/compare_spec.rb2
-rw-r--r--spec/lib/gitlab/git/merge_base_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb17
-rw-r--r--spec/lib/gitlab/git_access_spec.rb18
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb2
-rw-r--r--spec/lib/gitlab/gon_helper_spec.rb9
-rw-r--r--spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb2
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb32
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml8
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/reader_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml12
-rw-r--r--spec/lib/gitlab/incoming_email_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb6
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb32
-rw-r--r--spec/lib/gitlab/loop_helpers_spec.rb45
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb24
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb34
-rw-r--r--spec/lib/gitlab/release_blog_post_spec.rb97
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb22
-rw-r--r--spec/lib/gitlab/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb31
-rw-r--r--spec/lib/gitlab/slash_commands/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/tracing/factory_spec.rb43
-rw-r--r--spec/lib/gitlab/tracing/grpc_interceptor_spec.rb47
-rw-r--r--spec/lib/gitlab/tracing/jaeger_factory_spec.rb71
-rw-r--r--spec/lib/gitlab/tracing/rack_middleware_spec.rb62
-rw-r--r--spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb73
-rw-r--r--spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb43
-rw-r--r--spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb43
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/utils_spec.rb16
-rw-r--r--spec/lib/gitlab_spec.rb77
-rw-r--r--spec/lib/omni_auth/strategies/jwt_spec.rb2
-rw-r--r--spec/lib/sentry/client_spec.rb119
-rw-r--r--spec/lib/serializers/json_spec.rb102
-rw-r--r--spec/mailers/emails/issues_spec.rb33
-rw-r--r--spec/mailers/emails/pages_domains_spec.rb2
-rw-r--r--spec/mailers/notify_spec.rb2
-rw-r--r--spec/migrations/README.md76
-rw-r--r--spec/migrations/add_foreign_keys_to_todos_spec.rb6
-rw-r--r--spec/migrations/cleanup_legacy_artifact_migration_spec.rb52
-rw-r--r--spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb12
-rw-r--r--spec/migrations/delete_inconsistent_internal_id_records_spec.rb9
-rw-r--r--spec/migrations/rename_more_reserved_project_names_spec.rb5
-rw-r--r--spec/migrations/rename_reserved_project_names_spec.rb5
-rw-r--r--spec/models/appearance_spec.rb7
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb10
-rw-r--r--spec/models/ci/build_metadata_spec.rb4
-rw-r--r--spec/models/ci/build_spec.rb354
-rw-r--r--spec/models/ci/pipeline_spec.rb23
-rw-r--r--spec/models/ci/runner_spec.rb9
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb8
-rw-r--r--spec/models/clusters/applications/helm_spec.rb10
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb14
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb8
-rw-r--r--spec/models/clusters/applications/knative_spec.rb60
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb120
-rw-r--r--spec/models/clusters/applications/runner_spec.rb14
-rw-r--r--spec/models/clusters/cluster_spec.rb28
-rw-r--r--spec/models/clusters/kubernetes_namespace_spec.rb2
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb12
-rw-r--r--spec/models/clusters/providers/gcp_spec.rb12
-rw-r--r--spec/models/commit_collection_spec.rb25
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb2
-rw-r--r--spec/models/concerns/issuable_spec.rb2
-rw-r--r--spec/models/concerns/manual_inverse_association_spec.rb4
-rw-r--r--spec/models/error_tracking/project_error_tracking_setting_spec.rb109
-rw-r--r--spec/models/event_spec.rb5
-rw-r--r--spec/models/external_issue_spec.rb2
-rw-r--r--spec/models/global_milestone_spec.rb6
-rw-r--r--spec/models/gpg_key_spec.rb2
-rw-r--r--spec/models/group_spec.rb36
-rw-r--r--spec/models/issue_spec.rb45
-rw-r--r--spec/models/label_note_spec.rb4
-rw-r--r--spec/models/lfs_file_lock_spec.rb2
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/merge_request_diff_commit_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb97
-rw-r--r--spec/models/milestone_spec.rb122
-rw-r--r--spec/models/note_spec.rb15
-rw-r--r--spec/models/project_import_data_spec.rb11
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb2
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb2
-rw-r--r--spec/models/project_spec.rb391
-rw-r--r--spec/models/release_spec.rb25
-rw-r--r--spec/models/releases/link_spec.rb80
-rw-r--r--spec/models/releases/source_spec.rb41
-rw-r--r--spec/models/remote_mirror_spec.rb19
-rw-r--r--spec/models/repository_spec.rb18
-rw-r--r--spec/models/resource_label_event_spec.rb2
-rw-r--r--spec/policies/personal_snippet_policy_spec.rb11
-rw-r--r--spec/policies/project_policy_spec.rb6
-rw-r--r--spec/presenters/projects/settings/deploy_keys_presenter_spec.rb2
-rw-r--r--spec/requests/api/avatar_spec.rb2
-rw-r--r--spec/requests/api/award_emoji_spec.rb4
-rw-r--r--spec/requests/api/boards_spec.rb2
-rw-r--r--spec/requests/api/container_registry_spec.rb224
-rw-r--r--spec/requests/api/discussions_spec.rb2
-rw-r--r--spec/requests/api/features_spec.rb34
-rw-r--r--spec/requests/api/files_spec.rb5
-rw-r--r--spec/requests/api/group_milestones_spec.rb2
-rw-r--r--spec/requests/api/groups_spec.rb14
-rw-r--r--spec/requests/api/import_github_spec.rb56
-rw-r--r--spec/requests/api/internal_spec.rb8
-rw-r--r--spec/requests/api/issues_spec.rb320
-rw-r--r--spec/requests/api/merge_requests_spec.rb8
-rw-r--r--spec/requests/api/notes_spec.rb4
-rw-r--r--spec/requests/api/pages/private_access_spec.rb2
-rw-r--r--spec/requests/api/pages/public_access_spec.rb2
-rw-r--r--spec/requests/api/pipelines_spec.rb4
-rw-r--r--spec/requests/api/project_clusters_spec.rb467
-rw-r--r--spec/requests/api/project_milestones_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb36
-rw-r--r--spec/requests/api/release/links_spec.rb359
-rw-r--r--spec/requests/api/releases_spec.rb193
-rw-r--r--spec/requests/api/repositories_spec.rb5
-rw-r--r--spec/requests/api/resource_label_events_spec.rb2
-rw-r--r--spec/requests/api/runner_spec.rb14
-rw-r--r--spec/requests/api/settings_spec.rb4
-rw-r--r--spec/requests/api/submodules_spec.rb4
-rw-r--r--spec/requests/api/tags_spec.rb12
-rw-r--r--spec/requests/api/users_spec.rb2
-rw-r--r--spec/requests/api/wikis_spec.rb2
-rw-r--r--spec/requests/git_http_spec.rb4
-rw-r--r--spec/requests/lfs_locks_api_spec.rb15
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb2
-rw-r--r--spec/requests/request_profiler_spec.rb2
-rw-r--r--spec/routing/project_routing_spec.rb4
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb35
-rw-r--r--spec/serializers/container_repository_entity_spec.rb2
-rw-r--r--spec/serializers/container_tag_entity_spec.rb2
-rw-r--r--spec/serializers/deployment_entity_spec.rb2
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/services/application_settings/update_service_spec.rb4
-rw-r--r--spec/services/boards/issues/list_service_spec.rb4
-rw-r--r--spec/services/boards/issues/move_service_spec.rb4
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb3
-rw-r--r--spec/services/ci/destroy_expired_job_artifacts_service_spec.rb104
-rw-r--r--spec/services/ci/destroy_pipeline_service_spec.rb4
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb8
-rw-r--r--spec/services/ci/register_job_service_spec.rb7
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb2
-rw-r--r--spec/services/error_tracking/list_issues_service_spec.rb87
-rw-r--r--spec/services/event_create_service_spec.rb2
-rw-r--r--spec/services/git_push_service_spec.rb4
-rw-r--r--spec/services/issues/create_service_spec.rb2
-rw-r--r--spec/services/issues/import_csv_service_spec.rb64
-rw-r--r--spec/services/labels/find_or_create_service_spec.rb2
-rw-r--r--spec/services/labels/promote_service_spec.rb4
-rw-r--r--spec/services/lfs/unlock_file_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_service_spec.rb31
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb2
-rw-r--r--spec/services/projects/after_rename_service_spec.rb124
-rw-r--r--spec/services/projects/container_repository/cleanup_tags_service_spec.rb162
-rw-r--r--spec/services/projects/destroy_service_spec.rb34
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb14
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_service_spec.rb2
-rw-r--r--spec/services/projects/operations/update_service_spec.rb86
-rw-r--r--spec/services/projects/protect_default_branch_service_spec.rb242
-rw-r--r--spec/services/resource_events/change_labels_service_spec.rb2
-rw-r--r--spec/services/resource_events/merge_into_notes_service_spec.rb4
-rw-r--r--spec/services/suggestions/apply_service_spec.rb173
-rw-r--r--spec/services/system_note_service_spec.rb8
-rw-r--r--spec/services/todo_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/entity_leave_service_spec.rb8
-rw-r--r--spec/services/todos/destroy/group_private_service_spec.rb2
-rw-r--r--spec/services/upload_service_spec.rb4
-rw-r--r--spec/services/users/migrate_to_ghost_user_service_spec.rb2
-rw-r--r--spec/services/web_hook_service_spec.rb4
-rw-r--r--spec/sidekiq/cron/job_gem_dependency_spec.rb4
-rw-r--r--spec/simplecov_env.rb6
-rw-r--r--spec/spec_helper.rb12
-rw-r--r--spec/support/features/resolving_discussions_in_issues_shared_examples.rb2
-rw-r--r--spec/support/helpers/api_helpers.rb7
-rw-r--r--spec/support/helpers/graphql_helpers.rb2
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb44
-rw-r--r--spec/support/helpers/login_helpers.rb2
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb28
-rw-r--r--spec/support/helpers/test_env.rb7
-rw-r--r--spec/support/migrations_helpers/cluster_helpers.rb71
-rw-r--r--spec/support/redis/redis_shared_examples.rb4
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb (renamed from spec/support/shared_contexts/email_shared_blocks.rb)0
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/dirty_submit_form_shared_examples.rb26
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb4
-rw-r--r--spec/support/shared_examples/malicious_regexp_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/mentionable_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/project_list_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/url_validator_examples.rb2
-rw-r--r--spec/uploaders/file_uploader_spec.rb2
-rw-r--r--spec/uploaders/import_export_uploader_spec.rb2
-rw-r--r--spec/uploaders/personal_file_uploader_spec.rb32
-rw-r--r--spec/validators/url_validator_spec.rb51
-rw-r--r--spec/views/help/instance_configuration.html.haml_spec.rb6
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb24
-rw-r--r--spec/views/projects/commit/show.html.haml_spec.rb4
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb39
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb37
-rw-r--r--spec/workers/cleanup_container_repository_worker_spec.rb47
-rw-r--r--spec/workers/expire_build_artifacts_worker_spec.rb14
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb11
-rw-r--r--spec/workers/import_issues_csv_worker_spec.rb21
-rw-r--r--spec/workers/prune_old_events_worker_spec.rb4
-rw-r--r--spec/workers/remote_mirror_notification_worker_spec.rb39
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb7
-rw-r--r--spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb10
-rw-r--r--vendor/prometheus/values.yaml102
-rw-r--r--yarn.lock2022
1961 files changed, 36107 insertions, 12030 deletions
diff --git a/.babelrc.js b/.babelrc.js
index bfcc7d96634..1b05a67354e 100644
--- a/.babelrc.js
+++ b/.babelrc.js
@@ -18,6 +18,7 @@ const plugins = [
'@babel/plugin-syntax-import-meta',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-json-strings',
+ '@babel/plugin-proposal-private-methods',
];
// add code coverage tooling if necessary
diff --git a/.gitignore b/.gitignore
index 65f61e1fade..0696dd217af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,3 +80,4 @@ eslint-report.html
package-lock.json
/junit_*.xml
/coverage-frontend/
+jsdoc/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3d12f4142ba..16c56747711 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.9-git-2.18-chrome-69.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.18-chrome-71.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
.dedicated-runner: &dedicated-runner
retry: 1
@@ -315,6 +315,7 @@ cloud-native-image:
variables:
GIT_DEPTH: "1"
cache: {}
+ when: always
script:
- gem install gitlab --no-document
- CNG_PROJECT_PATH="gitlab-org/build/CNG" BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN ./scripts/trigger-build cng
@@ -426,15 +427,7 @@ setup-test-env:
- vendor/gitaly-ruby
# GitLab Review apps
-.review-base: &review-base
- <<: *dedicated-no-docs-no-db-pull-cache-job
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
- stage: test
- cache: {}
- dependencies: []
- environment: &review-environment
- name: review/${CI_COMMIT_REF_NAME}
- url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
+.review-only: &review-only
only:
refs:
- branches@gitlab-org/gitlab-ce
@@ -444,6 +437,17 @@ setup-test-env:
refs:
- master
- /(^docs[\/-].*|.*-docs$)/
+
+.review-base: &review-base
+ <<: *dedicated-no-docs-no-db-pull-cache-job
+ <<: *review-only
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
+ stage: test
+ cache: {}
+ dependencies: []
+ environment: &review-environment
+ name: review/${CI_COMMIT_REF_NAME}
+ url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
before_script: []
.review-docker: &review-docker
@@ -498,6 +502,22 @@ rspec-mysql:
<<: *rspec-metadata-mysql
parallel: 50
+.rspec-quarantine: &rspec-quarantine
+ script:
+ - export CACHE_CLASSES=true
+ - scripts/gitaly-test-spawn
+ - bin/rspec --color --format documentation --tag quarantine spec/
+
+rspec-pg-quarantine:
+ <<: *rspec-metadata-pg
+ <<: *rspec-quarantine
+ allow_failure: true
+
+rspec-mysql-quarantine:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-quarantine
+ allow_failure: true
+
static-analysis:
<<: *dedicated-no-docs-no-db-pull-cache-job
dependencies:
@@ -526,7 +546,7 @@ docs lint:
script:
- scripts/lint-doc.sh
- scripts/lint-changelog-yaml
- - mv doc/ /tmp/gitlab-docs/content/
+ - mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
- cd /tmp/gitlab-docs
# Build HTML from Markdown
- bundle exec nanoc
@@ -607,7 +627,7 @@ gitlab:setup-mysql:
# Frontend-related jobs
gitlab:assets:compile:
<<: *dedicated-no-docs-pull-cache-job
- image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-69.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
+ image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-71.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
dependencies: []
services:
- docker:stable-dind
@@ -787,6 +807,7 @@ qa:selectors:
- bundle exec bin/qa Test::Sanity::Selectors
.qa-frontend-node: &qa-frontend-node
+ <<: *dedicated-no-docs-no-db-pull-cache-job
stage: test
variables:
NODE_OPTIONS: --max_old_space_size=3584
@@ -801,11 +822,6 @@ qa:selectors:
- yarn install --frozen-lockfile --cache-folder .yarn-cache
- date
- yarn run webpack-prod
- <<: *except-docs
-
-qa-frontend-node:6:
- <<: *qa-frontend-node
- image: node:6-alpine
qa-frontend-node:8:
<<: *qa-frontend-node
@@ -853,6 +869,21 @@ lint:javascript:report:
paths:
- eslint-report.html
+jsdoc:
+ <<: *dedicated-no-docs-pull-cache-job
+ stage: post-test
+ dependencies:
+ - compile-assets
+ before_script: []
+ script:
+ - date
+ - yarn run jsdoc || true # ignore exit code
+ artifacts:
+ name: jsdoc
+ expire_in: 31d
+ paths:
+ - jsdoc/
+
pages:
<<: *dedicated-no-docs-no-db-pull-cache-job
before_script: []
@@ -862,6 +893,7 @@ pages:
- karma
- gitlab:assets:compile
- lint:javascript:report
+ - jsdoc
script:
- mv public/ .public/
- mkdir public/
@@ -871,6 +903,7 @@ pages:
- mv webpack-report/ public/webpack-report/ || true
- cp .public/assets/application-*.css public/application.css || true
- cp .public/assets/application-*.css.gz public/application.css.gz || true
+ - mv jsdoc/ public/jsdoc/ || true
artifacts:
paths:
- public
@@ -917,6 +950,22 @@ no_ee_check:
- //@gitlab-org/gitlab-ce
# GitLab Review apps
+review-build-cng:
+ <<: *single-script-job
+ <<: *review-only
+ variables:
+ <<: *single-script-job-variables
+ SCRIPT_NAME: trigger-build
+ API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
+ script:
+ - gem install gitlab --no-document
+ - apk add --update openssl curl jq
+ - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/review_apps/review-apps.sh
+ - chmod 755 review-apps.sh
+ - source ./review-apps.sh
+ - wait_for_job_to_be_done "gitlab:assets:compile"
+ - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./$SCRIPT_NAME cng
+
review-deploy:
<<: *review-base
retry: 2
@@ -931,15 +980,14 @@ review-deploy:
<<: *review-environment
on_stop: review-stop
before_script:
- - apk update && apk add jq
- - gem install gitlab --no-document
- script:
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
+ - apk update && apk add jq
+ - gem install gitlab --no-document
- source ./scripts/review_apps/review-apps.sh
- - wait_for_job_to_be_done "gitlab:assets:compile"
- - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
+ script:
+ - wait_for_job_to_be_done "review-build-cng"
- check_kube_domain
- download_gitlab_chart
- ensure_namespace
@@ -950,7 +998,6 @@ review-deploy:
.review-qa-base: &review-qa-base
<<: *review-docker
- retry: 2
allow_failure: true
variables:
<<: *review-docker-variables
diff --git a/.gitlab/issue_templates/Add style proposal.md b/.gitlab/issue_templates/Add style proposal.md
new file mode 100644
index 00000000000..1a3be44bea0
--- /dev/null
+++ b/.gitlab/issue_templates/Add style proposal.md
@@ -0,0 +1,16 @@
+## Description of the proposal
+
+<!--
+Please describe the proposal and add a link to the source (for example, http://www.betterspecs.org/).
+-->
+
+- [ ] Mention the proposal in the next backend weekly call and the #backend channel to encourage contribution
+- [ ] Proceed with the proposal once 50% of the maintainers have weighed in, and 80% of the votes are :+1:
+- [ ] Once approved, mention it again in the next backend weekly call and the #backend channel
+
+
+/label ~"development guidelines"
+/label ~"Style decision"
+/label ~Documentation
+
+/cc @gitlab-org/maintainers/rails-backend
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index 639a236631d..0b22c7bc26b 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -4,7 +4,30 @@
### Target audience
-<!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst" -->
+<!--- For whom are we doing this? Include a [persona](https://design.gitlab.com/research/personas)
+listed below, if applicable, along with its [label](https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A),
+or define a specific company role, e.g. "Release Manager".
+
+Existing personas are: (copy relevant personas out of this comment, and delete any persona that does not apply)
+
+- Parker, Product Manager, https://design.gitlab.com/research/personas#persona-parker
+/label ~"Persona: Product Manager"
+
+- Delaney, Development Team Lead, https://design.gitlab.com/research/personas#persona-delaney
+/label ~"Persona: Development Team Lead"
+
+- Sasha, Software Developer, https://design.gitlab.com/research/personas#persona-sasha
+/label ~"Persona: Software developer"
+
+- Devon, DevOps Engineer, https://design.gitlab.com/research/personas#persona-devon
+/label ~"Persona: DevOps Engineer"
+
+- Sidney, Systems Administrator, https://design.gitlab.com/research/personas#persona-sidney
+/label ~"Persona: Systems Administrator"
+
+- Sam, Security Analyst, https://design.gitlab.com/research/personas#persona-sam
+/label ~"Persona: Security Analyst"
+-->
### Further details
@@ -12,12 +35,12 @@
### Proposal
-<!--- How are we going to solve the problem? -->
+<!--- How are we going to solve the problem? Try to include the user journey! -->
### What does success look like, and how can we measure that?
-<!--- If no way to measure success, link to an issue that will implement a way to measure this -->
+<!--- Define both the success metrics and acceptance criteria. Note thet success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this -->
### Links / references
-/label ~"feature proposal"
+/label ~feature
diff --git a/.gitlab/issue_templates/Security Release.md b/.gitlab/issue_templates/Security Release.md
new file mode 100644
index 00000000000..1734e915ad2
--- /dev/null
+++ b/.gitlab/issue_templates/Security Release.md
@@ -0,0 +1,69 @@
+<!--
+# Read me first!
+
+Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X`
+-->
+
+## Releases tasks
+
+- https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/release-manager.md
+- https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
+- https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/security-engineer.md
+
+## Version issues:
+
+* 11.4.X: {release task link}
+* 11.3.X: {release task link}
+* 11.2.X: {release task link}
+
+## Security Issues:
+
+### CE
+
+* {https://gitlab.com/gitlab-org/gitlab-ce/issues link}
+
+### EE
+
+* {https://gitlab.com/gitlab-org/gitlab-ee/issues link}
+
+## Security Issues in dev.gitlab.org:
+
+### CE
+
+- {https://dev.gitlab.org/gitlab/gitlabhq/issues link}
+
+| Version | MR | Status|
+|---------|----|-------|
+| 11.4 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+| 11.3 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+| 11.2 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+| master | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+
+
+
+### EE
+
+* {https://dev.gitlab.org/gitlab/gitlabhq/issues/ link}
+
+
+| Version | MR | Status|
+|---------|----|-------|
+| 11.4| {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+| 11.3 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+| 11.2 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+| master | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+
+
+## QA
+{QA issue link}
+
+## Blog post
+
+Dev: {https://dev.gitlab.org/gitlab/www-gitlab-com/merge_requests/ link}<br/>
+gitlab.com: {https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/ link}
+
+## Email notification
+{https://gitlab.com/gitlab-com/marketing/general/issues/ link}
+
+/label ~security
+/confidential
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index 08651195d98..f9bf700f809 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -20,7 +20,7 @@ Set the title to: `[Security] Description of the original issue`
#### Backports
-- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases
+- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases, plus the current RC if between the 7th and 22nd of the month.
- [ ] At this point, it might be easy to squash the commits from the MR into one
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
- [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
diff --git a/.rubocop.yml b/.rubocop.yml
index e8e550fdbde..bcff67ded8c 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -143,6 +143,7 @@ Naming/FileName:
- XMPP
- XSRF
- XSS
+ - GRPC
# GitLab ###################################################################
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 847a0f74aa2..91810d84c50 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -15,12 +15,6 @@ Capybara/CurrentPathExpectation:
Layout/EmptyLinesAroundArguments:
Enabled: false
-# Offense count: 253
-# Cop supports --auto-correct.
-# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
-Layout/ExtraSpacing:
- Enabled: false
-
# Offense count: 83
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth.
@@ -686,17 +680,6 @@ Style/TrailingUnderscoreVariable:
- 'spec/lib/gitlab/etag_caching/middleware_spec.rb'
- 'spec/services/quick_actions/interpret_service_spec.rb'
-# Offense count: 5
-# Cop supports --auto-correct.
-# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
-# Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym
-Style/TrivialAccessors:
- Exclude:
- - 'app/models/external_issue.rb'
- - 'app/serializers/base_serializer.rb'
- - 'lib/gitlab/auth/ldap/person.rb'
- - 'lib/system_check/base_check.rb'
-
# Offense count: 4
# Cop supports --auto-correct.
Style/UnlessElse:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56ac809b548..c1deab58d38 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,217 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.7.0 (2019-01-22)
+
+### Security (14 changes, 1 of them is from the community)
+
+- Escape label and milestone titles to prevent XSS in GFM autocomplete. !2693
+- Bump Ruby on Rails to 5.0.7.1. !23396 (@blackst0ne)
+- Delete confidential todos for user when downgraded to Guest.
+- Project guests no longer are able to see refs page.
+- Set URL rel attribute for broken URLs.
+- Prevent leaking protected variables for ambiguous refs.
+- Authorize before reading job information via API.
+- Allow changing group CI/CD settings only for owners.
+- Fix SSRF with import_url and remote mirror url.
+- Don't expose cross project repositories through diffs when creating merge reqeusts.
+- Validate bundle files before unpacking them.
+- Issuable no longer is visible to users when project can't be viewed.
+- Escape html entities in LabelReferenceFilter when no label found.
+- Prevent private snippets from being embeddable.
+
+### Removed (3 changes, 1 of them is from the community)
+
+- Removes all instances of deprecated Gitlab Upgrader calls. !23603 (@jwolen)
+- Removed discard draft comment button form notes. !24185
+- Remove migration to backfill project_repositories for legacy storage projects. !24299
+
+### Fixed (42 changes, 7 of them are from the community)
+
+- Prevent awards emoji being updated when updating status. !23470
+- Allow merge after rebase without page refresh on FF repositories. !23572
+- Prevent admins from attempting hashed storage migration on read only DB. !23597
+- Correct the ordering of metrics on the performance dashboard. !23630
+- Display empty files properly on MR diffs. !23671 (Sean Nichols)
+- Allow GitHub imports via token even if OAuth2 provider not configured. !23703
+- Update header navigation theme colors. !23734 (George Tsiolis)
+- Fix login box bottom margins on signin page. !23739 (@gear54)
+- Return an ApplicationSetting in CurrentSettings. !23766
+- Fix bug commenting on LFS images. !23812
+- Only prompt user once when navigating away from file editor. !23820 (Sam Bigelow)
+- Display commit ID for discussions made on merge request commits. !23837
+- Stop autofocusing on diff comment after initial mount. !23849
+- Fix object storage not working properly with Google S3 compatibility. !23858
+- Fix project calendar feed when sorted by priority. !23870
+- Fix edit button disappearing in issue title. !23948 (Ruben Moya)
+- Aligns build loader animation with the job log. !23959
+- Allow 'rake gitlab:cleanup:remote_upload_files' to read bucket files without having permissions to see all buckets. !23981
+- Correctly externalize pipeline tags. !24028
+- Fix error when creating labels in a new issue in the boards page. !24039 (Ruben Moya)
+- Use 'parsePikadayDate' to parse due date string. !24045
+- Fix commit SHA not showing in merge request compare dropdown. !24084
+- Remove top margin in modal header titles. !24108
+- Drop Webhooks from project import/export config. !24121
+- Only validate project visibility when it has changed. !24142
+- Resolve About this feature link should open in new window. !24149
+- Add syntax highlighting to suggestion diff. !24156
+- Fix Bitbucket Server import only including first 25 pull requests. !24178
+- Enable caching for records which primary key is not `id`. !24245
+- Adjust applied suggestion reverting previous changes. !24250
+- Fix unexpected exception by failure of finding an actual head pipeline. !24257
+- Fix broken templated "Too many changes to show" text. !24282
+- Fix requests profiler in admin page not rendering HTML properly. !24291
+- Fix no avatar not showing in user selection box. !24346
+- Upgrade to gitaly 1.12.1. !24361
+- Fix runner eternal loop when update job result. !24481
+- Fix notification email for image diff notes.
+- Fixed merge request diffs empty states.
+- Fixed diff suggestions removing dashes.
+- Don't hide CI dropdown behind diff summary. (gfyoung)
+- Fix spacing on discussions.
+- Fixes missing margin in releases block.
+
+### Changed (22 changes, 8 of them are from the community)
+
+- Show clusters of ancestors in cluster list page. !22996
+- Remove unnecessary line before reply holder. !23092 (George Tsiolis)
+- Make the Pages permission setting more clear. !23146
+- Disable merging of labels with same names. !23265
+- Allow basic authentication on go get middleware. !23497 (Morty Choi @mortyccp)
+- No longer require email subaddressing for issue creation by email. !23523
+- Adjust padding of .dropdown-title to comply with design specs. !23546
+- Make commit IDs in merge request discussion header monospace. !23562
+- Update environments breadcrumb. !23751 (George Tsiolis)
+- Add date range in milestone change email notifications. !23762
+- Require Knative to be installed only on an RBAC kubernetes cluster. !23807 (Chris Baumbauer)
+- Fix label and header styles in the job details sidebar. !23816 (Nathan Friend)
+- Add % prefix to milestone reference links. !23928
+- Reorder sidebar menu item for group clusters. !24001 (George Tsiolis)
+- Support CURD operation for Links as one of the Release assets. !24056
+- Upgrade Omniauth and JWT gems to switch away from Google+ API. !24068
+- Renames Milestone sort into Milestone due date. !24080 (Jacopo Beschi @jacopo-beschi)
+- Discussion filter only displayed in discussions tab for merge requests. !24082
+- Make RBAC enabled default for new clusters. !24119
+- Hashed Storage: Only set as `read_only` when starting the per-project migration. !24128
+- Knative version bump 0.1.3 -> 0.2.2. (Chris Baumbauer)
+- Show message on non-diff discussions.
+
+### Performance (7 changes)
+
+- Fix some N+1 queries related to Admin Dashboard, User Dashboards and Activity Stream. !23034
+- Add indexes to speed up CI query. !23188
+- Improve the loading time on merge request's discussion page by caching diff highlight. !23857
+- Cache avatar URLs and paths within a request. !23950
+- Improve snippet search performance by removing duplicate counts. !23952
+- Skip per-commit validations already evaluated. !23984
+- Fix timeout issues retrieving branches via API. !24034
+
+### Added (29 changes, 6 of them are from the community)
+
+- Handle ci.skip push option. !15643 (Jonathon Reinhart)
+- Add NGINX 0.16.0 and above metrics. !22133
+- Add project milestone link. !22552
+- Support tls communication in gitaly. !22602
+- Add option to make ci variables protected by default. !22744 (Alexis Reigel)
+- Add project identifier as List-Id email Header to ease filtering. !22817 (Olivier Crête)
+- Add markdown helper buttons to file editor. !23480
+- Allow to include templates in gitlab-ci.yml. !23495
+- Extend override check to also check arity. !23498 (Jacopo Beschi @jacopo-beschi)
+- Add importing of issues from CSV file. !23532
+- Add submit feedback link to help dropdown. !23547
+- Send a notification email to project maintainers when a mirror update fails. !23595
+- Restore Object Pools when restoring an object pool. !23682
+- Creates component for release block. !23697
+- Configure Auto DevOps deployed applications with secrets from prefixed CI variables. !23719
+- Add name, author_id, and sha to releases table. !23763
+- Display a list of Sentry Issues in GitLab. !23770
+- Releases API. !23795
+- Creates frontend app for releases. !23796
+- Add new pipeline variable CI_COMMIT_SHORT_SHA. !23822
+- Create system notes on issue / MR creation when labels, milestone, or due date is set. !23859
+- Adds API documentation for releases. !23901
+- Add API Support for Kubernetes integration. !23922
+- Expose CI/CD predefined variable `CI_API_V4_URL`. !23936
+- Add Knative metrics to Prometheus. !23972 (Chris Baumbauer)
+- Use reports syntax for Dependency scanning in Auto DevOps. !24081
+- Allow to include files from another projects in gitlab-ci.yml. !24101
+- User Popovers for Commit Infos, Member Lists and Snippets. !24132
+- Add no-color theme for syntax highlighting. (khm)
+
+### Other (45 changes, 30 of them are from the community)
+
+- Redesign project lists UI. !22682
+- [Rails5.1] Update functional specs to use new keyword format. !23095 (@blackst0ne)
+- Update a condition to visibility a merge request collaboration message. !23104 (Harry Kiselev)
+- Remove framework/mobile.scss. !23301 (Takuya Noguchi)
+- Passing the separator argument as a positional parameter is deprecated. !23334 (Jasper Maes)
+- Clarifies docs about CI `allow_failure`. !23367 (C.J. Jameson)
+- Refactor issuable sidebar to use serializer. !23379
+- Refactor the logic of updating head pipelines for merge requests. !23502
+- Allow user to add Kubernetes cluster for clusterable when there are ancestor clusters. !23569
+- Adds explanatory text to input fields on user profile settings page. !23673
+- Externalize strings from `/app/views/shared/notes`. !23696 (Tao Wang)
+- Remove rails 4 support in CI, Gemfiles, bin/ and config/. !23717 (Jasper Maes)
+- Fix calendar events fetching error on private profile page. !23718 (Harry Kiselev)
+- Update GitLab Workhorse to v8.0.0. !23740
+- Hide confidential events in the API. !23746
+- Changed Userpopover Fixtures and shadow color. !23768
+- Fix deprecation: Passing conditions to delete_all is deprecated. !23817 (Jasper Maes)
+- Fix deprecation: Passing ActiveRecord::Base objects to sanitize_sql_hash_for_assignment. !23818 (Jasper Maes)
+- Remove rails4 specific code. !23847 (Jasper Maes)
+- Remove deprecated ActionDispatch::ParamsParser. !23848 (Jasper Maes)
+- Fix deprecation: Comparing equality between ActionController::Parameters and a Hash is deprecated. !23855 (Jasper Maes)
+- Fix deprecation: Directly inheriting from ActiveRecord::Migration is deprecated. !23884 (Jasper Maes)
+- Fix deprecation: alias_method_chain is deprecated. Please, use Module#prepend instead. !23887 (Jasper Maes)
+- Update specs to exclude possible false positive pass. !23893 (@blackst0ne)
+- Passing an argument to force an association to reload is now deprecated. !23894 (Jasper Maes)
+- ActiveRecord::Migration -> ActiveRecord::Migration[5.0]. !23910 (Jasper Maes)
+- Split bio into individual line in extended user tooltips. !23940
+- Fix deprecation: redirect_to :back is deprecated. !23943 (Jasper Maes)
+- Fix deprecation: insert_sql is deprecated and will be removed. !23944 (Jasper Maes)
+- Upgrade @gitlab/ui to 1.16.2. !23946
+- convert specs in javascripts/ and support/ to new syntax. !23947 (Jasper Maes)
+- Remove deprecated xhr from specs. !23949 (Jasper Maes)
+- Remove app/views/shared/issuable/_filter.html.haml. !24008 (Takuya Noguchi)
+- Fix deprecation: Using positional arguments in integration tests. !24009 (Jasper Maes)
+- UI improvements for redesigned project lists. !24011
+- Update cert-manager chart from v0.5.0 to v0.5.2. !24025 (Takuya Noguchi)
+- Hide spinner on empty activites list on user profile overview. !24063
+- Don't show Auto DevOps enabled banner for projects with CI file or CI disabled. !24067
+- Update GitLab Runner Helm Chart to 0.1.43. !24083
+- Fix navigation style in docs. !24090 (Takuya Noguchi)
+- Remove gem install bundler from Docker-based Ruby environments. !24093 (Takuya Noguchi)
+- Fix deprecation: Using positional arguments in integration tests. !24110 (Jasper Maes)
+- Fix deprecation: returning false in Active Record and Active Model callbacks will not implicitly halt a callback chain. !24134 (Jasper Maes)
+- ActiveRecord::Migration -> ActiveRecord::Migration[5.0] for AddIndexesToCiBuildsAndPipelines. !24167 (Jasper Maes)
+- Update url placeholder for the sentry configuration page. !24338
+
+
+## 11.6.5 (2019-01-17)
+
+### Fixed (5 changes)
+
+- Add syntax highlighting to suggestion diff. !24156
+- Fix broken templated "Too many changes to show" text. !24282
+- Fix requests profiler in admin page not rendering HTML properly. !24291
+- Fix no avatar not showing in user selection box. !24346
+- Fixed diff suggestions removing dashes.
+
+
+## 11.6.4 (2019-01-15)
+
+### Security (1 change)
+
+- Validate bundle files before unpacking them.
+
+
+## 11.6.3 (2019-01-04)
+
+### Fixed (1 change)
+
+- Fix clone URL not showing if protocol is HTTPS. !24131
+
+
## 11.6.2 (2019-01-02)
### Fixed (7 changes)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4304f6c8744..97dbe2f512b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,48 +15,6 @@ repository is licensed under Creative Commons:
_This notice should stay as the first item in the CONTRIBUTING.md file._
----
-
-<!-- START doctoc generated TOC please keep comment here to allow auto update -->
-<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
-**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
-
-- [Contributing Documentation has been moved](#contributing-documentation-has-been-moved)
-- [Contribute to GitLab](#contribute-to-gitlab)
-- [Security vulnerability disclosure](#security-vulnerability-disclosure)
-- [Code of conduct](#code-of-conduct)
-- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
-- [Helping others](#helping-others)
-- [I want to contribute!](#i-want-to-contribute)
-- [Contribution Flow](#contribution-flow)
-- [Workflow labels](#workflow-labels)
- - [Type labels](#type-labels)
- - [Subject labels](#subject-labels)
- - [Team labels](#team-labels)
- - [Release Scoping labels](#release-scoping-labels)
- - [Priority labels](#priority-labels)
- - [Severity labels](#severity-labels)
- - [Severity impact guidance](#severity-impact-guidance)
- - [Label for community contributors](#label-for-community-contributors)
-- [Implement design & UI elements](#implement-design--ui-elements)
-- [Issue tracker](#issue-tracker)
- - [Issue triaging](#issue-triaging)
- - [Feature proposals](#feature-proposals)
- - [Issue tracker guidelines](#issue-tracker-guidelines)
- - [Issue weight](#issue-weight)
- - [Regression issues](#regression-issues)
- - [Technical and UX debt](#technical-and-ux-debt)
- - [Stewardship](#stewardship)
-- [Merge requests](#merge-requests)
- - [Merge request guidelines](#merge-request-guidelines)
- - [Contribution acceptance criteria](#contribution-acceptance-criteria)
-- [Definition of done](#definition-of-done)
-- [Style guides](#style-guides)
-
-<!-- END doctoc generated TOC please keep comment here to allow auto update -->
-
----
-
## Contributing Documentation has been moved
As of July 2018, all the documentation for contributing to the GitLab project has been moved to a new location.
@@ -92,7 +50,7 @@ This [documentation](doc/development/contributing/index.md) has been moved.
## Workflow labels
-This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
### Type labels
@@ -170,7 +128,6 @@ This [documentation](doc/development/contributing/merge_request_workflow.md) has
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
-
### Contribution acceptance criteria
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 0eed1a29efd..cd99d386a8d 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.12.0
+1.14.0 \ No newline at end of file
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 3a3cd8cc8b0..88c5fb891dc 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.3.1
+1.4.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index ae9a76b9249..da156181014 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.0.0
+8.1.0 \ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 8d5fd8bbbcb..943b0260dcb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -16,7 +16,7 @@ gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for'
# Supported DBs
gem 'mysql2', '~> 0.4.10', group: :mysql
-gem 'pg', '~> 0.18.2', group: :postgres
+gem 'pg', '~> 1.1', group: :postgres
gem 'rugged', '~> 0.27'
gem 'grape-path-helpers', '~> 1.0'
@@ -112,7 +112,7 @@ gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing
gem 'html-pipeline', '~> 2.8'
-gem 'deckar01-task_list', '2.0.0'
+gem 'deckar01-task_list', '2.0.1'
gem 'gitlab-markup', '~> 1.6.5'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'redcarpet', '~> 3.4'
@@ -125,9 +125,9 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.8'
gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 3.1'
-gem 'truncato', '~> 0.7.9'
+gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 2.7.0'
-gem 'nokogiri', '~> 1.8.5'
+gem 'nokogiri', '~> 1.10.1'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
@@ -160,12 +160,12 @@ gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs
gem 'sidekiq', '~> 5.2.1'
-gem 'sidekiq-cron', '~> 0.6.0'
+gem 'sidekiq-cron', '~> 1.0'
gem 'redis-namespace', '~> 1.6.0'
gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch'
# Cron Parser
-gem 'rufus-scheduler', '~> 3.4'
+gem 'fugit', '~> 1.1'
# HTTP requests
gem 'httparty', '~> 0.13.3'
@@ -224,7 +224,7 @@ gem 'asana', '~> 0.8.1'
gem 'ruby-fogbugz', '~> 0.2.1'
# Kubernetes integration
-gem 'kubeclient', '~> 4.0.0'
+gem 'kubeclient', '~> 4.2.2'
# Sanitize user input
gem 'sanitize', '~> 4.6'
@@ -304,6 +304,12 @@ group :metrics do
gem 'raindrops', '~> 0.18'
end
+group :tracing do
+ # OpenTracing
+ gem 'opentracing', '~> 0.4.3'
+ gem 'jaeger-client', '~> 0.10.0'
+end
+
group :development do
gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 4.2', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 4dda0bcad18..1c8222eb634 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -108,7 +108,7 @@ GEM
capybara-screenshot (1.0.22)
capybara (>= 1.0, < 4)
launchy
- carrierwave (1.3.0)
+ carrierwave (1.3.1)
activemodel (>= 4.0.0)
activesupport (>= 4.0.0)
mime-types (>= 1.16)
@@ -143,7 +143,7 @@ GEM
database_cleaner (1.7.0)
debug_inspector (0.0.3)
debugger-ruby_core_source (1.3.8)
- deckar01-task_list (2.0.0)
+ deckar01-task_list (2.0.1)
html-pipeline
declarative (0.0.10)
declarative-option (0.1.0)
@@ -185,7 +185,7 @@ GEM
erubi (1.7.1)
erubis (2.7.0)
escape_utils (1.2.1)
- et-orbi (1.0.3)
+ et-orbi (1.1.7)
tzinfo
eventmachine (1.2.7)
excon (0.62.0)
@@ -206,7 +206,7 @@ GEM
fast_blank (1.0.0)
fast_gettext (1.6.0)
ffaker (2.10.0)
- ffi (1.9.25)
+ ffi (1.10.0)
flipper (0.13.0)
flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6)
@@ -258,6 +258,9 @@ GEM
foreman (0.84.0)
thor (~> 0.19.1)
formatador (0.2.5)
+ fugit (1.1.7)
+ et-orbi (~> 1.1, >= 1.1.7)
+ raabro (~> 1.1)
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
@@ -282,7 +285,7 @@ GEM
gitlab-markup (1.6.5)
gitlab-sidekiq-fetcher (0.4.0)
sidekiq (~> 5)
- gitlab-styles (2.4.1)
+ gitlab-styles (2.5.1)
rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19)
@@ -389,6 +392,9 @@ GEM
cause
json
ipaddress (0.8.3)
+ jaeger-client (0.10.0)
+ opentracing (~> 0.3)
+ thrift
jira-ruby (1.4.1)
activesupport
multipart-post
@@ -419,7 +425,7 @@ GEM
kgio (2.10.0)
knapsack (1.17.0)
rake
- kubeclient (4.0.0)
+ kubeclient (4.2.2)
http (~> 3.0)
recursive-open-struct (~> 1.0, >= 1.0.4)
rest-client (~> 2.0)
@@ -462,9 +468,9 @@ GEM
mimemagic (0.3.2)
mini_magick (4.8.0)
mini_mime (1.0.1)
- mini_portile2 (2.3.0)
+ mini_portile2 (2.4.0)
minitest (5.11.3)
- msgpack (1.2.4)
+ msgpack (1.2.6)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
@@ -477,8 +483,8 @@ GEM
net-ssh (5.0.1)
netrc (0.11.0)
nio4r (2.3.1)
- nokogiri (1.8.5)
- mini_portile2 (~> 2.3.0)
+ nokogiri (1.10.1)
+ mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0)
nokogiri
numerizer (0.1.1)
@@ -544,6 +550,8 @@ GEM
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0)
+ opentracing (0.4.3)
+ optimist (3.0.0)
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
@@ -575,7 +583,7 @@ GEM
atomic (>= 1.0.0)
peek
redis
- pg (0.18.4)
+ pg (1.1.3)
po_to_json (1.0.1)
json (>= 1.6.0)
powerpack (0.1.1)
@@ -606,6 +614,7 @@ GEM
get_process_mem (~> 0.2)
puma (>= 2.7, < 4)
pyu-ruby-sasl (0.0.3.3)
+ raabro (1.1.6)
rack (2.0.6)
rack-accept (0.4.5)
rack (>= 0.4)
@@ -664,10 +673,10 @@ GEM
ffi (>= 0.5.0, < 2)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
- rbtrace (0.4.10)
+ rbtrace (0.4.11)
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
- trollop (>= 1.16.2)
+ optimist (>= 3.0.0)
rdoc (6.0.4)
re2 (1.1.1)
recaptcha (3.0.0)
@@ -775,8 +784,6 @@ GEM
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.2)
- rufus-scheduler (3.4.0)
- et-orbi (~> 1.0)
rugged (0.27.5)
safe_yaml (1.0.4)
sanitize (4.6.6)
@@ -820,8 +827,8 @@ GEM
connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
- sidekiq-cron (0.6.0)
- rufus-scheduler (>= 3.3.0)
+ sidekiq-cron (1.0.4)
+ fugit (~> 1.1)
sidekiq (>= 4.2.1)
signet (0.11.0)
addressable (~> 2.3)
@@ -868,6 +875,7 @@ GEM
rack (>= 1, < 3)
thor (0.19.4)
thread_safe (0.3.6)
+ thrift (0.11.0.0)
tilt (2.0.8)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
@@ -875,10 +883,9 @@ GEM
parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
- trollop (2.1.3)
- truncato (0.7.10)
+ truncato (0.7.11)
htmlentities (~> 4.3.1)
- nokogiri (~> 1.8.0, >= 1.7.0)
+ nokogiri (>= 1.7.0, <= 2.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
u2f (0.2.1)
@@ -974,7 +981,7 @@ DEPENDENCIES
connection_pool (~> 2.0)
creole (~> 0.5.0)
database_cleaner (~> 1.7.0)
- deckar01-task_list (= 2.0.0)
+ deckar01-task_list (= 2.0.1)
device_detector
devise (~> 4.4)
devise-two-factor (~> 3.0.0)
@@ -1003,6 +1010,7 @@ DEPENDENCIES
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
foreman (~> 0.84.0)
+ fugit (~> 1.1)
fuubar (~> 2.2.0)
gemojione (~> 3.3)
gettext (~> 3.2.2)
@@ -1037,6 +1045,7 @@ DEPENDENCIES
httparty (~> 0.13.3)
icalendar
influxdb (~> 0.2)
+ jaeger-client (~> 0.10.0)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
js_regex (~> 2.2.1)
@@ -1044,7 +1053,7 @@ DEPENDENCIES
jwt (~> 2.1.0)
kaminari (~> 1.0)
knapsack (~> 1.17)
- kubeclient (~> 4.0.0)
+ kubeclient (~> 4.2.2)
letter_opener_web (~> 1.3.0)
license_finder (~> 5.4)
licensee (~> 8.9)
@@ -1059,7 +1068,7 @@ DEPENDENCIES
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ssh (~> 5.0)
- nokogiri (~> 1.8.5)
+ nokogiri (~> 1.10.1)
oauth2 (~> 1.4)
octokit (~> 4.9)
omniauth (~> 1.8)
@@ -1077,6 +1086,7 @@ DEPENDENCIES
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0)
+ opentracing (~> 0.4.3)
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
@@ -1084,7 +1094,7 @@ DEPENDENCIES
peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0)
- pg (~> 0.18.2)
+ pg (~> 1.1)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.4)
pry-byebug (~> 3.5.1)
@@ -1127,7 +1137,6 @@ DEPENDENCIES
ruby-prof (~> 0.17.0)
ruby-progressbar
ruby_parser (~> 3.8)
- rufus-scheduler (~> 3.4)
rugged (~> 0.27)
sanitize (~> 4.6)
sass (~> 3.5)
@@ -1141,7 +1150,7 @@ DEPENDENCIES
sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2)
sidekiq (~> 5.2.1)
- sidekiq-cron (~> 0.6.0)
+ sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2)
simplecov (~> 0.14.0)
slack-notifier (~> 1.5.1)
@@ -1156,7 +1165,7 @@ DEPENDENCIES
thin (~> 1.7.0)
timecop (~> 0.8.0)
toml-rb (~> 1.0.0)
- truncato (~> 0.7.9)
+ truncato (~> 0.7.11)
u2f (~> 0.2.1)
uglifier (~> 2.7.2)
unf (~> 0.1.4)
diff --git a/PROCESS.md b/PROCESS.md
index 5aafbd33daf..7fdac098807 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -56,7 +56,7 @@ Below we describe the contributing process to GitLab for two reasons:
Several people from the [GitLab team][team] are helping community members to get
their contributions accepted by meeting our [Definition of done][done].
-What you can expect from them is described at https://about.gitlab.com/roles/merge-request-coach/.
+What you can expect from them is described at https://about.gitlab.com/job-families/expert/merge-request-coach/.
### Milestones on community contribution issues
@@ -86,10 +86,13 @@ star, smile, etc.). Some good tips about code reviews can be found in our
## Feature freeze on the 7th for the release on the 22nd
-After 7th at 23:59 (Pacific Time Zone) of each month, RC1 of the upcoming
-release (to be shipped on the 22nd) is created and deployed to GitLab.com and
-the stable branch for this release is frozen, which means master is no longer
-merged into it. Merge requests may still be merged into master during this
+After 7th at 23:59 (Pacific Time Zone) of each month, stable branch and RC1
+of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com.
+The stable branch is frozen at the most recent "qualifying commit" on master.
+A "qualifying commit" is one that is pushed before the feature freeze cutoff time
+and that passes all CI jobs (green pipeline).
+
+Merge requests may still be merged into master during this
period, but they will go into the _next_ release, unless they are manually
cherry-picked into the stable branch.
diff --git a/VERSION b/VERSION
index 74f35bff618..106400c03a2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.7.0-pre
+11.8.0-pre
diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue
index 9051be1e102..cad5611c8c5 100644
--- a/app/assets/javascripts/badges/components/badge_list_row.vue
+++ b/app/assets/javascripts/badges/components/badge_list_row.vue
@@ -55,7 +55,7 @@ export default {
:disabled="badge.isDeleting"
class="btn btn-default append-right-8"
type="button"
- @click="editBadge(badge);"
+ @click="editBadge(badge)"
>
<icon :size="16" :aria-label="__('Edit')" name="pencil" />
</button>
@@ -65,7 +65,7 @@ export default {
type="button"
data-toggle="modal"
data-target="#delete-badge-modal"
- @click="updateBadgeInModal(badge);"
+ @click="updateBadgeInModal(badge)"
>
<icon :size="16" :aria-label="__('Delete')" name="remove" />
</button>
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
index fe02096d903..947d019c725 100644
--- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
@@ -1,320 +1,8 @@
-/* eslint-disable object-shorthand, no-unused-vars, no-use-before-define, no-restricted-syntax, guard-for-in, no-continue */
-
import $ from 'jquery';
-import _ from 'underscore';
-import { insertText, getSelectedFragment, nodeMatchesSelector } from '~/lib/utils/common_utils';
-import { placeholderImage } from '~/lazy_loader';
-
-const gfmRules = {
- // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
- // GitLab Flavored Markdown (GFM) to HTML.
- // These handlers consequently convert that same HTML to GFM to be copied to the clipboard.
- // Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML
- // from GFM should have a handler here, in reverse order.
- // The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
- InlineDiffFilter: {
- 'span.idiff.addition'(el, text) {
- return `{+${text}+}`;
- },
- 'span.idiff.deletion'(el, text) {
- return `{-${text}-}`;
- },
- },
- TaskListFilter: {
- 'input[type=checkbox].task-list-item-checkbox'(el) {
- return `[${el.checked ? 'x' : ' '}]`;
- },
- },
- ReferenceFilter: {
- '.tooltip'(el) {
- return '';
- },
- 'a.gfm:not([data-link=true])'(el, text) {
- return el.dataset.original || text;
- },
- },
- AutolinkFilter: {
- a(el, text) {
- // Fallback on the regular MarkdownFilter's `a` handler.
- if (text !== el.getAttribute('href')) return false;
-
- return text;
- },
- },
- TableOfContentsFilter: {
- 'ul.section-nav'(el) {
- return '[[_TOC_]]';
- },
- },
- EmojiFilter: {
- 'img.emoji'(el) {
- return el.getAttribute('alt');
- },
- 'gl-emoji'(el) {
- return `:${el.getAttribute('data-name')}:`;
- },
- },
- ImageLinkFilter: {
- 'a.no-attachment-icon'(el, text) {
- return text;
- },
- },
- ImageLazyLoadFilter: {
- img(el, text) {
- return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
- },
- },
- VideoLinkFilter: {
- '.video-container'(el) {
- const videoEl = el.querySelector('video');
- if (!videoEl) return false;
-
- return CopyAsGFM.nodeToGFM(videoEl);
- },
- video(el) {
- return `![${el.dataset.title}](${el.getAttribute('src')})`;
- },
- },
- MermaidFilter: {
- 'svg.mermaid'(el, text) {
- const sourceEl = el.querySelector('text.source');
- if (!sourceEl) return false;
-
- return `\`\`\`mermaid\n${CopyAsGFM.nodeToGFM(sourceEl)}\n\`\`\``;
- },
- 'svg.mermaid style, svg.mermaid g'(el, text) {
- // We don't want to include the content of these elements in the copied text.
- return '';
- },
- },
- MathFilter: {
- 'pre.code.math[data-math-style=display]'(el, text) {
- return `\`\`\`math\n${text.trim()}\n\`\`\``;
- },
- 'code.code.math[data-math-style=inline]'(el, text) {
- return `$\`${text}\`$`;
- },
- 'span.katex-display span.katex-mathml'(el) {
- const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
- if (!mathAnnotation) return false;
-
- return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
- },
- 'span.katex-mathml'(el) {
- const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
- if (!mathAnnotation) return false;
-
- return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
- },
- 'span.katex-html'(el) {
- // We don't want to include the content of this element in the copied text.
- return '';
- },
- 'annotation[encoding="application/x-tex"]'(el, text) {
- return text.trim();
- },
- },
- SanitizationFilter: {
- 'a[name]:not([href]):empty'(el) {
- return el.outerHTML;
- },
- dl(el, text) {
- let lines = text
- .replace(/\n\n/g, '\n')
- .trim()
- .split('\n');
- // Add two spaces to the front of subsequent list items lines,
- // or leave the line entirely blank.
- lines = lines.map(l => {
- const line = l.trim();
- if (line.length === 0) return '';
-
- return ` ${line}`;
- });
-
- return `<dl>\n${lines.join('\n')}\n</dl>\n`;
- },
- 'dt, dd, summary, details'(el, text) {
- const tag = el.nodeName.toLowerCase();
- return `<${tag}>${text}</${tag}>\n`;
- },
- 'sup, sub, kbd, q, samp, var, ruby, rt, rp, abbr'(el, text) {
- const tag = el.nodeName.toLowerCase();
- return `<${tag}>${text}</${tag}>`;
- },
- },
- SyntaxHighlightFilter: {
- 'pre.code.highlight'(el, t) {
- const text = t.trimRight();
-
- let lang = el.getAttribute('lang');
- if (!lang || lang === 'plaintext') {
- lang = '';
- }
-
- // Prefixes lines with 4 spaces if the code contains triple backticks
- if (lang === '' && text.match(/^```/gm)) {
- return text
- .split('\n')
- .map(l => {
- const line = l.trim();
- if (line.length === 0) return '';
-
- return ` ${line}`;
- })
- .join('\n');
- }
-
- return `\`\`\`${lang}\n${text}\n\`\`\``;
- },
- 'pre > code'(el, text) {
- // Don't wrap code blocks in ``
- return text;
- },
- },
- MarkdownFilter: {
- br(el) {
- // Two spaces at the end of a line are turned into a BR
- return ' ';
- },
- code(el, text) {
- let backtickCount = 1;
- const backtickMatch = text.match(/`+/);
- if (backtickMatch) {
- backtickCount = backtickMatch[0].length + 1;
- }
-
- const backticks = Array(backtickCount + 1).join('`');
- const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
-
- return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
- },
- blockquote(el, text) {
- return text
- .trim()
- .split('\n')
- .map(s => `> ${s}`.trim())
- .join('\n');
- },
- img(el) {
- const imageSrc = el.src;
- const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : el.dataset.src || '';
- return `![${el.getAttribute('alt')}](${imageUrl})`;
- },
- 'a.anchor'(el, text) {
- // Don't render a Markdown link for the anchor link inside a heading
- return text;
- },
- a(el, text) {
- return `[${text}](${el.getAttribute('href')})`;
- },
- li(el, text) {
- const lines = text.trim().split('\n');
- const firstLine = `- ${lines.shift()}`;
- // Add four spaces to the front of subsequent list items lines,
- // or leave the line entirely blank.
- const nextLines = lines.map(s => {
- if (s.trim().length === 0) return '';
-
- return ` ${s}`;
- });
-
- return `${firstLine}\n${nextLines.join('\n')}`;
- },
- ul(el, text) {
- return text;
- },
- ol(el, text) {
- // LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists.
- return text.replace(/^- /gm, '1. ');
- },
- h1(el, text) {
- return `# ${text.trim()}\n`;
- },
- h2(el, text) {
- return `## ${text.trim()}\n`;
- },
- h3(el, text) {
- return `### ${text.trim()}\n`;
- },
- h4(el, text) {
- return `#### ${text.trim()}\n`;
- },
- h5(el, text) {
- return `##### ${text.trim()}\n`;
- },
- h6(el, text) {
- return `###### ${text.trim()}\n`;
- },
- strong(el, text) {
- return `**${text}**`;
- },
- em(el, text) {
- return `_${text}_`;
- },
- del(el, text) {
- return `~~${text}~~`;
- },
- hr(el) {
- // extra leading \n is to ensure that there is a blank line between
- // a list followed by an hr, otherwise this breaks old redcarpet rendering
- return '\n-----\n';
- },
- p(el, text) {
- return `${text.trim()}\n`;
- },
- table(el) {
- const theadEl = el.querySelector('thead');
- const tbodyEl = el.querySelector('tbody');
- if (!theadEl || !tbodyEl) return false;
-
- const theadText = CopyAsGFM.nodeToGFM(theadEl);
- const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
-
- return [theadText, tbodyText].join('\n');
- },
- thead(el, text) {
- const cells = _.map(el.querySelectorAll('th'), cell => {
- let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
-
- let before = '';
- let after = '';
- const alignment = cell.align || cell.style.textAlign;
-
- switch (alignment) {
- case 'center':
- before = ':';
- after = ':';
- chars -= 2;
- break;
- case 'right':
- after = ':';
- chars -= 1;
- break;
- default:
- break;
- }
-
- chars = Math.max(chars, 3);
-
- const middle = Array(chars + 1).join('-');
-
- return before + middle + after;
- });
-
- const separatorRow = `|${cells.join('|')}|`;
-
- return [text, separatorRow].join('\n');
- },
- tr(el) {
- const cellEls = el.querySelectorAll('td, th');
- if (cellEls.length === 0) return false;
-
- const cells = _.map(cellEls, cell => CopyAsGFM.nodeToGFM(cell));
- return `| ${cells.join(' | ')} |`;
- },
- },
-};
+import { DOMParser } from 'prosemirror-model';
+import { getSelectedFragment } from '~/lib/utils/common_utils';
+import schema from './schema';
+import markdownSerializer from './serializer';
export class CopyAsGFM {
constructor() {
@@ -347,8 +35,13 @@ export class CopyAsGFM {
e.preventDefault();
e.stopPropagation();
+ const div = document.createElement('div');
+ div.appendChild(el.cloneNode(true));
+ const html = div.innerHTML;
+
clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', this.nodeToGFM(el));
+ clipboardData.setData('text/html', html);
}
static pasteGFM(e) {
@@ -361,7 +54,7 @@ export class CopyAsGFM {
e.preventDefault();
- window.gl.utils.insertText(e.target, (textBefore, textAfter) => {
+ window.gl.utils.insertText(e.target, textBefore => {
// If the text before the cursor contains an odd number of backticks,
// we are either inside an inline code span that starts with 1 backtick
// or a code block that starts with 3 backticks.
@@ -443,75 +136,12 @@ export class CopyAsGFM {
return codeElement;
}
- static nodeToGFM(node, respectWhitespaceParam = false) {
- if (node.nodeType === Node.COMMENT_NODE) {
- return '';
- }
-
- if (node.nodeType === Node.TEXT_NODE) {
- return node.textContent;
- }
-
- const respectWhitespace =
- respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
-
- const text = this.innerGFM(node, respectWhitespace);
-
- if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
- return text;
- }
-
- for (const filter in gfmRules) {
- const rules = gfmRules[filter];
-
- for (const selector in rules) {
- const func = rules[selector];
-
- if (!nodeMatchesSelector(node, selector)) continue;
-
- let result;
- if (func.length === 2) {
- // if `func` takes 2 arguments, it depends on text.
- // if there is no text, we don't need to generate GFM for this node.
- if (text.length === 0) continue;
-
- result = func(node, text);
- } else {
- result = func(node);
- }
-
- if (result === false) continue;
-
- return result;
- }
- }
-
- return text;
- }
-
- static innerGFM(parentNode, respectWhitespace = false) {
- const nodes = parentNode.childNodes;
-
- const clonedParentNode = parentNode.cloneNode(true);
- const clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0);
-
- for (let i = 0; i < nodes.length; i += 1) {
- const node = nodes[i];
- const clonedNode = clonedNodes[i];
-
- const text = this.nodeToGFM(node, respectWhitespace);
-
- // `clonedNode.replaceWith(text)` is not yet widely supported
- clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
- }
-
- let nodeText = clonedParentNode.innerText || clonedParentNode.textContent;
-
- if (!respectWhitespace) {
- nodeText = nodeText.trim();
- }
+ static nodeToGFM(node) {
+ const wrapEl = document.createElement('div');
+ wrapEl.appendChild(node.cloneNode(true));
+ const doc = DOMParser.fromSchema(schema).parse(wrapEl);
- return nodeText;
+ return markdownSerializer.serialize(doc);
}
}
diff --git a/app/assets/javascripts/behaviors/markdown/editor_extensions.js b/app/assets/javascripts/behaviors/markdown/editor_extensions.js
new file mode 100644
index 00000000000..47e5fc65c48
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/editor_extensions.js
@@ -0,0 +1,106 @@
+import Doc from './nodes/doc';
+import Paragraph from './nodes/paragraph';
+import Text from './nodes/text';
+
+import Blockquote from './nodes/blockquote';
+import CodeBlock from './nodes/code_block';
+import HardBreak from './nodes/hard_break';
+import Heading from './nodes/heading';
+import HorizontalRule from './nodes/horizontal_rule';
+import Image from './nodes/image';
+
+import Table from './nodes/table';
+import TableHead from './nodes/table_head';
+import TableBody from './nodes/table_body';
+import TableHeaderRow from './nodes/table_header_row';
+import TableRow from './nodes/table_row';
+import TableCell from './nodes/table_cell';
+
+import Emoji from './nodes/emoji';
+import Reference from './nodes/reference';
+
+import TableOfContents from './nodes/table_of_contents';
+import Video from './nodes/video';
+
+import BulletList from './nodes/bullet_list';
+import OrderedList from './nodes/ordered_list';
+import ListItem from './nodes/list_item';
+
+import DescriptionList from './nodes/description_list';
+import DescriptionTerm from './nodes/description_term';
+import DescriptionDetails from './nodes/description_details';
+
+import TaskList from './nodes/task_list';
+import OrderedTaskList from './nodes/ordered_task_list';
+import TaskListItem from './nodes/task_list_item';
+
+import Summary from './nodes/summary';
+import Details from './nodes/details';
+
+import Bold from './marks/bold';
+import Italic from './marks/italic';
+import Strike from './marks/strike';
+import InlineDiff from './marks/inline_diff';
+
+import Link from './marks/link';
+import Code from './marks/code';
+import MathMark from './marks/math';
+import InlineHTML from './marks/inline_html';
+
+// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb transform
+// GitLab Flavored Markdown (GFM) to HTML.
+// The nodes and marks referenced here transform that same HTML to GFM to be copied to the clipboard.
+// Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML
+// from GFM should have a node or mark here.
+// The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
+
+export default [
+ new Doc(),
+ new Paragraph(),
+ new Text(),
+
+ new Blockquote(),
+ new CodeBlock(),
+ new HardBreak(),
+ new Heading({ maxLevel: 6 }),
+ new HorizontalRule(),
+ new Image(),
+
+ new Table(),
+ new TableHead(),
+ new TableBody(),
+ new TableHeaderRow(),
+ new TableRow(),
+ new TableCell(),
+
+ new Emoji(),
+ new Reference(),
+
+ new TableOfContents(),
+ new Video(),
+
+ new BulletList(),
+ new OrderedList(),
+ new ListItem(),
+
+ new DescriptionList(),
+ new DescriptionTerm(),
+ new DescriptionDetails(),
+
+ new TaskList(),
+ new OrderedTaskList(),
+ new TaskListItem(),
+
+ new Summary(),
+ new Details(),
+
+ new Bold(),
+ new Italic(),
+ new Strike(),
+ new InlineDiff(),
+
+ new Link(),
+ new Code(),
+ new MathMark(),
+ new InlineHTML(),
+];
diff --git a/app/assets/javascripts/behaviors/markdown/marks/bold.js b/app/assets/javascripts/behaviors/markdown/marks/bold.js
new file mode 100644
index 00000000000..b537954c1cb
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/bold.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { Bold as BaseBold } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Bold extends BaseBold {
+ get toMarkdown() {
+ return defaultMarkdownSerializer.marks.strong;
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/code.js b/app/assets/javascripts/behaviors/markdown/marks/code.js
new file mode 100644
index 00000000000..a760ee80dd0
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/code.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { Code as BaseCode } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Code extends BaseCode {
+ get toMarkdown() {
+ return defaultMarkdownSerializer.marks.code;
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/inline_diff.js b/app/assets/javascripts/behaviors/markdown/marks/inline_diff.js
new file mode 100644
index 00000000000..ce425e80cd3
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/inline_diff.js
@@ -0,0 +1,41 @@
+/* eslint-disable class-methods-use-this */
+
+import { Mark } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::InlineDiffFilter
+export default class InlineDiff extends Mark {
+ get name() {
+ return 'inline_diff';
+ }
+
+ get schema() {
+ return {
+ attrs: {
+ addition: {
+ default: true,
+ },
+ },
+ parseDOM: [
+ { tag: 'span.idiff.addition', attrs: { addition: true } },
+ { tag: 'span.idiff.deletion', attrs: { addition: false } },
+ ],
+ toDOM: node => [
+ 'span',
+ { class: `idiff left right ${node.attrs.addition ? 'addition' : 'deletion'}` },
+ 0,
+ ],
+ };
+ }
+
+ get toMarkdown() {
+ return {
+ mixable: true,
+ open(state, mark) {
+ return mark.attrs.addition ? '{+' : '{-';
+ },
+ close(state, mark) {
+ return mark.attrs.addition ? '+}' : '-}';
+ },
+ };
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/inline_html.js b/app/assets/javascripts/behaviors/markdown/marks/inline_html.js
new file mode 100644
index 00000000000..ebed8698e21
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/inline_html.js
@@ -0,0 +1,46 @@
+/* eslint-disable class-methods-use-this */
+
+import { Mark } from 'tiptap';
+import _ from 'underscore';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class InlineHTML extends Mark {
+ get name() {
+ return 'inline_html';
+ }
+
+ get schema() {
+ return {
+ excludes: '',
+ attrs: {
+ tag: {},
+ title: { default: null },
+ },
+ parseDOM: [
+ {
+ tag: 'sup, sub, kbd, q, samp, var',
+ getAttrs: el => ({ tag: el.nodeName.toLowerCase() }),
+ },
+ {
+ tag: 'abbr',
+ getAttrs: el => ({ tag: 'abbr', title: el.getAttribute('title') }),
+ },
+ ],
+ toDOM: node => [node.attrs.tag, { title: node.attrs.title }, 0],
+ };
+ }
+
+ get toMarkdown() {
+ return {
+ mixable: true,
+ open(state, mark) {
+ return `<${mark.attrs.tag}${
+ mark.attrs.title ? ` title="${state.esc(_.escape(mark.attrs.title))}"` : ''
+ }>`;
+ },
+ close(state, mark) {
+ return `</${mark.attrs.tag}>`;
+ },
+ };
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/italic.js b/app/assets/javascripts/behaviors/markdown/marks/italic.js
new file mode 100644
index 00000000000..44b35c97739
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/italic.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { Italic as BaseItalic } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Italic extends BaseItalic {
+ get toMarkdown() {
+ return defaultMarkdownSerializer.marks.em;
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/link.js b/app/assets/javascripts/behaviors/markdown/marks/link.js
new file mode 100644
index 00000000000..5c23d6a5ceb
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/link.js
@@ -0,0 +1,21 @@
+/* eslint-disable class-methods-use-this */
+
+import { Link as BaseLink } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Link extends BaseLink {
+ get toMarkdown() {
+ return {
+ mixable: true,
+ open(state, mark, parent, index) {
+ const open = defaultMarkdownSerializer.marks.link.open(state, mark, parent, index);
+ return open === '<' ? '' : open;
+ },
+ close(state, mark, parent, index) {
+ const close = defaultMarkdownSerializer.marks.link.close(state, mark, parent, index);
+ return close === '>' ? '' : close;
+ },
+ };
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/math.js b/app/assets/javascripts/behaviors/markdown/marks/math.js
new file mode 100644
index 00000000000..e582fb18f15
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/math.js
@@ -0,0 +1,41 @@
+/* eslint-disable class-methods-use-this */
+
+import { Mark } from 'tiptap';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MathFilter
+export default class MathMark extends Mark {
+ get name() {
+ return 'math';
+ }
+
+ get schema() {
+ return {
+ parseDOM: [
+ // Matches HTML generated by Banzai::Filter::MathFilter
+ {
+ tag: 'code.code.math[data-math-style=inline]',
+ priority: 51,
+ },
+ // Matches HTML after being transformed by app/assets/javascripts/behaviors/markdown/render_math.js
+ {
+ tag: 'span.katex',
+ contentElement: 'annotation[encoding="application/x-tex"]',
+ },
+ ],
+ toDOM: () => ['code', { class: 'code math', 'data-math-style': 'inline' }, 0],
+ };
+ }
+
+ get toMarkdown() {
+ return {
+ escape: false,
+ open(state, mark, parent, index) {
+ return `$${defaultMarkdownSerializer.marks.code.open(state, mark, parent, index)}`;
+ },
+ close(state, mark, parent, index) {
+ return `${defaultMarkdownSerializer.marks.code.close(state, mark, parent, index)}$`;
+ },
+ };
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/marks/strike.js b/app/assets/javascripts/behaviors/markdown/marks/strike.js
new file mode 100644
index 00000000000..c2951a40a4b
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/marks/strike.js
@@ -0,0 +1,15 @@
+/* eslint-disable class-methods-use-this */
+
+import { Strike as BaseStrike } from 'tiptap-extensions';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Strike extends BaseStrike {
+ get toMarkdown() {
+ return {
+ open: '~~',
+ close: '~~',
+ mixable: true,
+ expelEnclosingWhitespace: true,
+ };
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js b/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js
new file mode 100644
index 00000000000..b0bc8f79643
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/blockquote.js
@@ -0,0 +1,13 @@
+/* eslint-disable class-methods-use-this */
+
+import { Blockquote as BaseBlockquote } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Blockquote extends BaseBlockquote {
+ toMarkdown(state, node) {
+ if (!node.childCount) return;
+
+ defaultMarkdownSerializer.nodes.blockquote(state, node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js b/app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js
new file mode 100644
index 00000000000..3b0792e1af8
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/bullet_list.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { BulletList as BaseBulletList } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class BulletList extends BaseBulletList {
+ toMarkdown(state, node) {
+ defaultMarkdownSerializer.nodes.bullet_list(state, node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/code_block.js b/app/assets/javascripts/behaviors/markdown/nodes/code_block.js
new file mode 100644
index 00000000000..b9b894b3348
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/code_block.js
@@ -0,0 +1,79 @@
+/* eslint-disable class-methods-use-this */
+
+import { CodeBlock as BaseCodeBlock } from 'tiptap-extensions';
+
+const PLAINTEXT_LANG = 'plaintext';
+
+// Transforms generated HTML back to GFM for:
+// - Banzai::Filter::SyntaxHighlightFilter
+// - Banzai::Filter::MathFilter
+// - Banzai::Filter::MermaidFilter
+export default class CodeBlock extends BaseCodeBlock {
+ get schema() {
+ return {
+ content: 'text*',
+ marks: '',
+ group: 'block',
+ code: true,
+ defining: true,
+ attrs: {
+ lang: { default: PLAINTEXT_LANG },
+ },
+ parseDOM: [
+ // Matches HTML generated by Banzai::Filter::SyntaxHighlightFilter, Banzai::Filter::MathFilter or Banzai::Filter::MermaidFilter
+ {
+ tag: 'pre.code.highlight',
+ preserveWhitespace: 'full',
+ getAttrs: el => {
+ const lang = el.getAttribute('lang');
+ if (!lang || lang === '') return {};
+
+ return { lang };
+ },
+ },
+ // Matches HTML generated by Banzai::Filter::MathFilter,
+ // after being transformed by app/assets/javascripts/behaviors/markdown/render_math.js
+ {
+ tag: 'span.katex-display',
+ preserveWhitespace: 'full',
+ contentElement: 'annotation[encoding="application/x-tex"]',
+ attrs: { lang: 'math' },
+ },
+ // Matches HTML generated by Banzai::Filter::MathFilter,
+ // after being transformed by app/assets/javascripts/behaviors/markdown/render_mermaid.js
+ {
+ tag: 'svg.mermaid',
+ preserveWhitespace: 'full',
+ contentElement: 'text.source',
+ attrs: { lang: 'mermaid' },
+ },
+ ],
+ toDOM: node => ['pre', { class: 'code highlight', lang: node.attrs.lang }, ['code', 0]],
+ };
+ }
+
+ toMarkdown(state, node) {
+ if (!node.childCount) return;
+
+ const {
+ textContent: text,
+ attrs: { lang },
+ } = node;
+
+ // Prefixes lines with 4 spaces if the code contains a line that starts with triple backticks
+ if (lang === PLAINTEXT_LANG && text.match(/^```/gm)) {
+ state.wrapBlock(' ', null, node, () => state.text(text, false));
+ return;
+ }
+
+ state.write('```');
+ if (lang !== PLAINTEXT_LANG) state.write(lang);
+
+ state.ensureNewLine();
+ state.text(text, false);
+ state.ensureNewLine();
+
+ state.write('```');
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/description_details.js b/app/assets/javascripts/behaviors/markdown/nodes/description_details.js
new file mode 100644
index 00000000000..a4451d8ce8d
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/description_details.js
@@ -0,0 +1,28 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class DescriptionDetails extends Node {
+ get name() {
+ return 'description_details';
+ }
+
+ get schema() {
+ return {
+ content: 'text*',
+ marks: '',
+ defining: true,
+ parseDOM: [{ tag: 'dd' }],
+ toDOM: () => ['dd', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.flushClose(1);
+ state.write('<dd>');
+ state.text(node.textContent, false);
+ state.write('</dd>');
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/description_list.js b/app/assets/javascripts/behaviors/markdown/nodes/description_list.js
new file mode 100644
index 00000000000..6aa1aca29d7
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/description_list.js
@@ -0,0 +1,28 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class DescriptionList extends Node {
+ get name() {
+ return 'description_list';
+ }
+
+ get schema() {
+ return {
+ content: '(description_term+ description_details+)+',
+ group: 'block',
+ parseDOM: [{ tag: 'dl' }],
+ toDOM: () => ['dl', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.write('<dl>\n');
+ state.wrapBlock(' ', null, node, () => state.renderContent(node));
+ state.flushClose(1);
+ state.ensureNewLine();
+ state.write('</dl>');
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/description_term.js b/app/assets/javascripts/behaviors/markdown/nodes/description_term.js
new file mode 100644
index 00000000000..89057ec6444
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/description_term.js
@@ -0,0 +1,28 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class DescriptionTerm extends Node {
+ get name() {
+ return 'description_term';
+ }
+
+ get schema() {
+ return {
+ content: 'text*',
+ marks: '',
+ defining: true,
+ parseDOM: [{ tag: 'dt' }],
+ toDOM: () => ['dt', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.flushClose(state.closed && state.closed.type === node.type ? 1 : 2);
+ state.write('<dt>');
+ state.text(node.textContent, false);
+ state.write('</dt>');
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/details.js b/app/assets/javascripts/behaviors/markdown/nodes/details.js
new file mode 100644
index 00000000000..1c40dbb8168
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/details.js
@@ -0,0 +1,28 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Details extends Node {
+ get name() {
+ return 'details';
+ }
+
+ get schema() {
+ return {
+ content: 'summary block*',
+ group: 'block',
+ parseDOM: [{ tag: 'details' }],
+ toDOM: () => ['details', { open: true, onclick: 'return false', tabindex: '-1' }, 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.write('<details>\n');
+ state.renderContent(node);
+ state.flushClose(1);
+ state.ensureNewLine();
+ state.write('</details>');
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/doc.js b/app/assets/javascripts/behaviors/markdown/nodes/doc.js
new file mode 100644
index 00000000000..88b16fd85da
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/doc.js
@@ -0,0 +1,15 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+export default class Doc extends Node {
+ get name() {
+ return 'doc';
+ }
+
+ get schema() {
+ return {
+ content: 'block+',
+ };
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/emoji.js b/app/assets/javascripts/behaviors/markdown/nodes/emoji.js
new file mode 100644
index 00000000000..a7cc3e828f5
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/emoji.js
@@ -0,0 +1,41 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::EmojiFilter
+export default class Emoji extends Node {
+ get name() {
+ return 'emoji';
+ }
+
+ get schema() {
+ return {
+ inline: true,
+ group: 'inline',
+ attrs: {
+ name: {},
+ title: {},
+ moji: {},
+ },
+ parseDOM: [
+ {
+ tag: 'gl-emoji',
+ getAttrs: el => ({
+ name: el.dataset.name,
+ title: el.getAttribute('title'),
+ moji: el.textContent,
+ }),
+ },
+ ],
+ toDOM: node => [
+ 'gl-emoji',
+ { 'data-name': node.attrs.name, title: node.attrs.title },
+ node.attrs.moji,
+ ],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.write(`:${node.attrs.name}:`);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/hard_break.js b/app/assets/javascripts/behaviors/markdown/nodes/hard_break.js
new file mode 100644
index 00000000000..59e5d8ab3e2
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/hard_break.js
@@ -0,0 +1,10 @@
+/* eslint-disable class-methods-use-this */
+
+import { HardBreak as BaseHardBreak } from 'tiptap-extensions';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class HardBreak extends BaseHardBreak {
+ toMarkdown(state) {
+ if (!state.atBlank()) state.write(' \n');
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/heading.js b/app/assets/javascripts/behaviors/markdown/nodes/heading.js
new file mode 100644
index 00000000000..fec8608cf5d
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/heading.js
@@ -0,0 +1,13 @@
+/* eslint-disable class-methods-use-this */
+
+import { Heading as BaseHeading } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Heading extends BaseHeading {
+ toMarkdown(state, node) {
+ if (!node.childCount) return;
+
+ defaultMarkdownSerializer.nodes.heading(state, node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js b/app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js
new file mode 100644
index 00000000000..695c7160bde
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/horizontal_rule.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { HorizontalRule as BaseHorizontalRule } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class HorizontalRule extends BaseHorizontalRule {
+ toMarkdown(state, node) {
+ defaultMarkdownSerializer.nodes.horizontal_rule(state, node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/image.js b/app/assets/javascripts/behaviors/markdown/nodes/image.js
new file mode 100644
index 00000000000..c225a5ed876
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/image.js
@@ -0,0 +1,52 @@
+/* eslint-disable class-methods-use-this */
+
+import { Image as BaseImage } from 'tiptap-extensions';
+import { placeholderImage } from '~/lazy_loader';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+export default class Image extends BaseImage {
+ get schema() {
+ return {
+ attrs: {
+ src: {},
+ alt: {
+ default: null,
+ },
+ title: {
+ default: null,
+ },
+ },
+ group: 'inline',
+ inline: true,
+ draggable: true,
+ parseDOM: [
+ // Matches HTML generated by Banzai::Filter::ImageLinkFilter
+ {
+ tag: 'a.no-attachment-icon',
+ priority: 51,
+ skip: true,
+ },
+ // Matches HTML generated by Banzai::Filter::ImageLazyLoadFilter
+ {
+ tag: 'img[src]',
+ getAttrs: el => {
+ const imageSrc = el.src;
+ const imageUrl =
+ imageSrc && imageSrc !== placeholderImage ? imageSrc : el.dataset.src || '';
+
+ return {
+ src: imageUrl,
+ title: el.getAttribute('title'),
+ alt: el.getAttribute('alt'),
+ };
+ },
+ },
+ ],
+ toDOM: node => ['img', node.attrs],
+ };
+ }
+
+ toMarkdown(state, node) {
+ defaultMarkdownSerializer.nodes.image(state, node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/list_item.js b/app/assets/javascripts/behaviors/markdown/nodes/list_item.js
new file mode 100644
index 00000000000..4237637ed9a
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/list_item.js
@@ -0,0 +1,11 @@
+/* eslint-disable class-methods-use-this */
+
+import { ListItem as BaseListItem } from 'tiptap-extensions';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class ListItem extends BaseListItem {
+ toMarkdown(state, node) {
+ defaultMarkdownSerializer.nodes.list_item(state, node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js b/app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js
new file mode 100644
index 00000000000..4c1542d14ea
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/ordered_list.js
@@ -0,0 +1,10 @@
+/* eslint-disable class-methods-use-this */
+
+import { OrderedList as BaseOrderedList } from 'tiptap-extensions';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class OrderedList extends BaseOrderedList {
+ toMarkdown(state, node) {
+ state.renderList(node, ' ', () => '1. ');
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js b/app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js
new file mode 100644
index 00000000000..25c4976a1bc
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js
@@ -0,0 +1,28 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::TaskListFilter
+export default class OrderedTaskList extends Node {
+ get name() {
+ return 'ordered_task_list';
+ }
+
+ get schema() {
+ return {
+ group: 'block',
+ content: '(task_list_item|list_item)+',
+ parseDOM: [
+ {
+ priority: 51,
+ tag: 'ol.task-list',
+ },
+ ],
+ toDOM: () => ['ol', { class: 'task-list' }, 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.renderList(node, ' ', () => '1. ');
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/paragraph.js b/app/assets/javascripts/behaviors/markdown/nodes/paragraph.js
new file mode 100644
index 00000000000..dec3207b1bb
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/paragraph.js
@@ -0,0 +1,24 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Paragraph extends Node {
+ get name() {
+ return 'paragraph';
+ }
+
+ get schema() {
+ return {
+ content: 'inline*',
+ group: 'block',
+ parseDOM: [{ tag: 'p' }],
+ toDOM: () => ['p', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ defaultMarkdownSerializer.nodes.paragraph(state, node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/reference.js b/app/assets/javascripts/behaviors/markdown/nodes/reference.js
new file mode 100644
index 00000000000..5d6bbeca833
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/reference.js
@@ -0,0 +1,52 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::ReferenceFilter and subclasses
+export default class Reference extends Node {
+ get name() {
+ return 'reference';
+ }
+
+ get schema() {
+ return {
+ inline: true,
+ group: 'inline',
+ atom: true,
+ attrs: {
+ className: {},
+ referenceType: {},
+ originalText: { default: null },
+ href: {},
+ text: {},
+ },
+ parseDOM: [
+ {
+ tag: 'a.gfm:not([data-link=true])',
+ priority: 51,
+ getAttrs: el => ({
+ className: el.className,
+ referenceType: el.dataset.referenceType,
+ originalText: el.dataset.original,
+ href: el.getAttribute('href'),
+ text: el.textContent,
+ }),
+ },
+ ],
+ toDOM: node => [
+ 'a',
+ {
+ class: node.attrs.className,
+ href: node.attrs.href,
+ 'data-reference-type': node.attrs.referenceType,
+ 'data-original': node.attrs.originalText,
+ },
+ node.attrs.text,
+ ],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.write(node.attrs.originalText || node.attrs.text);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/summary.js b/app/assets/javascripts/behaviors/markdown/nodes/summary.js
new file mode 100644
index 00000000000..2e36e316d71
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/summary.js
@@ -0,0 +1,27 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Summary extends Node {
+ get name() {
+ return 'summary';
+ }
+
+ get schema() {
+ return {
+ content: 'text*',
+ marks: '',
+ defining: true,
+ parseDOM: [{ tag: 'summary' }],
+ toDOM: () => ['summary', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.write('<summary>');
+ state.text(node.textContent, false);
+ state.write('</summary>');
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table.js b/app/assets/javascripts/behaviors/markdown/nodes/table.js
new file mode 100644
index 00000000000..a7fcb9227cd
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table.js
@@ -0,0 +1,25 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class Table extends Node {
+ get name() {
+ return 'table';
+ }
+
+ get schema() {
+ return {
+ content: 'table_head table_body',
+ group: 'block',
+ isolating: true,
+ parseDOM: [{ tag: 'table' }],
+ toDOM: () => ['table', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.renderContent(node);
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_body.js b/app/assets/javascripts/behaviors/markdown/nodes/table_body.js
new file mode 100644
index 00000000000..403556dc0c8
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_body.js
@@ -0,0 +1,24 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class TableBody extends Node {
+ get name() {
+ return 'table_body';
+ }
+
+ get schema() {
+ return {
+ content: 'table_row+',
+ parseDOM: [{ tag: 'tbody' }],
+ toDOM: () => ['tbody', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.flushClose(1);
+ state.renderContent(node);
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_cell.js b/app/assets/javascripts/behaviors/markdown/nodes/table_cell.js
new file mode 100644
index 00000000000..c63bfe10e39
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_cell.js
@@ -0,0 +1,35 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class TableCell extends Node {
+ get name() {
+ return 'table_cell';
+ }
+
+ get schema() {
+ return {
+ attrs: {
+ header: { default: false },
+ align: { default: null },
+ },
+ content: 'inline*',
+ isolating: true,
+ parseDOM: [
+ {
+ tag: 'td, th',
+ getAttrs: el => ({
+ header: el.tagName === 'TH',
+ align: el.getAttribute('align') || el.style.textAlign,
+ }),
+ },
+ ],
+ toDOM: node => [node.attrs.header ? 'th' : 'td', { align: node.attrs.align }, 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.renderInline(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_head.js b/app/assets/javascripts/behaviors/markdown/nodes/table_head.js
new file mode 100644
index 00000000000..4cb94bf088c
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_head.js
@@ -0,0 +1,24 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class TableHead extends Node {
+ get name() {
+ return 'table_head';
+ }
+
+ get schema() {
+ return {
+ content: 'table_header_row',
+ parseDOM: [{ tag: 'thead' }],
+ toDOM: () => ['thead', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.flushClose(1);
+ state.renderContent(node);
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js b/app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js
new file mode 100644
index 00000000000..e7eee636402
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_header_row.js
@@ -0,0 +1,43 @@
+/* eslint-disable class-methods-use-this */
+
+import TableRow from './table_row';
+
+const CENTER_ALIGN = 'center';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class TableHeaderRow extends TableRow {
+ get name() {
+ return 'table_header_row';
+ }
+
+ get schema() {
+ return {
+ content: 'table_cell+',
+ parseDOM: [
+ {
+ tag: 'thead tr',
+ priority: 51,
+ },
+ ],
+ toDOM: () => ['tr', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ const cellWidths = super.toMarkdown(state, node);
+
+ state.flushClose(1);
+
+ state.write('|');
+ node.forEach((cell, _, i) => {
+ if (i) state.write('|');
+
+ state.write(cell.attrs.align === CENTER_ALIGN ? ':' : '-');
+ state.write(state.repeat('-', cellWidths[i]));
+ state.write(cell.attrs.align === CENTER_ALIGN || cell.attrs.align === 'right' ? ':' : '-');
+ });
+ state.write('|');
+
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js b/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js
new file mode 100644
index 00000000000..20c7fa8a9ab
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js
@@ -0,0 +1,33 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::TableOfContentsFilter
+export default class TableOfContents extends Node {
+ get name() {
+ return 'table_of_contents';
+ }
+
+ get schema() {
+ return {
+ group: 'block',
+ atom: true,
+ parseDOM: [
+ {
+ tag: 'ul.section-nav',
+ priority: 51,
+ },
+ {
+ tag: 'p.table-of-contents',
+ priority: 51,
+ },
+ ],
+ toDOM: () => ['p', { class: 'table-of-contents' }, 'Table of Contents'],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.write('[[_TOC_]]');
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/table_row.js b/app/assets/javascripts/behaviors/markdown/nodes/table_row.js
new file mode 100644
index 00000000000..5852502773a
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/table_row.js
@@ -0,0 +1,38 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::MarkdownFilter
+export default class TableRow extends Node {
+ get name() {
+ return 'table_row';
+ }
+
+ get schema() {
+ return {
+ content: 'table_cell+',
+ parseDOM: [{ tag: 'tr' }],
+ toDOM: () => ['tr', 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ const cellWidths = [];
+
+ state.flushClose(1);
+
+ state.write('| ');
+ node.forEach((cell, _, i) => {
+ if (i) state.write(' | ');
+
+ const { length } = state.out;
+ state.render(cell, node, i);
+ cellWidths.push(state.out.length - length);
+ });
+ state.write(' |');
+
+ state.closeBlock(node);
+
+ return cellWidths;
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/task_list.js b/app/assets/javascripts/behaviors/markdown/nodes/task_list.js
new file mode 100644
index 00000000000..ab33bc21502
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/task_list.js
@@ -0,0 +1,28 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::TaskListFilter
+export default class TaskList extends Node {
+ get name() {
+ return 'task_list';
+ }
+
+ get schema() {
+ return {
+ group: 'block',
+ content: '(task_list_item|list_item)+',
+ parseDOM: [
+ {
+ priority: 51,
+ tag: 'ul.task-list',
+ },
+ ],
+ toDOM: () => ['ul', { class: 'task-list' }, 0],
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.renderList(node, ' ', () => '* ');
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js b/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js
new file mode 100644
index 00000000000..d0ee7333d5e
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js
@@ -0,0 +1,49 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::TaskListFilter
+export default class TaskListItem extends Node {
+ get name() {
+ return 'task_list_item';
+ }
+
+ get schema() {
+ return {
+ attrs: {
+ done: {
+ default: false,
+ },
+ },
+ defining: true,
+ draggable: false,
+ content: 'paragraph block*',
+ parseDOM: [
+ {
+ priority: 51,
+ tag: 'li.task-list-item',
+ getAttrs: el => {
+ const checkbox = el.querySelector('input[type=checkbox].task-list-item-checkbox');
+ return { done: checkbox && checkbox.checked };
+ },
+ },
+ ],
+ toDOM(node) {
+ return [
+ 'li',
+ { class: 'task-list-item' },
+ [
+ 'input',
+ { type: 'checkbox', class: 'task-list-item-checkbox', checked: node.attrs.done },
+ ],
+ ['div', { class: 'todo-content' }, 0],
+ ];
+ },
+ };
+ }
+
+ toMarkdown(state, node) {
+ state.write(`[${node.attrs.done ? 'x' : ' '}] `);
+ state.renderContent(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/text.js b/app/assets/javascripts/behaviors/markdown/nodes/text.js
new file mode 100644
index 00000000000..84838c14999
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/text.js
@@ -0,0 +1,20 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+export default class Text extends Node {
+ get name() {
+ return 'text';
+ }
+
+ get schema() {
+ return {
+ group: 'inline',
+ };
+ }
+
+ toMarkdown(state, node) {
+ defaultMarkdownSerializer.nodes.text(state, node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/nodes/video.js b/app/assets/javascripts/behaviors/markdown/nodes/video.js
new file mode 100644
index 00000000000..516f983397d
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/nodes/video.js
@@ -0,0 +1,54 @@
+/* eslint-disable class-methods-use-this */
+
+import { Node } from 'tiptap';
+import { defaultMarkdownSerializer } from 'prosemirror-markdown';
+
+// Transforms generated HTML back to GFM for Banzai::Filter::VideoLinkFilter
+export default class Video extends Node {
+ get name() {
+ return 'video';
+ }
+
+ get schema() {
+ return {
+ attrs: {
+ src: {},
+ alt: {
+ default: null,
+ },
+ },
+ group: 'block',
+ draggable: true,
+ parseDOM: [
+ {
+ tag: '.video-container',
+ skip: true,
+ },
+ {
+ tag: '.video-container p',
+ priority: 51,
+ ignore: true,
+ },
+ {
+ tag: 'video[src]',
+ getAttrs: el => ({ src: el.getAttribute('src'), alt: el.dataset.title }),
+ },
+ ],
+ toDOM: node => [
+ 'video',
+ {
+ src: node.attrs.src,
+ width: '400',
+ controls: true,
+ 'data-setup': '{}',
+ 'data-title': node.attrs.alt,
+ },
+ ],
+ };
+ }
+
+ toMarkdown(state, node) {
+ defaultMarkdownSerializer.nodes.image(state, node);
+ state.closeBlock(node);
+ }
+}
diff --git a/app/assets/javascripts/behaviors/markdown/schema.js b/app/assets/javascripts/behaviors/markdown/schema.js
new file mode 100644
index 00000000000..163182ab778
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/schema.js
@@ -0,0 +1,24 @@
+import { Schema } from 'prosemirror-model';
+import editorExtensions from './editor_extensions';
+
+const nodes = editorExtensions
+ .filter(extension => extension.type === 'node')
+ .reduce(
+ (ns, { name, schema }) => ({
+ ...ns,
+ [name]: schema,
+ }),
+ {},
+ );
+
+const marks = editorExtensions
+ .filter(extension => extension.type === 'mark')
+ .reduce(
+ (ms, { name, schema }) => ({
+ ...ms,
+ [name]: schema,
+ }),
+ {},
+ );
+
+export default new Schema({ nodes, marks });
diff --git a/app/assets/javascripts/behaviors/markdown/serializer.js b/app/assets/javascripts/behaviors/markdown/serializer.js
new file mode 100644
index 00000000000..70dbd8bd206
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/serializer.js
@@ -0,0 +1,24 @@
+import { MarkdownSerializer } from 'prosemirror-markdown';
+import editorExtensions from './editor_extensions';
+
+const nodes = editorExtensions
+ .filter(extension => extension.type === 'node')
+ .reduce(
+ (ns, { name, toMarkdown }) => ({
+ ...ns,
+ [name]: toMarkdown,
+ }),
+ {},
+ );
+
+const marks = editorExtensions
+ .filter(extension => extension.type === 'mark')
+ .reduce(
+ (ms, { name, toMarkdown }) => ({
+ ...ms,
+ [name]: toMarkdown,
+ }),
+ {},
+ );
+
+export default new MarkdownSerializer(nodes, marks);
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index 2918e1486a7..0eb067d4963 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -1,6 +1,5 @@
import $ from 'jquery';
import Mousetrap from 'mousetrap';
-import _ from 'underscore';
import Sidebar from '../../right_sidebar';
import Shortcuts from './shortcuts';
import { CopyAsGFM } from '../markdown/copy_as_gfm';
@@ -63,18 +62,18 @@ export default class ShortcutsIssuable extends Shortcuts {
}
const el = CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
- const selected = CopyAsGFM.nodeToGFM(el);
+ const blockquoteEl = document.createElement('blockquote');
+ blockquoteEl.appendChild(el);
+ const text = CopyAsGFM.nodeToGFM(blockquoteEl);
- if (selected.trim() === '') {
+ if (text.trim() === '') {
return false;
}
- const quote = _.map(selected.split('\n'), val => `${`> ${val}`.trim()}\n`);
-
// If replyField already has some content, add a newline before our quote
const separator = ($replyField.val().trim() !== '' && '\n\n') || '';
$replyField
- .val((a, current) => `${current}${separator}${quote.join('')}\n`)
+ .val((a, current) => `${current}${separator}${text}\n\n`)
.trigger('input')
.trigger('change');
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index b07f951346e..5f64175362d 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -16,6 +16,7 @@ export default () => {
const filePath = editBlobForm.data('blobFilename');
const currentAction = $('.js-file-title').data('currentAction');
const projectId = editBlobForm.data('project-id');
+ const isMarkdown = editBlobForm.data('is-markdown');
const commitButton = $('.js-commit-button');
const cancelLink = $('.btn.btn-cancel');
@@ -27,7 +28,13 @@ export default () => {
window.onbeforeunload = null;
});
- new EditBlob(`${urlRoot}${assetsPath}`, filePath, currentAction, projectId);
+ new EditBlob({
+ assetsPath: `${urlRoot}${assetsPath}`,
+ filePath,
+ currentAction,
+ projectId,
+ isMarkdown,
+ });
new NewCommitForm(editBlobForm);
// returning here blocks page navigation
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index 6e19548eed2..011898a5e7a 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -6,22 +6,31 @@ import createFlash from '~/flash';
import { __ } from '~/locale';
import TemplateSelectorMediator from '../blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils';
+import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
export default class EditBlob {
- constructor(assetsPath, aceMode, currentAction, projectId) {
- this.configureAceEditor(aceMode, assetsPath);
+ // The options object has:
+ // assetsPath, filePath, currentAction, projectId, isMarkdown
+ constructor(options) {
+ this.options = options;
+ this.configureAceEditor();
this.initModePanesAndLinks();
this.initSoftWrap();
- this.initFileSelectors(currentAction, projectId);
+ this.initFileSelectors();
}
- configureAceEditor(filePath, assetsPath) {
+ configureAceEditor() {
+ const { filePath, assetsPath, isMarkdown } = this.options;
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor');
+ if (isMarkdown) {
+ addEditorMarkdownListeners(this.editor);
+ }
+
// This prevents warnings re: automatic scrolling being logged
this.editor.$blockScrolling = Infinity;
@@ -32,7 +41,8 @@ export default class EditBlob {
}
}
- initFileSelectors(currentAction, projectId) {
+ initFileSelectors() {
+ const { currentAction, projectId } = this.options;
this.fileTemplateMediator = new TemplateSelectorMediator({
currentAction,
editor: this.editor,
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 30fbdb9e97f..f569322ab70 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -86,7 +86,7 @@ export default {
class="board-card"
@mousedown="mouseDown"
@mousemove="mouseMove"
- @mouseup="showIssue($event);"
+ @mouseup="showIssue($event)"
>
<issue-card-inner
:list="list"
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index f3f341ece5c..a689dfc3768 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -221,7 +221,7 @@ export default {
</script>
<template>
- <div class="board-list-component">
+ <div class="board-list-component d-flex flex-column">
<div v-if="loading" class="board-list-loading text-center" aria-label="Loading issues">
<gl-loading-icon />
</div>
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 93bcb4e129e..28d96dab605 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -96,7 +96,7 @@ export default {
<template>
<div class="board-new-issue-form">
<div class="board-card">
- <form @submit="submit($event);">
+ <form @submit="submit($event)">
<div v-if="error" class="flash-container">
<div class="flash-alert">An error occurred. Please try again.</div>
</div>
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index 0f581c3d37d..90ab3a76342 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -184,7 +184,7 @@ export default {
:title="label.description"
class="badge color-label append-right-4 prepend-top-4"
type="button"
- @click="filterByLabel(label);"
+ @click="filterByLabel(label)"
>
{{ label.title }}
</button>
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
index defd857b92c..2a0008467c4 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ b/app/assets/javascripts/boards/components/modal/empty_state.vue
@@ -58,7 +58,7 @@ export default {
v-if="activeTab === 'selected'"
class="btn btn-default"
type="button"
- @click="changeTab('all');"
+ @click="changeTab('all')"
>
Open issues
</button>
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index b1bc7d87086..d4afd9d59da 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -71,7 +71,7 @@ export default {
<span class="inline add-issues-footer-to-list"> to list </span>
<lists-dropdown />
</div>
- <button class="btn btn-default float-right" type="button" @click="toggleModal(false);">
+ <button class="btn btn-default float-right" type="button" @click="toggleModal(false)">
Cancel
</button>
</footer>
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index d0e285a149e..1f0961e02d8 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -58,7 +58,7 @@ export default {
class="close"
data-dismiss="modal"
aria-label="Close"
- @click="toggleModal(false);"
+ @click="toggleModal(false)"
>
<span aria-hidden="true">×</span>
</button>
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index 878bb002c6c..e9ed2de859d 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -130,7 +130,7 @@ export default {
<div
:class="{ 'is-active': issue.selected }"
class="board-card"
- @click="toggleIssue($event, issue);"
+ @click="toggleIssue($event, issue)"
>
<issue-card-inner :issue="issue" :issue-link-base="issueLinkBase" :root-path="rootPath" />
<icon
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
index 820d0679df5..3fbe8fe1be7 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
@@ -38,7 +38,7 @@ export default {
:class="{ 'is-active': list.id == selected.id }"
href="#"
role="button"
- @click.prevent="modal.selectedList = list;"
+ @click.prevent="modal.selectedList = list"
>
<span :style="{ backgroundColor: list.label.color }" class="dropdown-label-box"> </span>
{{ list.title }}
diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue
index 7b800a6ab97..2d2920e312e 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.vue
+++ b/app/assets/javascripts/boards/components/modal/tabs.vue
@@ -21,12 +21,12 @@ export default {
<div class="top-area prepend-top-10 append-bottom-10">
<ul class="nav-links issues-state-filters">
<li :class="{ active: activeTab == 'all' }">
- <a href="#" role="button" @click.prevent="changeTab('all');">
+ <a href="#" role="button" @click.prevent="changeTab('all')">
Open issues <span class="badge badge-pill"> {{ issuesCount }} </span>
</a>
</li>
<li :class="{ active: activeTab == 'selected' }">
- <a href="#" role="button" @click.prevent="changeTab('selected');">
+ <a href="#" role="button" @click.prevent="changeTab('selected')">
Selected issues <span class="badge badge-pill"> {{ selectedCount }} </span>
</a>
</li>
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index d899b7fbd8c..8274647744f 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -82,7 +82,7 @@ export default {
<template>
<div>
<label class="label-bold prepend-top-10"> Project </label>
- <div ref="projectsDropdown" class="dropdown">
+ <div ref="projectsDropdown" class="dropdown dropdown-projects">
<button
class="dropdown-menu-toggle wide"
type="button"
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index dd3feedbc0e..9f6d9a853da 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -244,6 +244,7 @@ class List {
issue.project = data.project;
issue.path = data.real_path;
issue.referencePath = data.reference_path;
+ issue.assignableLabelsEndpoint = data.assignable_labels_endpoint;
if (this.issuesSize > 1) {
const moveBeforeId = this.issues[1].id;
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index cf70a48f076..b1f992c03ff 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -1,6 +1,6 @@
import Visibility from 'visibilityjs';
import Vue from 'vue';
-import initDismissableCallout from '~/dismissable_callout';
+import PersistentUserCallout from '../persistent_user_callout';
import { s__, sprintf } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
@@ -32,6 +32,7 @@ export default class Clusters {
installKnativePath,
installPrometheusPath,
managePrometheusPath,
+ hasRbac,
clusterType,
clusterStatus,
clusterStatusReason,
@@ -45,6 +46,7 @@ export default class Clusters {
this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
+ this.store.updateRbac(hasRbac);
this.service = new ClustersService({
endpoint: statusPath,
installHelmEndpoint: installHelmPath,
@@ -67,7 +69,7 @@ export default class Clusters {
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
- initDismissableCallout('.js-cluster-security-warning');
+ Clusters.initDismissableCallout();
initSettingsPanels();
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
this.initApplications(clusterType);
@@ -102,12 +104,19 @@ export default class Clusters {
ingressHelpPath: this.state.ingressHelpPath,
managePrometheusPath: this.state.managePrometheusPath,
ingressDnsHelpPath: this.state.ingressDnsHelpPath,
+ rbac: this.state.rbac,
},
});
},
});
}
+ static initDismissableCallout() {
+ const callout = document.querySelector('.js-cluster-security-warning');
+
+ if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ }
+
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 489615f1f78..5d19c79570a 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -52,6 +52,11 @@ export default {
required: false,
default: '',
},
+ rbac: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data: () => ({
elasticsearchLogo,
@@ -442,6 +447,18 @@ export default {
title-link="https://github.com/knative/docs"
>
<div slot="description">
+ <span v-if="!rbac">
+ <p v-if="!rbac" class="bs-callout bs-callout-info append-bottom-0">
+ {{
+ s__(`ClusterIntegration|You must have an RBAC-enabled cluster
+ to install Knative.`)
+ }}
+ <a :href="helpPath" target="_blank" rel="noopener noreferrer">
+ {{ __('More information') }}
+ </a>
+ </p>
+ <br />
+ </span>
<p>
{{
s__(`ClusterIntegration|Knative extends Kubernetes to provide
@@ -465,7 +482,7 @@ export default {
/>
</div>
</template>
- <template v-else-if="helmInstalled">
+ <template v-else-if="helmInstalled && rbac">
<div class="form-group">
<label for="knative-domainname">
{{ s__('ClusterIntegration|Knative Domain Name:') }}
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index c750daab112..8f74be4e0e6 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -1,4 +1,5 @@
import { s__ } from '../../locale';
+import { parseBoolean } from '../../lib/utils/common_utils';
import { INGRESS, JUPYTER, KNATIVE, CERT_MANAGER } from '../constants';
export default class ClusterStore {
@@ -7,6 +8,7 @@ export default class ClusterStore {
helpPath: null,
ingressHelpPath: null,
status: null,
+ rbac: false,
statusReason: null,
applications: {
helm: {
@@ -81,6 +83,10 @@ export default class ClusterStore {
this.state.status = status;
}
+ updateRbac(rbac) {
+ this.state.rbac = parseBoolean(rbac);
+ }
+
updateStatusReason(reason) {
this.state.statusReason = reason;
}
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index 10f02739ec8..50efecb3475 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -13,6 +13,9 @@ export default class ContextualSidebar {
initDomElements() {
this.$page = $('.layout-page');
this.$sidebar = $('.nav-sidebar');
+
+ if (!this.$sidebar.length) return;
+
this.$innerScroll = $('.nav-sidebar-inner-scroll', this.$sidebar);
this.$overlay = $('.mobile-overlay');
this.$openSidebar = $('.toggle-mobile-nav');
@@ -21,12 +24,14 @@ export default class ContextualSidebar {
}
bindEvents() {
+ if (!this.$sidebar.length) return;
+
document.addEventListener('click', e => {
if (
!e.target.closest('.nav-sidebar') &&
(bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md')
) {
- this.toggleCollapsedSidebar(true);
+ this.toggleCollapsedSidebar(true, true);
}
});
this.$openSidebar.on('click', () => this.toggleSidebarNav(true));
@@ -34,7 +39,7 @@ export default class ContextualSidebar {
this.$overlay.on('click', () => this.toggleSidebarNav(false));
this.$sidebarToggle.on('click', () => {
const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop');
- this.toggleCollapsedSidebar(value);
+ this.toggleCollapsedSidebar(value, true);
});
$(window).on('resize', () => _.debounce(this.render(), 100));
@@ -53,16 +58,19 @@ export default class ContextualSidebar {
this.$sidebar.removeClass('sidebar-collapsed-desktop');
}
- toggleCollapsedSidebar(collapsed) {
+ toggleCollapsedSidebar(collapsed, saveCookie) {
const breakpoint = bp.getBreakpointSize();
if (this.$sidebar.length) {
this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed);
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
}
- ContextualSidebar.setCollapsedCookie(collapsed);
- this.toggleSidebarOverflow();
+ if (saveCookie) {
+ ContextualSidebar.setCollapsedCookie(collapsed);
+ }
+
+ requestIdleCallback(() => this.toggleSidebarOverflow());
}
toggleSidebarOverflow() {
@@ -74,13 +82,15 @@ export default class ContextualSidebar {
}
render() {
+ if (!this.$sidebar.length) return;
+
const breakpoint = bp.getBreakpointSize();
if (breakpoint === 'sm' || breakpoint === 'md') {
- this.toggleCollapsedSidebar(true);
+ this.toggleCollapsedSidebar(true, false);
} else if (breakpoint === 'lg') {
const collapse = parseBoolean(Cookies.get('sidebar_collapsed'));
- this.toggleCollapsedSidebar(collapse);
+ this.toggleCollapsedSidebar(collapse, false);
}
}
}
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index f01e6f2a639..6ffb8c4e1c0 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -113,7 +113,7 @@ export default {
<div class="gl-responsive-table-row deploy-key">
<div class="table-section section-40">
<div role="rowheader" class="table-mobile-header">{{ s__('DeployKeys|Deploy key') }}</div>
- <div class="table-mobile-content">
+ <div class="table-mobile-content qa-key">
<strong class="title qa-key-title"> {{ deployKey.title }} </strong>
<div class="fingerprint qa-key-fingerprint">{{ deployKey.fingerprint }}</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index ebc4a83af4d..c02a8740a42 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -5,6 +5,7 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CIIcon from '~/vue_shared/components/ci_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
+import initUserPopovers from '../../user_popovers';
/**
* CommitItem
@@ -35,20 +36,30 @@ export default {
},
},
computed: {
+ author() {
+ return this.commit.author || {};
+ },
authorName() {
- return (this.commit.author && this.commit.author.name) || this.commit.author_name;
+ return this.author.name || this.commit.author_name;
+ },
+ authorClass() {
+ return this.author.name ? 'js-user-link' : '';
+ },
+ authorId() {
+ return this.author.id ? this.author.id : '';
},
authorUrl() {
- return (
- (this.commit.author && this.commit.author.web_url) || `mailto:${this.commit.author_email}`
- );
+ return this.author.web_url || `mailto:${this.commit.author_email}`;
},
authorAvatar() {
- return (
- (this.commit.author && this.commit.author.avatar_url) || this.commit.author_gravatar_url
- );
+ return this.author.avatar_url || this.commit.author_gravatar_url;
},
},
+ created() {
+ this.$nextTick(() => {
+ initUserPopovers(this.$el.querySelectorAll('.js-user-link'));
+ });
+ },
};
</script>
@@ -81,7 +92,13 @@ export default {
</button>
<div class="commiter">
- <a :href="authorUrl" v-text="authorName"></a> {{ s__('CommitWidget|authored') }}
+ <a
+ :href="authorUrl"
+ :class="authorClass"
+ :data-user-id="authorId"
+ v-text="authorName"
+ ></a>
+ {{ s__('CommitWidget|authored') }}
<time-ago-tooltip :time="commit.authored_date" />
</div>
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index f75345d31f8..3ef54752436 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -2,9 +2,10 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
-import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
+import { polyfillSticky } from '~/lib/utils/sticky';
import Icon from '~/vue_shared/components/icon.vue';
import CompareVersionsDropdown from './compare_versions_dropdown.vue';
+import SettingsDropdown from './settings_dropdown.vue';
export default {
components: {
@@ -12,6 +13,7 @@ export default {
Icon,
GlLink,
GlButton,
+ SettingsDropdown,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -34,26 +36,25 @@ export default {
},
computed: {
...mapState('diffs', ['commit', 'showTreeList', 'startVersion', 'latestVersionPath']),
- ...mapGetters('diffs', ['isInlineView', 'isParallelView', 'hasCollapsedFile']),
+ ...mapGetters('diffs', ['hasCollapsedFile']),
comparableDiffs() {
return this.mergeRequestDiffs.slice(1);
},
- toggleWhitespaceText() {
- if (this.isWhitespaceVisible()) {
- return __('Hide whitespace changes');
- }
- return __('Show whitespace changes');
- },
- toggleWhitespacePath() {
- if (this.isWhitespaceVisible()) {
- return mergeUrlParams({ w: 1 }, window.location.href);
- }
-
- return mergeUrlParams({ w: 0 }, window.location.href);
- },
showDropdowns() {
return !this.commit && this.mergeRequestDiffs.length;
},
+ fileTreeIcon() {
+ return this.showTreeList ? 'collapse-left' : 'expand-left';
+ },
+ toggleFileBrowserTitle() {
+ return this.showTreeList ? __('Hide file browser') : __('Show file browser');
+ },
+ baseVersionPath() {
+ return this.mergeRequestDiff.base_version_path;
+ },
+ },
+ mounted() {
+ polyfillSticky(this.$el);
},
methods: {
...mapActions('diffs', [
@@ -62,15 +63,12 @@ export default {
'expandAllFiles',
'toggleShowTreeList',
]),
- isWhitespaceVisible() {
- return getParameterValues('w')[0] !== '1';
- },
},
};
</script>
<template>
- <div class="mr-version-controls">
+ <div class="mr-version-controls" :class="{ 'is-fileTreeOpen': showTreeList }">
<div class="mr-version-menus-container content-block">
<button
v-gl-tooltip.hover
@@ -79,10 +77,10 @@ export default {
:class="{
active: showTreeList,
}"
- :title="__('Toggle file browser')"
+ :title="toggleFileBrowserTitle"
@click="toggleShowTreeList"
>
- <icon name="hamburger" />
+ <icon :name="fileTreeIcon" />
</button>
<div v-if="showDropdowns" class="d-flex align-items-center compare-versions-container">
Changes between
@@ -95,6 +93,7 @@ export default {
and
<compare-versions-dropdown
:other-versions="comparableDiffs"
+ :base-version-path="baseVersionPath"
:start-version="startVersion"
:target-branch="targetBranch"
class="mr-version-compare-dropdown"
@@ -115,31 +114,7 @@ export default {
<a v-show="hasCollapsedFile" class="btn btn-default append-right-8" @click="expandAllFiles">
{{ __('Expand all') }}
</a>
- <a :href="toggleWhitespacePath" class="btn btn-default qa-toggle-whitespace">
- {{ toggleWhitespaceText }}
- </a>
- <div class="btn-group prepend-left-8">
- <button
- id="inline-diff-btn"
- :class="{ active: isInlineView }"
- type="button"
- class="btn js-inline-diff-button"
- data-view-type="inline"
- @click="setInlineDiffViewType"
- >
- {{ __('Inline') }}
- </button>
- <button
- id="parallel-diff-btn"
- :class="{ active: isParallelView }"
- type="button"
- class="btn js-parallel-diff-button"
- data-view-type="parallel"
- @click="setParallelDiffViewType"
- >
- {{ __('Side-by-side') }}
- </button>
- </div>
+ <settings-dropdown />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
index b9b1ee02697..80aec84f574 100644
--- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
@@ -34,14 +34,13 @@ export default {
required: false,
default: false,
},
+ baseVersionPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
- baseVersion() {
- return {
- name: 'hii',
- versionIndex: -1,
- };
- },
targetVersions() {
if (this.mergeRequestVersion) {
return this.otherVersions;
@@ -62,6 +61,9 @@ export default {
);
},
href(version) {
+ if (this.isBase(version)) {
+ return this.baseVersionPath;
+ }
if (this.showCommitCount) {
return version.version_path;
}
@@ -139,7 +141,7 @@ export default {
<time-ago
v-if="version.created_at"
:time="version.created_at"
- class="js-timeago js-timeago-render"
+ class="js-timeago"
/>
</small>
</div>
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index ba6dcd63880..6dc2f5d3f68 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -127,7 +127,7 @@ export default {
:save-button-title="__('Comment')"
class="diff-comment-form new-note discussion-form discussion-form-container"
@handleFormUpdate="handleSaveNote"
- @cancelForm="closeDiffFileCommentForm(diffFile.file_hash);"
+ @cancelForm="closeDiffFileCommentForm(diffFile.file_hash)"
/>
</div>
</diff-viewer>
diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue
index b2021cd6061..4c73eea4049 100644
--- a/app/assets/javascripts/diffs/components/diff_discussions.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussions.vue
@@ -68,7 +68,7 @@ export default {
}"
type="button"
class="js-diff-notes-toggle"
- @click="toggleDiscussion({ discussionId: discussion.id });"
+ @click="toggleDiscussion({ discussionId: discussion.id })"
>
<icon v-if="discussion.expanded" name="collapse" class="collapse-icon" />
<template v-else>
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index f75a01b023b..b58f704bebb 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -145,7 +145,7 @@ export default {
<div
ref="header"
class="js-file-title file-title file-title-flex-parent"
- @click="handleToggleFile($event, true);"
+ @click="handleToggleFile($event, true)"
>
<div class="file-header-content">
<icon
diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
index c0613d80d37..6709df48637 100644
--- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
@@ -179,7 +179,7 @@ export default {
v-if="lineNumber"
:data-linenumber="lineNumber"
:href="lineHref"
- @click="setHighlightedRow(lineCode);"
+ @click="setHighlightedRow(lineCode)"
>
</a>
<diff-gutter-avatars v-if="shouldShowAvatarsOnGutter" :discussions="line.discussions" />
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 e7569ba7b84..18edbe286ba 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -28,6 +28,11 @@ export default {
type: Object,
required: true,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
...mapState({
@@ -95,6 +100,7 @@ export default {
:is-editing="true"
:line-code="line.line_code"
:line="line"
+ :help-page-path="helpPagePath"
save-button-title="Comment"
class="diff-comment-form"
@cancelForm="handleCancelCommentForm"
diff --git a/app/assets/javascripts/diffs/components/image_diff_overlay.vue b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
index d30e64312aa..4a83c5a72a5 100644
--- a/app/assets/javascripts/diffs/components/image_diff_overlay.vue
+++ b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
@@ -97,7 +97,7 @@ export default {
v-if="canComment"
type="button"
class="btn-transparent position-absolute image-diff-overlay-add-comment w-100 h-100 js-add-image-diff-note-button"
- @click="clickedImage($event.offsetX, $event.offsetY);"
+ @click="clickedImage($event.offsetX, $event.offsetY)"
>
<span class="sr-only"> {{ __('Add image comment') }} </span>
</button>
@@ -109,7 +109,7 @@ export default {
:disabled="!shouldToggleDiscussion"
class="js-image-badge"
type="button"
- @click="toggleDiscussion({ discussionId: discussion.id });"
+ @click="toggleDiscussion({ discussionId: discussion.id })"
>
<icon v-if="showCommentIcon" name="image-comment-dark" />
<template v-else>
diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
index 814ee0b7c02..69146f1f6fd 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
@@ -54,6 +54,7 @@ export default {
:diff-file-hash="diffFileHash"
:line="line"
:note-target-line="line"
+ :help-page-path="helpPagePath"
/>
</div>
</td>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
index a65cf025cde..370cb6e339a 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -101,6 +101,7 @@ export default {
:diff-file-hash="diffFileHash"
:line="line.left"
:note-target-line="line.left"
+ :help-page-path="helpPagePath"
line-position="left"
/>
</td>
diff --git a/app/assets/javascripts/diffs/components/settings_dropdown.vue b/app/assets/javascripts/diffs/components/settings_dropdown.vue
new file mode 100644
index 00000000000..0129763161a
--- /dev/null
+++ b/app/assets/javascripts/diffs/components/settings_dropdown.vue
@@ -0,0 +1,92 @@
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import { GlButton } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ GlButton,
+ Icon,
+ },
+ computed: {
+ ...mapGetters('diffs', ['isInlineView', 'isParallelView']),
+ ...mapState('diffs', ['renderTreeList', 'showWhitespace']),
+ },
+ methods: {
+ ...mapActions('diffs', [
+ 'setInlineDiffViewType',
+ 'setParallelDiffViewType',
+ 'setRenderTreeList',
+ 'setShowWhitespace',
+ ]),
+ },
+};
+</script>
+
+<template>
+ <div class="dropdown">
+ <button
+ type="button"
+ class="btn btn-default js-show-diff-settings"
+ data-toggle="dropdown"
+ data-display="static"
+ >
+ <icon name="settings" /> <icon name="arrow-down" />
+ </button>
+ <div class="dropdown-menu dropdown-menu-right p-2 pt-3 pb-3">
+ <div>
+ <span class="bold d-block mb-1">{{ __('File browser') }}</span>
+ <div class="btn-group d-flex">
+ <gl-button
+ :class="{ active: !renderTreeList }"
+ class="w-100 js-list-view"
+ @click="setRenderTreeList(false)"
+ >
+ {{ __('List view') }}
+ </gl-button>
+ <gl-button
+ :class="{ active: renderTreeList }"
+ class="w-100 js-tree-view"
+ @click="setRenderTreeList(true)"
+ >
+ {{ __('Tree view') }}
+ </gl-button>
+ </div>
+ </div>
+ <div class="mt-2">
+ <span class="bold d-block mb-1">{{ __('Compare changes') }}</span>
+ <div class="btn-group d-flex js-diff-view-buttons">
+ <gl-button
+ id="inline-diff-btn"
+ :class="{ active: isInlineView }"
+ class="w-100 js-inline-diff-button"
+ data-view-type="inline"
+ @click="setInlineDiffViewType"
+ >
+ {{ __('Inline') }}
+ </gl-button>
+ <gl-button
+ id="parallel-diff-btn"
+ :class="{ active: isParallelView }"
+ class="w-100 js-parallel-diff-button"
+ data-view-type="parallel"
+ @click="setParallelDiffViewType"
+ >
+ {{ __('Side-by-side') }}
+ </gl-button>
+ </div>
+ </div>
+ <div class="mt-2">
+ <label class="mb-0">
+ <input
+ id="show-whitespace"
+ type="checkbox"
+ :checked="showWhitespace"
+ @change="setShowWhitespace({ showWhitespace: $event.target.checked, pushState: true })"
+ />
+ {{ __('Show whitespace changes') }}
+ </label>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index f40a7b25fde..0b3def3d29d 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -1,13 +1,10 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
-import { parseBoolean } from '~/lib/utils/common_utils';
import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileRowStats from './file_row_stats.vue';
-const treeListStorageKey = 'mr_diff_tree_list';
-
export default {
directives: {
GlTooltip: GlTooltipDirective,
@@ -17,50 +14,36 @@ export default {
FileRow,
},
data() {
- const treeListStored = localStorage.getItem(treeListStorageKey);
- const renderTreeList = treeListStored !== null ? parseBoolean(treeListStored) : true;
-
return {
search: '',
- renderTreeList,
- focusSearch: false,
};
},
computed: {
- ...mapState('diffs', ['tree', 'addedLines', 'removedLines']),
+ ...mapState('diffs', ['tree', 'addedLines', 'removedLines', 'renderTreeList']),
...mapGetters('diffs', ['allBlobs', 'diffFilesLength']),
filteredTreeList() {
const search = this.search.toLowerCase().trim();
if (search === '') return this.renderTreeList ? this.tree : this.allBlobs;
- return this.allBlobs.filter(f => f.path.toLowerCase().indexOf(search) >= 0);
- },
- rowDisplayTextKey() {
- if (this.renderTreeList && this.search.trim() === '') {
- return 'name';
- }
+ return this.allBlobs.reduce((acc, folder) => {
+ const tree = folder.tree.filter(f => f.path.toLowerCase().indexOf(search) >= 0);
- return 'path';
+ if (tree.length) {
+ return acc.concat({
+ ...folder,
+ tree,
+ });
+ }
+
+ return acc;
+ }, []);
},
},
methods: {
...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']),
clearSearch() {
this.search = '';
- this.toggleFocusSearch(false);
- },
- toggleRenderTreeList(toggle) {
- this.renderTreeList = toggle;
- localStorage.setItem(treeListStorageKey, this.renderTreeList);
- },
- toggleFocusSearch(toggle) {
- this.focusSearch = toggle;
- },
- blurSearch() {
- if (this.search.trim() === '') {
- this.toggleFocusSearch(false);
- }
},
},
FileRowStats,
@@ -77,8 +60,6 @@ export default {
:placeholder="s__('MergeRequest|Filter files')"
type="search"
class="form-control"
- @focus="toggleFocusSearch(true);"
- @blur="blurSearch"
/>
<button
v-show="search"
@@ -90,36 +71,8 @@ export default {
<icon name="close" />
</button>
</div>
- <div v-show="!focusSearch" class="btn-group prepend-left-8 tree-list-view-toggle">
- <button
- v-gl-tooltip.hover
- :aria-label="__('List view')"
- :title="__('List view')"
- :class="{
- active: !renderTreeList,
- }"
- class="btn btn-default pt-0 pb-0 d-flex align-items-center"
- type="button"
- @click="toggleRenderTreeList(false);"
- >
- <icon name="hamburger" />
- </button>
- <button
- v-gl-tooltip.hover
- :aria-label="__('Tree view')"
- :title="__('Tree view')"
- :class="{
- active: renderTreeList,
- }"
- class="btn btn-default pt-0 pb-0 d-flex align-items-center"
- type="button"
- @click="toggleRenderTreeList(true);"
- >
- <icon name="file-tree" />
- </button>
- </div>
</div>
- <div class="tree-list-scroll">
+ <div :class="{ 'pt-0 tree-list-blobs': !renderTreeList }" class="tree-list-scroll">
<template v-if="filteredTreeList.length">
<file-row
v-for="file in filteredTreeList"
@@ -129,8 +82,6 @@ export default {
:hide-extra-on-tree="true"
:extra-component="$options.FileRowStats"
:show-changed-icon="true"
- :display-text-key="rowDisplayTextKey"
- :should-truncate-start="true"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile"
/>
@@ -148,3 +99,9 @@ export default {
</div>
</div>
</template>
+
+<style>
+.tree-list-blobs .file-row-name {
+ margin-left: 12px;
+}
+</style>
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 78a39baa4cb..bd188d9de9e 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -32,3 +32,7 @@ export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
export const MAX_LINES_TO_BE_RENDERED = 2000;
export const MR_TREE_SHOW_KEY = 'mr_tree_show';
+
+export const TREE_TYPE = 'tree';
+export const TREE_LIST_STORAGE_KEY = 'mr_diff_tree_list';
+export const WHITESPACE_STORAGE_KEY = 'mr_show_whitespace';
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index b130cedc24c..094e5cdea9c 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -1,6 +1,9 @@
import Vue from 'vue';
-import { mapState } from 'vuex';
+import { mapActions, mapState } from 'vuex';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import { getParameterValues } from '~/lib/utils/url_utility';
import diffsApp from './components/app.vue';
+import { TREE_LIST_STORAGE_KEY } from './constants';
export default function initDiffsApp(store) {
return new Vue({
@@ -26,6 +29,16 @@ export default function initDiffsApp(store) {
activeTab: state => state.page.activeTab,
}),
},
+ created() {
+ const treeListStored = localStorage.getItem(TREE_LIST_STORAGE_KEY);
+ const renderTreeList = treeListStored !== null ? parseBoolean(treeListStored) : true;
+
+ this.setRenderTreeList(renderTreeList);
+ this.setShowWhitespace({ showWhitespace: getParameterValues('w')[0] !== '1' });
+ },
+ methods: {
+ ...mapActions('diffs', ['setRenderTreeList', 'setShowWhitespace']),
+ },
render(createElement) {
return createElement('diffs-app', {
props: {
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 00a4bb6d3a3..2c5019fb652 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -5,6 +5,7 @@ import createFlash from '~/flash';
import { s__ } from '~/locale';
import { handleLocationHash, historyPushState, scrollToElement } from '~/lib/utils/common_utils';
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
+import TreeWorker from '../workers/tree_worker';
import eventHub from '../../notes/event_hub';
import { getDiffPositionByLineCode, getNoteFormData } from './utils';
import * as types from './mutation_types';
@@ -13,6 +14,8 @@ import {
INLINE_DIFF_VIEW_TYPE,
DIFF_VIEW_COOKIE_NAME,
MR_TREE_SHOW_KEY,
+ TREE_LIST_STORAGE_KEY,
+ WHITESPACE_STORAGE_KEY,
} from '../constants';
export const setBaseConfig = ({ commit }, options) => {
@@ -21,17 +24,29 @@ export const setBaseConfig = ({ commit }, options) => {
};
export const fetchDiffFiles = ({ state, commit }) => {
+ const worker = new TreeWorker();
+
commit(types.SET_LOADING, true);
+ worker.addEventListener('message', ({ data }) => {
+ commit(types.SET_TREE_DATA, data);
+
+ worker.terminate();
+ });
+
return axios
- .get(state.endpoint)
+ .get(state.endpoint, { params: { w: state.showWhitespace ? null : '1' } })
.then(res => {
commit(types.SET_LOADING, false);
commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []);
commit(types.SET_DIFF_DATA, res.data);
+
+ worker.postMessage(state.diffFiles);
+
return Vue.nextTick();
})
- .then(handleLocationHash);
+ .then(handleLocationHash)
+ .catch(() => worker.terminate());
};
export const setHighlightedRow = ({ commit }, lineCode) => {
@@ -265,5 +280,21 @@ export const closeDiffFileCommentForm = ({ commit }, fileHash) => {
commit(types.CLOSE_DIFF_FILE_COMMENT_FORM, fileHash);
};
+export const setRenderTreeList = ({ commit }, renderTreeList) => {
+ commit(types.SET_RENDER_TREE_LIST, renderTreeList);
+
+ localStorage.setItem(TREE_LIST_STORAGE_KEY, renderTreeList);
+};
+
+export const setShowWhitespace = ({ commit }, { showWhitespace, pushState = false }) => {
+ commit(types.SET_SHOW_WHITESPACE, showWhitespace);
+
+ localStorage.setItem(WHITESPACE_STORAGE_KEY, showWhitespace);
+
+ if (pushState) {
+ historyPushState(showWhitespace ? '?w=0' : '?w=1');
+ }
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index fdf1efbb10e..86c0c7190f9 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -74,7 +74,24 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
export const getDiffFileByHash = state => fileHash =>
state.diffFiles.find(file => file.file_hash === fileHash);
-export const allBlobs = state => Object.values(state.treeEntries).filter(f => f.type === 'blob');
+export const allBlobs = state =>
+ Object.values(state.treeEntries)
+ .filter(f => f.type === 'blob')
+ .reduce((acc, file) => {
+ const { parentPath } = file;
+
+ if (parentPath && !acc.some(f => f.path === parentPath)) {
+ acc.push({
+ path: parentPath,
+ isHeader: true,
+ tree: [],
+ });
+ }
+
+ acc.find(f => f.path === parentPath).tree.push(file);
+
+ return acc;
+ }, []);
export const diffFilesLength = state => state.diffFiles.length;
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 98e57d52d77..05b4c552f6e 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -27,4 +27,6 @@ export default () => ({
projectPath: '',
commentForms: [],
highlightedRow: null,
+ renderTreeList: true,
+ showWhitespace: true,
});
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 0338cde3658..e760b4d1079 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -18,3 +18,7 @@ export const OPEN_DIFF_FILE_COMMENT_FORM = 'OPEN_DIFF_FILE_COMMENT_FORM';
export const UPDATE_DIFF_FILE_COMMENT_FORM = 'UPDATE_DIFF_FILE_COMMENT_FORM';
export const CLOSE_DIFF_FILE_COMMENT_FORM = 'CLOSE_DIFF_FILE_COMMENT_FORM';
export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW';
+
+export const SET_TREE_DATA = 'SET_TREE_DATA';
+export const SET_RENDER_TREE_LIST = 'SET_RENDER_TREE_LIST';
+export const SET_SHOW_WHITESPACE = 'SET_SHOW_WHITESPACE';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index ed4203cf5e0..4aeb393b29b 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -1,5 +1,4 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { sortTree } from '~/ide/stores/utils';
import {
findDiffFile,
addLineReferences,
@@ -7,7 +6,6 @@ import {
addContextLines,
prepareDiffData,
isDiscussionApplicableToLine,
- generateTreeList,
} from './utils';
import * as types from './mutation_types';
@@ -23,12 +21,9 @@ export default {
[types.SET_DIFF_DATA](state, data) {
prepareDiffData(data);
- const { tree, treeEntries } = generateTreeList(data.diff_files);
Object.assign(state, {
...convertObjectPropsToCamelCase(data),
- tree: sortTree(tree),
- treeEntries,
});
},
@@ -239,4 +234,14 @@ export default {
[types.SET_HIGHLIGHTED_ROW](state, lineCode) {
state.highlightedRow = lineCode;
},
+ [types.SET_TREE_DATA](state, { treeEntries, tree }) {
+ state.treeEntries = treeEntries;
+ state.tree = tree;
+ },
+ [types.SET_RENDER_TREE_LIST](state, renderTreeList) {
+ state.renderTreeList = renderTreeList;
+ },
+ [types.SET_SHOW_WHITESPACE](state, showWhitespace) {
+ state.showWhitespace = showWhitespace;
+ },
};
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 2fe20551642..effb6202327 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -1,5 +1,6 @@
import _ from 'underscore';
import { diffModes } from '~/ide/constants';
+import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
import {
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
@@ -11,6 +12,7 @@ import {
MATCH_LINE_TYPE,
LINES_TO_BE_RENDERED_DIRECTLY,
MAX_LINES_TO_BE_RENDERED,
+ TREE_TYPE,
} from '../constants';
export function findDiffFile(files, hash) {
@@ -180,8 +182,6 @@ export function addContextLines(options) {
export function trimFirstCharOfLineContent(line = {}) {
// eslint-disable-next-line no-param-reassign
delete line.text;
- // eslint-disable-next-line no-param-reassign
- line.discussions = [];
const parsedLine = Object.assign({}, line);
@@ -221,10 +221,12 @@ export function prepareDiffData(diffData) {
line.line_code = getLineCode(line, u);
if (line.left) {
line.left = trimFirstCharOfLineContent(line.left);
+ line.left.discussions = [];
line.left.hasForm = false;
}
if (line.right) {
line.right = trimFirstCharOfLineContent(line.right);
+ line.right.discussions = [];
line.right.hasForm = false;
}
}
@@ -234,7 +236,11 @@ export function prepareDiffData(diffData) {
const linesLength = file.highlighted_diff_lines.length;
for (let u = 0; u < linesLength; u += 1) {
const line = file.highlighted_diff_lines[u];
- Object.assign(line, { ...trimFirstCharOfLineContent(line), hasForm: false });
+ Object.assign(line, {
+ ...trimFirstCharOfLineContent(line),
+ discussions: [],
+ hasForm: false,
+ });
}
showingLines += file.parallel_diff_lines.length;
}
@@ -289,8 +295,63 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD
return latestDiff && discussion.active && line_code === discussion.line_code;
}
-export const generateTreeList = files =>
- files.reduce(
+export const getLowestSingleFolder = folder => {
+ const getFolder = (blob, start = []) =>
+ blob.tree.reduce(
+ (acc, file) => {
+ const shouldGetFolder = file.tree.length === 1 && file.tree[0].type === TREE_TYPE;
+ const currentFileTypeTree = file.type === TREE_TYPE;
+ const path = shouldGetFolder || currentFileTypeTree ? acc.path.concat(file.name) : acc.path;
+ const tree = shouldGetFolder || currentFileTypeTree ? acc.tree.concat(file) : acc.tree;
+
+ if (shouldGetFolder) {
+ const firstFolder = getFolder(file);
+
+ path.push(...firstFolder.path);
+ tree.push(...firstFolder.tree);
+ }
+
+ return {
+ ...acc,
+ path,
+ tree,
+ };
+ },
+ { path: start, tree: [] },
+ );
+ const { path, tree } = getFolder(folder, [folder.name]);
+
+ return {
+ path: truncatePathMiddleToLength(path.join('/'), 40),
+ treeAcc: tree.length ? tree[tree.length - 1].tree : null,
+ };
+};
+
+export const flattenTree = tree => {
+ const flatten = blobTree =>
+ blobTree.reduce((acc, file) => {
+ const blob = file;
+ let treeToFlatten = blob.tree;
+
+ if (file.type === TREE_TYPE && file.tree.length === 1) {
+ const { treeAcc, path } = getLowestSingleFolder(file);
+
+ if (treeAcc) {
+ blob.name = path;
+ treeToFlatten = flatten(treeAcc);
+ }
+ }
+
+ blob.tree = flatten(treeToFlatten);
+
+ return acc.concat(blob);
+ }, []);
+
+ return flatten(tree);
+};
+
+export const generateTreeList = files => {
+ const { treeEntries, tree } = files.reduce(
(acc, file) => {
const split = file.new_path.split('/');
@@ -318,6 +379,7 @@ export const generateTreeList = files =>
fileHash: file.file_hash,
addedLines: file.added_lines,
removedLines: file.removed_lines,
+ parentPath: parent ? `${parent.path}/` : '/',
});
} else {
Object.assign(entry, {
@@ -334,6 +396,9 @@ export const generateTreeList = files =>
{ treeEntries: {}, tree: [] },
);
+ return { treeEntries, tree: flattenTree(tree) };
+};
+
export const getDiffMode = diffFile => {
const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}_file`]);
return (
diff --git a/app/assets/javascripts/diffs/workers/tree_worker.js b/app/assets/javascripts/diffs/workers/tree_worker.js
new file mode 100644
index 00000000000..534d737c77e
--- /dev/null
+++ b/app/assets/javascripts/diffs/workers/tree_worker.js
@@ -0,0 +1,14 @@
+import { sortTree } from '~/ide/stores/utils';
+import { generateTreeList } from '../store/utils';
+
+// eslint-disable-next-line no-restricted-globals
+self.addEventListener('message', e => {
+ const { data } = e;
+ const { treeEntries, tree } = generateTreeList(data);
+
+ // eslint-disable-next-line no-restricted-globals
+ self.postMessage({
+ treeEntries,
+ tree: sortTree(tree),
+ });
+});
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_form.js b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
index d8d0fa1fac4..00e41dd0301 100644
--- a/app/assets/javascripts/dirty_submit/dirty_submit_form.js
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
@@ -25,15 +25,16 @@ class DirtySubmitForm {
DirtySubmitForm.THROTTLE_DURATION,
);
this.form.addEventListener('input', throttledUpdateDirtyInput);
+ this.form.addEventListener('change', throttledUpdateDirtyInput);
this.form.addEventListener('submit', event => this.formSubmit(event));
}
updateDirtyInput(event) {
- const input = event.target;
+ const { target } = event;
- if (!input.dataset.isDirtySubmitInput) return;
+ if (!target.dataset.isDirtySubmitInput) return;
- this.updateDirtyInputs(input);
+ this.updateDirtyInputs(target);
this.toggleSubmission();
}
diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js
deleted file mode 100644
index 5185b019376..00000000000
--- a/app/assets/javascripts/dismissable_callout.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import $ from 'jquery';
-import axios from '~/lib/utils/axios_utils';
-import { __ } from '~/locale';
-import Flash from '~/flash';
-
-export default function initDismissableCallout(alertSelector) {
- const alertEl = document.querySelector(alertSelector);
- if (!alertEl) {
- return;
- }
-
- const closeButtonEl = alertEl.getElementsByClassName('close')[0];
- const { dismissEndpoint, featureId } = closeButtonEl.dataset;
-
- closeButtonEl.addEventListener('click', () => {
- axios
- .post(dismissEndpoint, {
- feature_name: featureId,
- })
- .then(() => {
- $(alertEl).alert('close');
- })
- .catch(() => {
- Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
- });
- });
-}
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index 1f7dab9fbd2..208bd19f6b0 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -92,7 +92,7 @@ export default {
:disabled="isActionDisabled(action)"
type="button"
class="js-manual-action-link no-btn btn d-flex align-items-center"
- @click="onClickAction(action);"
+ @click="onClickAction(action)"
>
<span class="flex-fill"> {{ action.name }} </span>
<span v-if="action.scheduledAt" class="text-secondary">
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index ae9459a2482..87c1d44dd40 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -96,9 +96,9 @@ export default {
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
<div v-if="canCreateEnvironment && !isLoading" class="nav-controls">
- <a :href="newEnvironmentPath" class="btn btn-success">
- {{ s__('Environments|New environment') }}
- </a>
+ <a :href="newEnvironmentPath" class="btn btn-success">{{
+ s__('Environments|New environment')
+ }}</a>
</div>
</div>
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 533e90e2222..75bdf87137f 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -85,9 +85,9 @@ export default {
<div :key="`sub-div-${i}`">
<div class="text-center prepend-top-10">
- <a :href="folderUrl(model)" class="btn btn-default">
- {{ s__('Environments|Show all') }}
- </a>
+ <a :href="folderUrl(model)" class="btn btn-default">{{
+ s__('Environments|Show all')
+ }}</a>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index 3cf6e4ad14d..982e550e73c 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -15,11 +15,11 @@ export default () =>
const environmentsData = document.querySelector(this.$options.el).dataset;
return {
- endpoint: environmentsData.endpoint,
- folderName: environmentsData.folderName,
+ endpoint: environmentsData.environmentsDataEndpoint,
+ folderName: environmentsData.environmentsDataFolderName,
cssContainerClass: environmentsData.cssClass,
- canCreateDeployment: parseBoolean(environmentsData.canCreateDeployment),
- canReadEnvironment: parseBoolean(environmentsData.canReadEnvironment),
+ canCreateDeployment: parseBoolean(environmentsData.environmentsDataCanCreateDeployment),
+ canReadEnvironment: parseBoolean(environmentsData.environmentsDataCanReadEnvironment),
};
},
render(createElement) {
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
new file mode 100644
index 00000000000..6981afe1ead
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -0,0 +1,118 @@
+<script>
+import { mapActions, mapState } from 'vuex';
+import { GlEmptyState, GlButton, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import { __ } from '~/locale';
+
+export default {
+ fields: [
+ { key: 'error', label: __('Open errors') },
+ { key: 'events', label: __('Events') },
+ { key: 'users', label: __('Users') },
+ { key: 'lastSeen', label: __('Last seen') },
+ ],
+ components: {
+ GlEmptyState,
+ GlButton,
+ GlLink,
+ GlLoadingIcon,
+ GlTable,
+ Icon,
+ TimeAgo,
+ },
+ props: {
+ indexPath: {
+ type: String,
+ required: true,
+ },
+ enableErrorTrackingLink: {
+ type: String,
+ required: true,
+ },
+ errorTrackingEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ illustrationPath: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['errors', 'externalUrl', 'loading']),
+ },
+ created() {
+ if (this.errorTrackingEnabled) {
+ this.startPolling(this.indexPath);
+ }
+ },
+ methods: {
+ ...mapActions(['startPolling']),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div v-if="errorTrackingEnabled">
+ <div v-if="loading" class="py-3"><gl-loading-icon :size="3" /></div>
+ <div v-else>
+ <div class="d-flex justify-content-end">
+ <gl-button class="my-3 ml-auto" variant="primary" :href="externalUrl" target="_blank"
+ >View in Sentry <icon name="external-link" />
+ </gl-button>
+ </div>
+ <gl-table
+ :items="errors"
+ :fields="$options.fields"
+ :show-empty="true"
+ :empty-text="__('No errors to display')"
+ >
+ <template slot="HEAD_events" slot-scope="data">
+ <div class="text-right">{{ data.label }}</div>
+ </template>
+ <template slot="HEAD_users" slot-scope="data">
+ <div class="text-right">{{ data.label }}</div>
+ </template>
+ <template slot="error" slot-scope="errors">
+ <div class="d-flex flex-column">
+ <div class="d-flex">
+ <gl-link :href="errors.item.externalUrl" class="d-flex text-dark" target="_blank">
+ <strong>{{ errors.item.title.trim() }}</strong>
+ <icon name="external-link" class="ml-1" />
+ </gl-link>
+ <span class="text-secondary ml-2">{{ errors.item.culprit }}</span>
+ </div>
+ {{ errors.item.message || __('No details available') }}
+ </div>
+ </template>
+
+ <template slot="events" slot-scope="errors">
+ <div class="text-right">{{ errors.item.count }}</div>
+ </template>
+
+ <template slot="users" slot-scope="errors">
+ <div class="text-right">{{ errors.item.userCount }}</div>
+ </template>
+
+ <template slot="lastSeen" slot-scope="errors">
+ <div class="d-flex align-items-center">
+ <icon name="calendar" css-classes="text-secondary mr-1" />
+ <time-ago :time="errors.item.lastSeen" class="text-secondary" />
+ </div>
+ </template>
+ </gl-table>
+ </div>
+ </div>
+ <div v-else>
+ <gl-empty-state
+ :title="__('Get started with error tracking')"
+ :description="__('Monitor your errors by integrating with Sentry')"
+ :primary-button-text="__('Enable error tracking')"
+ :primary-button-link="enableErrorTrackingLink"
+ :svg-path="illustrationPath"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/error_tracking/index.js b/app/assets/javascripts/error_tracking/index.js
new file mode 100644
index 00000000000..3d609448efe
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/index.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import store from './store';
+import ErrorTrackingList from './components/error_tracking_list.vue';
+
+export default () => {
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#js-error_tracking',
+ components: {
+ ErrorTrackingList,
+ },
+ store,
+ render(createElement) {
+ const domEl = document.querySelector(this.$options.el);
+ const { indexPath, enableErrorTrackingLink, illustrationPath } = domEl.dataset;
+ let { errorTrackingEnabled } = domEl.dataset;
+
+ errorTrackingEnabled = parseBoolean(errorTrackingEnabled);
+
+ return createElement('error-tracking-list', {
+ props: {
+ indexPath,
+ enableErrorTrackingLink,
+ errorTrackingEnabled,
+ illustrationPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/error_tracking/services/index.js b/app/assets/javascripts/error_tracking/services/index.js
new file mode 100644
index 00000000000..ab89521dc46
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/services/index.js
@@ -0,0 +1,7 @@
+import axios from '~/lib/utils/axios_utils';
+
+export default {
+ getErrorList({ endpoint }) {
+ return axios.get(endpoint);
+ },
+};
diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js
new file mode 100644
index 00000000000..2e192c958ba
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/actions.js
@@ -0,0 +1,31 @@
+import Service from '../services';
+import * as types from './mutation_types';
+import createFlash from '~/flash';
+import Poll from '~/lib/utils/poll';
+import { __ } from '~/locale';
+
+let eTagPoll;
+
+export function startPolling({ commit }, endpoint) {
+ eTagPoll = new Poll({
+ resource: Service,
+ method: 'getErrorList',
+ data: { endpoint },
+ successCallback: ({ data }) => {
+ if (!data) {
+ return;
+ }
+ commit(types.SET_ERRORS, data.errors);
+ commit(types.SET_EXTERNAL_URL, data.external_url);
+ commit(types.SET_LOADING, false);
+ },
+ errorCallback: () => {
+ commit(types.SET_LOADING, false);
+ createFlash(__('Failed to load errors from Sentry'));
+ },
+ });
+
+ eTagPoll.makeRequest();
+}
+
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking/store/index.js b/app/assets/javascripts/error_tracking/store/index.js
new file mode 100644
index 00000000000..3136682fb64
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/index.js
@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export const createStore = () =>
+ new Vuex.Store({
+ state: {
+ errors: [],
+ externalUrl: '',
+ loading: true,
+ },
+ actions,
+ mutations,
+ });
+
+export default createStore();
diff --git a/app/assets/javascripts/error_tracking/store/mutation_types.js b/app/assets/javascripts/error_tracking/store/mutation_types.js
new file mode 100644
index 00000000000..f9d77a6b08e
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/mutation_types.js
@@ -0,0 +1,3 @@
+export const SET_ERRORS = 'SET_ERRORS';
+export const SET_EXTERNAL_URL = 'SET_EXTERNAL_URL';
+export const SET_LOADING = 'SET_LOADING';
diff --git a/app/assets/javascripts/error_tracking/store/mutations.js b/app/assets/javascripts/error_tracking/store/mutations.js
new file mode 100644
index 00000000000..e4bd81db9c9
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/mutations.js
@@ -0,0 +1,14 @@
+import * as types from './mutation_types';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+
+export default {
+ [types.SET_ERRORS](state, data) {
+ state.errors = convertObjectPropsToCamelCase(data, { deep: true });
+ },
+ [types.SET_EXTERNAL_URL](state, url) {
+ state.externalUrl = url;
+ },
+ [types.SET_LOADING](state, loading) {
+ state.loading = loading;
+ },
+};
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js
index 173fe7c69de..be55e6923c6 100644
--- a/app/assets/javascripts/feature_highlight/feature_highlight.js
+++ b/app/assets/javascripts/feature_highlight/feature_highlight.js
@@ -31,12 +31,14 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
.removeAttr('disabled');
}
+const getPriority = e => parseInt(e.dataset.highlightPriority, 10) || 0;
+
export function findHighestPriorityFeature() {
let priorityFeature;
const sortedFeatureEls = [].slice
.call(document.querySelectorAll('.js-feature-highlight'))
- .sort((a, b) => (a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
+ .sort((a, b) => getPriority(b) - getPriority(a));
const [priorityFeatureEl] = sortedFeatureEls;
if (priorityFeatureEl) {
diff --git a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
index 6b1a934d3fe..19bc3313373 100644
--- a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
+++ b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
@@ -66,7 +66,7 @@ export default {
<button
type="button"
class="filtered-search-history-dropdown-item"
- @click="onItemActivated(item.text);"
+ @click="onItemActivated(item.text)"
>
<span>
<span
@@ -88,7 +88,7 @@ export default {
<button
type="button"
class="filtered-search-history-clear-button"
- @click="onRequestClearRecentSearches($event);"
+ @click="onRequestClearRecentSearches($event)"
>
Clear recent searches
</button>
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 3ac00c51df4..2b6af9060d1 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -24,6 +24,9 @@ export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
let headerHeight = 50;
export const getHeaderHeight = () => headerHeight;
+const setHeaderHeight = () => {
+ headerHeight = sidebar.offsetTop;
+};
export const isSidebarCollapsed = () =>
sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
@@ -186,7 +189,7 @@ export default () => {
});
}
- headerHeight = document.querySelector('.nav-sidebar').offsetTop;
+ requestIdleCallback(setHeaderHeight);
items.forEach(el => {
const subItems = el.querySelector('.sidebar-sub-level-items');
diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue
index 63531f1f246..968e255e1fc 100644
--- a/app/assets/javascripts/frequent_items/components/app.vue
+++ b/app/assets/javascripts/frequent_items/components/app.vue
@@ -47,6 +47,12 @@ export default {
}
eventHub.$on(`${this.namespace}-dropdownOpen`, this.dropdownOpenHandler);
+
+ // As we init it through requestIdleCallback it could be that the dropdown is already open
+ const namespaceDropdown = document.getElementById(`nav-${this.namespace}-dropdown`);
+ if (namespaceDropdown && namespaceDropdown.classList.contains('show')) {
+ this.dropdownOpenHandler();
+ }
},
beforeDestroy() {
eventHub.$off(`${this.namespace}-dropdownOpen`, this.dropdownOpenHandler);
diff --git a/app/assets/javascripts/frequent_items/index.js b/app/assets/javascripts/frequent_items/index.js
index 5157ff211dc..6263acbab8e 100644
--- a/app/assets/javascripts/frequent_items/index.js
+++ b/app/assets/javascripts/frequent_items/index.js
@@ -17,7 +17,7 @@ const frequentItemDropdowns = [
},
];
-document.addEventListener('DOMContentLoaded', () => {
+const initFrequentItemDropdowns = () => {
frequentItemDropdowns.forEach(dropdown => {
const { namespace, key } = dropdown;
const el = document.getElementById(`js-${namespace}-dropdown`);
@@ -66,4 +66,8 @@ document.addEventListener('DOMContentLoaded', () => {
},
});
});
+};
+
+document.addEventListener('DOMContentLoaded', () => {
+ requestIdleCallback(initFrequentItemDropdowns);
});
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 8178821be3d..570d3b712e0 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -221,13 +221,13 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
+ tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
data: GfmAutoComplete.defaultLoadingData,
- // eslint-disable-next-line no-template-curly-in-string
- insertTpl: '${atwho-at}${id}',
+ insertTpl: GfmAutoComplete.Issues.insertTemplateFunction,
+ skipSpecialCharacterTest: true,
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(issues) {
@@ -238,6 +238,7 @@ class GfmAutoComplete {
return {
id: i.iid,
title: sanitize(i.title),
+ reference: i.reference,
search: `${i.iid} ${i.title}`,
};
});
@@ -287,13 +288,13 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
+ tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
data: GfmAutoComplete.defaultLoadingData,
- // eslint-disable-next-line no-template-curly-in-string
- insertTpl: '${atwho-at}${id}',
+ insertTpl: GfmAutoComplete.Issues.insertTemplateFunction,
+ skipSpecialCharacterTest: true,
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(merges) {
@@ -304,6 +305,7 @@ class GfmAutoComplete {
return {
id: m.iid,
title: sanitize(m.title),
+ reference: m.reference,
search: `${m.iid} ${m.title}`,
};
});
@@ -397,7 +399,7 @@ class GfmAutoComplete {
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
- tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title);
+ tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
@@ -596,8 +598,12 @@ GfmAutoComplete.Labels = {
};
// Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = {
- templateFunction(id, title) {
- return `<li><small>${id}</small> ${_.escape(title)}</li>`;
+ insertTemplateFunction(value) {
+ // eslint-disable-next-line no-template-curly-in-string
+ return value.reference || '${atwho-at}${id}';
+ },
+ templateFunction({ id, title, reference }) {
+ return `<li><small>${reference || id}</small> ${_.escape(title)}</li>`;
},
};
// Milestones
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index f842d2d74db..f5e2e46237f 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -51,8 +51,6 @@ export default class GLForm {
// form and textarea event listeners
this.addEventListeners();
addMarkdownListeners(this.form);
- // hide discard button
- this.form.find('.js-note-discard').hide();
this.form.show();
if (this.isAutosizeable) this.setupAutosize();
}
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index 2fa7a219ea8..3d846310008 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -23,7 +23,7 @@ export default function initTodoToggle() {
});
}
-document.addEventListener('DOMContentLoaded', () => {
+function initStatusTriggers() {
const setStatusModalTriggerEl = document.querySelector('.js-set-status-modal-trigger');
const setStatusModalWrapperEl = document.querySelector('.js-set-status-modal-wrapper');
@@ -72,4 +72,8 @@ document.addEventListener('DOMContentLoaded', () => {
},
});
}
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ requestIdleCallback(initStatusTriggers);
});
diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue
index a1f66ff764d..7c769ab7fa0 100644
--- a/app/assets/javascripts/ide/components/activity_bar.vue
+++ b/app/assets/javascripts/ide/components/activity_bar.vue
@@ -45,7 +45,7 @@ export default {
data-placement="right"
type="button"
class="ide-sidebar-link js-ide-edit-mode"
- @click.prevent="changedActivityView($event, $options.activityBarViews.edit);"
+ @click.prevent="changedActivityView($event, $options.activityBarViews.edit)"
>
<icon name="code" />
</button>
@@ -62,7 +62,7 @@ export default {
data-placement="right"
type="button"
class="ide-sidebar-link js-ide-review-mode"
- @click.prevent="changedActivityView($event, $options.activityBarViews.review);"
+ @click.prevent="changedActivityView($event, $options.activityBarViews.review)"
>
<icon name="file-modified" />
</button>
@@ -79,7 +79,7 @@ export default {
data-placement="right"
type="button"
class="ide-sidebar-link js-ide-commit-mode"
- @click.prevent="changedActivityView($event, $options.activityBarViews.commit);"
+ @click.prevent="changedActivityView($event, $options.activityBarViews.commit)"
>
<icon name="commit" />
</button>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
index a1094570275..4f1260de0bc 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -87,7 +87,7 @@ export default {
},
},
discardModalText: __(
- "You will loose all the unstaged changes you've made in this project. This action cannot be undone.",
+ "You will lose all the unstaged changes you've made in this project. This action cannot be undone.",
),
};
</script>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
index 6f1ded91753..00b2d236da3 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
@@ -111,8 +111,8 @@ export default {
name="commit-message"
@scroll="handleScroll"
@input="onInput"
- @focus="updateIsFocused(true);"
- @blur="updateIsFocused(false);"
+ @focus="updateIsFocused(true)"
+ @blur="updateIsFocused(false)"
@keydown.ctrl.enter="onCtrlEnter"
@keydown.meta.enter="onCtrlEnter"
>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
index 3525084b1cb..2b44438f849 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
@@ -65,7 +65,7 @@ export default {
:disabled="disabled"
type="radio"
name="commit-action"
- @change="updateCommitAction($event.target.value);"
+ @change="updateCommitAction($event.target.value)"
/>
<span class="prepend-left-10">
<span v-if="label" class="ide-radio-label"> {{ label }} </span> <slot v-else></slot>
@@ -76,7 +76,7 @@ export default {
:placeholder="newBranchName"
type="text"
class="form-control monospace"
- @input="updateBranchName($event.target.value);"
+ @input="updateBranchName($event.target.value)"
/>
</div>
</fieldset>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
index 02c2004d495..09c9d135614 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
@@ -48,7 +48,7 @@ export default {
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click.stop.prevent="stageChange(path);"
+ @click.stop.prevent="stageChange(path)"
>
<icon :size="16" name="mobile-issue-close" class="ml-auto mr-auto" />
</button>
@@ -70,9 +70,9 @@ export default {
:header-title-text="modalTitle"
:footer-primary-button-text="__('Discard changes')"
footer-primary-button-variant="danger"
- @submit="discardFileChanges(path);"
+ @submit="discardFileChanges(path)"
>
- {{ __("You will loose all changes you've made to this file. This action cannot be undone.") }}
+ {{ __("You will lose all changes you've made to this file. This action cannot be undone.") }}
</gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
index ce41fcdb087..0567ef54ff3 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
@@ -33,7 +33,7 @@ export default {
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click.stop.prevent="unstageChange(path);"
+ @click.stop.prevent="unstageChange(path)"
>
<icon :size="16" name="redo" class="ml-auto mr-auto" />
</button>
diff --git a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
index 5f99261ec39..732fa0786b0 100644
--- a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
+++ b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
@@ -40,7 +40,7 @@ export default {
'is-active': viewer === $options.viewerTypes.mr,
}"
href="#"
- @click.prevent="changeMode($options.viewerTypes.mr);"
+ @click.prevent="changeMode($options.viewerTypes.mr)"
>
<strong class="dropdown-menu-inner-title"> {{ mergeReviewLine }} </strong>
<span class="dropdown-menu-inner-content">
@@ -54,7 +54,7 @@ export default {
'is-active': viewer === $options.viewerTypes.diff,
}"
href="#"
- @click.prevent="changeMode($options.viewerTypes.diff);"
+ @click.prevent="changeMode($options.viewerTypes.diff)"
>
<strong class="dropdown-menu-inner-title">{{ __('Reviewing') }}</strong>
<span class="dropdown-menu-inner-content">
diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue
index bb391912572..0b0cd7b75eb 100644
--- a/app/assets/javascripts/ide/components/file_finder/index.vue
+++ b/app/assets/javascripts/ide/components/file_finder/index.vue
@@ -164,7 +164,7 @@ export default {
</script>
<template>
- <div class="ide-file-finder-overlay" @mousedown.self="toggleFileFinder(false);">
+ <div class="ide-file-finder-overlay" @mousedown.self="toggleFileFinder(false)">
<div class="dropdown-menu diff-file-changes ide-file-finder show">
<div class="dropdown-input">
<input
@@ -174,8 +174,8 @@ export default {
type="search"
class="dropdown-input-field"
autocomplete="off"
- @keydown="onKeydown($event);"
- @keyup="onKeyup($event);"
+ @keydown="onKeydown($event)"
+ @keyup="onKeyup($event)"
/>
<i
:class="{
diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
index 414ea9c7d4d..343e0cca672 100644
--- a/app/assets/javascripts/ide/components/file_templates/dropdown.vue
+++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
@@ -91,7 +91,7 @@ export default {
<gl-loading-icon v-if="showLoading" :size="2" />
<ul v-else>
<li v-for="(item, index) in outputData" :key="index">
- <button type="button" @click="clickItem(item);">{{ item.name }}</button>
+ <button type="button" @click="clickItem(item)">{{ item.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index e2e0acc22b1..f1d40586903 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -84,7 +84,7 @@ export default {
<button
type="button"
class="p-0 border-0 h-50"
- @click="openRightPane($options.rightSidebarViews.pipelines);"
+ @click="openRightPane($options.rightSidebarViews.pipelines)"
>
<ci-icon
v-tooltip
diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue
index 9fc21adae7c..f93496132a4 100644
--- a/app/assets/javascripts/ide/components/ide_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_tree.vue
@@ -43,7 +43,7 @@ export default {
:show-label="false"
class="d-flex border-0 p-0 mr-3 qa-new-file"
icon="doc-new"
- @click="openNewEntryModal({ type: 'blob' });"
+ @click="openNewEntryModal({ type: 'blob' })"
/>
<upload
:show-label="false"
@@ -56,7 +56,7 @@ export default {
:show-label="false"
class="d-flex border-0 p-0"
icon="folder-new"
- @click="openNewEntryModal({ type: 'tree' });"
+ @click="openNewEntryModal({ type: 'tree' })"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue
index e8fe5fc696d..7710bfb49ec 100644
--- a/app/assets/javascripts/ide/components/jobs/detail.vue
+++ b/app/assets/javascripts/ide/components/jobs/detail.vue
@@ -75,7 +75,7 @@ export default {
<template>
<div class="ide-pipeline build-page d-flex flex-column flex-fill">
<header class="ide-job-header d-flex align-items-center">
- <button class="btn btn-default btn-sm d-flex" @click="setDetailJob(null);">
+ <button class="btn btn-default btn-sm d-flex" @click="setDetailJob(null)">
<icon name="chevron-left" /> {{ __('View jobs') }}
</button>
</header>
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index ac2b0eddfb4..2d55ffb3c65 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -84,7 +84,7 @@ export default {
:placeholder="__('Search merge requests')"
@focus="onSearchFocus"
@input="searchMergeRequests"
- @removeToken="setSearchType(null);"
+ @removeToken="setSearchType(null)"
/>
<icon :size="18" name="search" class="input-icon" />
</div>
@@ -102,7 +102,7 @@ export default {
<button
type="button"
class="btn-link d-flex align-items-center"
- @click.stop="setSearchType(searchType);"
+ @click.stop="setSearchType(searchType)"
>
<span class="d-flex append-right-default ide-search-list-current-icon">
<icon :size="18" name="search" />
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
index a50d729036f..d7a7b1b4d78 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -73,7 +73,7 @@ export default {
:aria-label="__('Create new file or directory')"
type="button"
class="rounded border-0 d-flex ide-entry-dropdown-toggle"
- @click.stop="openDropdown();"
+ @click.stop="openDropdown()"
>
<icon name="ellipsis_v" /> <icon name="arrow-down" />
</button>
@@ -85,7 +85,7 @@ export default {
class="d-flex"
icon="doc-new"
icon-classes="mr-2"
- @click="createNewItem('blob');"
+ @click="createNewItem('blob')"
/>
</li>
<li><upload :path="path" @create="createTempEntry" /></li>
@@ -95,7 +95,7 @@ export default {
class="d-flex"
icon="folder-new"
icon-classes="mr-2"
- @click="createNewItem($options.modalTypes.tree);"
+ @click="createNewItem($options.modalTypes.tree)"
/>
</li>
<li class="divider"></li>
@@ -106,7 +106,7 @@ export default {
class="d-flex"
icon="pencil"
icon-classes="mr-2"
- @click="createNewItem($options.modalTypes.rename);"
+ @click="createNewItem($options.modalTypes.rename)"
/>
</li>
<li>
@@ -115,7 +115,7 @@ export default {
class="d-flex"
icon="remove"
icon-classes="mr-2"
- @click="deleteEntry(path);"
+ @click="deleteEntry(path)"
/>
</li>
</ul>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index 63cbf41b89b..04ecd4ba4e7 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -114,7 +114,7 @@ export default {
<button
type="button"
class="btn btn-missing p-1 pr-2 pl-2"
- @click="createFromTemplate(template);"
+ @click="createFromTemplate(template)"
>
{{ template.name }}
</button>
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index 7a57ccf2dd3..2e6bd85feec 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -122,7 +122,7 @@ export default {
data-placement="left"
class="ide-sidebar-link is-right"
type="button"
- @click="clickTab($event, tab);"
+ @click="clickTab($event, tab)"
>
<icon :size="16" :name="tab.icon" />
</button>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index c13d3ec094b..94a9e87369c 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -219,7 +219,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
- @click.prevent="setFileViewMode({ file, viewMode: 'editor' });"
+ @click.prevent="setFileViewMode({ file, viewMode: 'editor' })"
>
<template v-if="viewer === $options.viewerTypes.edit">
{{ __('Edit') }}
@@ -233,7 +233,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
- @click.prevent="setFileViewMode({ file, viewMode: 'preview' });"
+ @click.prevent="setFileViewMode({ file, viewMode: 'preview' })"
>
{{ file.previewMode.previewTitle }}
</a>
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index 4b87b83db8a..f6aa2295844 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -74,7 +74,7 @@ export default {
active: tab.active,
disabled: tab.pending,
}"
- @click="clickFile(tab);"
+ @click="clickFile(tab)"
@mouseover="mouseOverTab"
@mouseout="mouseOutTab"
>
@@ -88,7 +88,7 @@ export default {
:disabled="tab.pending"
type="button"
class="multi-file-tab-close"
- @click.stop.prevent="closeFile(tab);"
+ @click.stop.prevent="closeFile(tab)"
>
<icon v-if="!showChangedIcon" :size="12" name="close" />
<changed-file-icon v-else :file="tab" />
diff --git a/app/assets/javascripts/ide/components/resizable_panel.vue b/app/assets/javascripts/ide/components/resizable_panel.vue
index a89de56ab5c..7277fcb7617 100644
--- a/app/assets/javascripts/ide/components/resizable_panel.vue
+++ b/app/assets/javascripts/ide/components/resizable_panel.vue
@@ -78,8 +78,8 @@ export default {
:min-size="minSize"
:max-size="$options.maxSize"
:side="side === 'right' ? 'left' : 'right'"
- @resize-start="setResizingStatus(true);"
- @resize-end="setResizingStatus(false);"
+ @resize-start="setResizingStatus(true)"
+ @resize-end="setResizingStatus(false)"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/shared/tokened_input.vue b/app/assets/javascripts/ide/components/shared/tokened_input.vue
index f58e08c2cc9..de3e71dad92 100644
--- a/app/assets/javascripts/ide/components/shared/tokened_input.vue
+++ b/app/assets/javascripts/ide/components/shared/tokened_input.vue
@@ -76,8 +76,8 @@ export default {
<button
class="selectable btn-blank"
type="button"
- @click.stop="removeToken(token);"
- @keyup.delete="removeToken(token);"
+ @click.stop="removeToken(token)"
+ @keyup.delete="removeToken(token)"
>
<div class="value-container rounded">
<div class="value">{{ token.label }}</div>
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index 3f6101e58f4..229ef168926 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -73,7 +73,7 @@ router.beforeEach((to, from, next) => {
projectId: to.params.project,
})
.then(() => {
- const basePath = to.params[0] || '';
+ const basePath = to.params.pathMatch || '';
const projectId = `${to.params.namespace}/${to.params.project}`;
const branchId = to.params.branchid;
const mergeRequestId = to.params.mrid;
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index 6351948f750..5a2b680c2f7 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -10,13 +10,20 @@ import { parseBoolean } from '../lib/utils/common_utils';
Vue.use(Translate);
/**
+ * Function that receives the default store and returns an extended one.
+ * @callback extendStoreCallback
+ * @param {Vuex.Store} store
+ * @param {Element} el
+ */
+
+/**
* Initialize the IDE on the given element.
*
* @param {Element} el - The element that will contain the IDE.
* @param {Object} options - Extra options for the IDE (Used by EE).
* @param {Component} options.rootComponent -
* Component that overrides the root component.
- * @param {(store:Vuex.Store, el:Element) => Vuex.Store} options.extendStore -
+ * @param {extendStoreCallback} options.extendStore -
* Function that receives the default store and returns an extended one.
*/
export function initIde(el, options = {}) {
diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js
index 60bddb34977..a15f04075d9 100644
--- a/app/assets/javascripts/ide/lib/common/model.js
+++ b/app/assets/javascripts/ide/lib/common/model.js
@@ -13,12 +13,12 @@ export default class Model {
(this.originalModel = monacoEditor.createModel(
head ? head.content : this.file.raw,
undefined,
- new Uri(false, false, `original/${this.path}`),
+ new Uri('gitlab', false, `original/${this.path}`),
)),
(this.model = monacoEditor.createModel(
this.content,
undefined,
- new Uri(false, false, this.path),
+ new Uri('gitlab', false, this.path),
)),
);
if (this.file.mrChange) {
@@ -26,7 +26,7 @@ export default class Model {
(this.baseModel = monacoEditor.createModel(
this.file.baseRaw,
undefined,
- new Uri(false, false, `target/${this.path}`),
+ new Uri('gitlab', false, `target/${this.path}`),
)),
);
}
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
index e0f55518eef..7076a79dd5d 100644
--- a/app/assets/javascripts/jobs/components/commit_block.vue
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -45,7 +45,7 @@ export default {
/>
<span v-if="mergeRequest">
- {{ __('in') }}
+ in
<gl-link :href="mergeRequest.path" class="js-link-commit link-commit"
>!{{ mergeRequest.iid }}</gl-link
>
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 786ab16992d..d473d6a482d 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -80,7 +80,6 @@ export default {
'hasError',
]),
...mapGetters([
- 'headerActions',
'headerTime',
'shouldRenderCalloutMessage',
'shouldRenderTriggeredLabel',
@@ -202,7 +201,6 @@ export default {
:item-id="job.id"
:time="headerTime"
:user="job.user"
- :actions="headerActions"
:has-sidebar-button="true"
:should-render-triggered-label="shouldRenderTriggeredLabel"
:item-name="__('Job')"
@@ -240,14 +238,19 @@ export default {
<div
v-if="job.archived"
ref="sticky"
- class="js-archived-job prepend-top-default archived-sticky sticky-top"
+ class="js-archived-job prepend-top-default archived-job"
+ :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 -->
- <div v-if="hasTrace" class="build-trace-container">
+ <div
+ v-if="hasTrace"
+ class="build-trace-container"
+ :class="{ 'prepend-top-default': !job.archived }"
+ >
<log-top-bar
:class="{
'sidebar-expanded': isSidebarOpen,
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index ad3e7dabc79..a2141dc3760 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -48,8 +48,7 @@ export default {
return `${this.job.runner.description} (#${this.job.runner.id})`;
},
retryButtonClass() {
- let className =
- 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
+ let className = 'js-retry-button btn btn-retry';
className +=
this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
return className;
@@ -110,24 +109,27 @@ export default {
<aside class="right-sidebar build-sidebar" data-offset-top="101" data-spy="affix">
<div class="sidebar-container">
<div class="blocks-container">
- <div class="block d-flex align-items-center">
- <h4 class="flex-grow-1 prepend-top-8 m-0">{{ job.name }}</h4>
- <gl-link
- v-if="job.retry_path"
- :class="retryButtonClass"
- :href="job.retry_path"
- data-method="post"
- rel="nofollow"
- >{{ __('Retry') }}</gl-link
- >
- <gl-link
- v-if="job.terminal_path"
- :href="job.terminal_path"
- class="js-terminal-link pull-right btn btn-primary btn-inverted visible-md-block visible-lg-block"
- target="_blank"
- >
- {{ __('Debug') }} <icon name="external-link" />
- </gl-link>
+ <div class="block d-flex flex-nowrap align-items-center">
+ <h4 class="my-0 mr-2">{{ job.name }}</h4>
+ <div class="flex-grow-1 flex-shrink-0 text-right">
+ <gl-link
+ v-if="job.retry_path"
+ :class="retryButtonClass"
+ :href="job.retry_path"
+ data-method="post"
+ rel="nofollow"
+ >{{ __('Retry') }}</gl-link
+ >
+ <gl-link
+ v-if="job.cancel_path"
+ :href="job.cancel_path"
+ class="js-cancel-job btn btn-default"
+ data-method="post"
+ rel="nofollow"
+ >{{ __('Cancel') }}</gl-link
+ >
+ </div>
+
<gl-button
:aria-label="__('Toggle Sidebar')"
type="button"
@@ -137,22 +139,24 @@ export default {
<i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"></i>
</gl-button>
</div>
- <div v-if="job.retry_path || job.new_issue_path" class="block retry-link">
+
+ <div v-if="job.terminal_path || job.new_issue_path" class="block retry-link">
<gl-link
v-if="job.new_issue_path"
:href="job.new_issue_path"
- class="js-new-issue btn btn-success btn-inverted"
+ class="js-new-issue btn btn-success btn-inverted float-left mr-2"
>{{ __('New issue') }}</gl-link
>
<gl-link
- v-if="job.retry_path"
- :href="job.retry_path"
- class="js-retry-job btn btn-inverted-secondary"
- data-method="post"
- rel="nofollow"
- >{{ __('Retry') }}</gl-link
+ v-if="job.terminal_path"
+ :href="job.terminal_path"
+ class="js-terminal-link btn btn-primary btn-inverted visible-md-block visible-lg-block float-left"
+ target="_blank"
>
+ {{ __('Debug') }} <icon name="external-link" :size="14" />
+ </gl-link>
</div>
+
<div :class="{ block: renderBlock }">
<detail-row
v-if="job.duration"
@@ -193,16 +197,6 @@ export default {
tag
}}</span>
</p>
-
- <div v-if="job.cancel_path" class="btn-group prepend-top-5" role="group">
- <gl-link
- :href="job.cancel_path"
- class="js-cancel-job btn btn-sm btn-default"
- data-method="post"
- rel="nofollow"
- >{{ __('Cancel') }}</gl-link
- >
- </div>
</div>
<artifacts-block v-if="hasArtifact" :artifact="job.artifact" />
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index 7f79e92067f..91332c21b52 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -55,7 +55,7 @@ export default {
<ul class="dropdown-menu">
<li v-for="stage in stages" :key="stage.name">
- <button type="button" class="js-stage-item stage-item" @click="onStageClick(stage);">
+ <button type="button" class="js-stage-item stage-item" @click="onStageClick(stage)">
{{ stage.name }}
</button>
</li>
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index 35e92b0b5d9..98911717381 100644
--- a/app/assets/javascripts/jobs/store/getters.js
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -1,22 +1,6 @@
import _ from 'underscore';
-import { __ } from '~/locale';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
-export const headerActions = state => {
- if (state.job.new_issue_path) {
- return [
- {
- label: __('New issue'),
- path: state.job.new_issue_path,
- cssClass:
- 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
- type: 'link',
- },
- ];
- }
- return [];
-};
-
export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
export const shouldRenderCalloutMessage = state =>
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index b8c3c237eb3..4314e5e1afb 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -11,48 +11,53 @@ function hideEndFade($scrollingTabs) {
});
}
+function initDeferred() {
+ $(document).trigger('init.scrolling-tabs');
+}
+
export default function initLayoutNav() {
const contextualSidebar = new ContextualSidebar();
contextualSidebar.bindEvents();
initFlyOutNav();
- $(document)
- .on('init.scrolling-tabs', () => {
- const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
- $scrollingTabs.addClass('is-initialized');
+ // We need to init it on DomContentLoaded as others could also call it
+ $(document).on('init.scrolling-tabs', () => {
+ const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
+ $scrollingTabs.addClass('is-initialized');
- $(window)
- .on('resize.nav', () => {
- hideEndFade($scrollingTabs);
- })
- .trigger('resize.nav');
+ $(window)
+ .on('resize.nav', () => {
+ hideEndFade($scrollingTabs);
+ })
+ .trigger('resize.nav');
- $scrollingTabs.on('scroll', function tabsScrollEvent() {
- const $this = $(this);
- const currentPosition = $this.scrollLeft();
- const maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
+ $scrollingTabs.on('scroll', function tabsScrollEvent() {
+ const $this = $(this);
+ const currentPosition = $this.scrollLeft();
+ const maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
- $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
- $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
- });
+ $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
+ $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
+ });
- $scrollingTabs.each(function scrollTabsEachLoop() {
- const $this = $(this);
- const scrollingTabWidth = $this.width();
- const $active = $this.find('.active');
- const activeWidth = $active.width();
+ $scrollingTabs.each(function scrollTabsEachLoop() {
+ const $this = $(this);
+ const scrollingTabWidth = $this.width();
+ const $active = $this.find('.active');
+ const activeWidth = $active.width();
- if ($active.length) {
- const offset = $active.offset().left + activeWidth;
+ if ($active.length) {
+ const offset = $active.offset().left + activeWidth;
- if (offset > scrollingTabWidth - 30) {
- const scrollLeft = offset - scrollingTabWidth / 2 - activeWidth / 2;
+ if (offset > scrollingTabWidth - 30) {
+ const scrollLeft = offset - scrollingTabWidth / 2 - activeWidth / 2;
- $this.scrollLeft(scrollLeft);
- }
+ $this.scrollLeft(scrollLeft);
}
- });
- })
- .trigger('init.scrolling-tabs');
+ }
+ });
+ });
+
+ requestIdleCallback(initDeferred);
}
diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js
index ee01a73a6e8..66f25b622e0 100644
--- a/app/assets/javascripts/lazy_loader.js
+++ b/app/assets/javascripts/lazy_loader.js
@@ -163,6 +163,7 @@ export default class LazyLoader {
img.removeAttribute('data-src');
img.classList.remove('lazy');
img.classList.add('js-lazy-loaded');
+ img.classList.add('qa-js-lazy-loaded');
}
}
}
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 9e22cdc04e9..3b6a57dad44 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,3 +1,7 @@
+/**
+ * @module common-utils
+ */
+
import $ from 'jquery';
import axios from './axios_utils';
import { getLocationHash } from './url_utility';
@@ -118,12 +122,13 @@ export const handleLocationHash = () => {
// Check if element scrolled into viewport from above or below
// Courtesy http://stackoverflow.com/a/7557433/414749
-export const isInViewport = el => {
+export const isInViewport = (el, offset = {}) => {
const rect = el.getBoundingClientRect();
+ const { top, left } = offset;
return (
- rect.top >= 0 &&
- rect.left >= 0 &&
+ rect.top >= (top || 0) &&
+ rect.left >= (left || 0) &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
@@ -425,13 +430,14 @@ export const historyPushState = newUrl => {
};
/**
- * Returns true for a String "true" and false otherwise.
- * This is the opposite of Boolean(...).toString()
+ * Returns true for a String value of "true" and false otherwise.
+ * This is the opposite of Boolean(...).toString().
+ * `parseBoolean` is idempotent.
*
* @param {String} value
* @returns {Boolean}
*/
-export const parseBoolean = value => value === 'true';
+export const parseBoolean = value => (value && value.toString()) === 'true';
/**
* Converts permission provided as strings to booleans.
@@ -449,10 +455,16 @@ export const convertPermissionToBoolean = permission => {
};
/**
+ * @callback backOffCallback
+ * @param {Function} next
+ * @param {Function} stop
+ */
+
+/**
* Back Off exponential algorithm
* backOff :: (Function<next, stop>, Number) -> Promise<Any, Error>
*
- * @param {Function<next, stop>} fn function to be called
+ * @param {backOffCallback} fn function to be called
* @param {Number} timeout
* @return {Promise<Any, Error>}
* @example
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 59007d5950e..d3fe8f77bd4 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -8,6 +8,14 @@ import { languageCode, s__ } from '../../locale';
window.timeago = timeago;
/**
+ * This method allows you to create new Date instance from existing
+ * date instance without keeping the reference.
+ *
+ * @param {Date} date
+ */
+export const newDate = date => (date instanceof Date ? new Date(date.getTime()) : new Date());
+
+/**
* Returns i18n month names array.
* If `abbreviated` is provided, returns abbreviated
* name.
@@ -79,44 +87,67 @@ let timeagoInstance;
*/
export const getTimeago = () => {
if (!timeagoInstance) {
- const localeRemaining = (number, index) =>
- [
- [s__('Timeago|just now'), s__('Timeago|right now')],
- [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')],
- [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
- [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
- [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
- [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')],
- [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')],
- [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
- [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')],
- [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
- [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')],
- [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
- [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')],
- [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
- ][index];
-
- const locale = (number, index) =>
- [
- [s__('Timeago|just now'), s__('Timeago|right now')],
- [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')],
- [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
- [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
- [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')],
- [s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')],
- [s__('Timeago|1 day ago'), s__('Timeago|in 1 day')],
- [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
- [s__('Timeago|1 week ago'), s__('Timeago|in 1 week')],
- [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
- [s__('Timeago|1 month ago'), s__('Timeago|in 1 month')],
- [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
- [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')],
- [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
- ][index];
-
- timeago.register(timeagoLanguageCode, locale);
- timeago.register(`${timeagoLanguageCode}-remaining`, localeRemaining);
+ const memoizedLocaleRemaining = () => {
+ const cache = [];
+
+ const timeAgoLocaleRemaining = [
+ () => [s__('Timeago|just now'), s__('Timeago|right now')],
+ () => [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')],
+ () => [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
+ () => [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
+ () => [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
+ () => [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')],
+ () => [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')],
+ () => [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
+ () => [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')],
+ () => [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
+ () => [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')],
+ () => [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
+ () => [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')],
+ () => [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
+ ];
+
+ return (number, index) => {
+ if (cache[index]) {
+ return cache[index];
+ }
+ cache[index] = timeAgoLocaleRemaining[index] && timeAgoLocaleRemaining[index]();
+ return cache[index];
+ };
+ };
+
+ const memoizedLocale = () => {
+ const cache = [];
+
+ const timeAgoLocale = [
+ () => [s__('Timeago|just now'), s__('Timeago|right now')],
+ () => [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')],
+ () => [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
+ () => [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
+ () => [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')],
+ () => [s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')],
+ () => [s__('Timeago|1 day ago'), s__('Timeago|in 1 day')],
+ () => [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
+ () => [s__('Timeago|1 week ago'), s__('Timeago|in 1 week')],
+ () => [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
+ () => [s__('Timeago|1 month ago'), s__('Timeago|in 1 month')],
+ () => [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
+ () => [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')],
+ () => [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
+ ];
+
+ return (number, index) => {
+ if (cache[index]) {
+ return cache[index];
+ }
+ cache[index] = timeAgoLocale[index] && timeAgoLocale[index]();
+ return cache[index];
+ };
+ };
+
+ timeago.register(timeagoLanguageCode, memoizedLocale());
+ timeago.register(`${timeagoLanguageCode}-remaining`, memoizedLocaleRemaining());
+
timeagoInstance = timeago();
}
@@ -124,35 +155,28 @@ export const getTimeago = () => {
};
/**
- * For the given element, renders a timeago instance.
- * @param {jQuery} $els
- */
-export const renderTimeago = $els => {
- const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
-
- // timeago.js sets timeouts internally for each timeago value to be updated in real time
- getTimeago().render(timeagoEls, timeagoLanguageCode);
-};
-
-/**
* For the given elements, sets a tooltip with a formatted date.
- * @param {jQuery}
+ * @param {JQuery} $timeagoEls
* @param {Boolean} setTimeago
*/
export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
- $timeagoEls.each((i, el) => {
- if (setTimeago) {
+ getTimeago().render($timeagoEls, timeagoLanguageCode);
+
+ if (!setTimeago) {
+ return;
+ }
+
+ function addTimeAgoTooltip() {
+ $timeagoEls.each((i, el) => {
// Recreate with custom template
$(el).tooltip({
template:
'<div class="tooltip local-timeago" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>',
});
- }
-
- el.classList.add('js-timeago-render');
- });
+ });
+ }
- renderTimeago($timeagoEls);
+ requestIdleCallback(addTimeAgoTooltip);
};
/**
@@ -321,23 +345,35 @@ export const getSundays = date => {
/**
* Returns list of Dates representing a timeframe of months from startDate and length
+ * This method also supports going back in time when `length` is negative number
*
- * @param {Date} startDate
+ * @param {Date} initialStartDate
* @param {Number} length
*/
-export const getTimeframeWindowFrom = (startDate, length) => {
- if (!(startDate instanceof Date) || !length) {
+export const getTimeframeWindowFrom = (initialStartDate, length) => {
+ if (!(initialStartDate instanceof Date) || !length) {
return [];
}
+ const startDate = newDate(initialStartDate);
+ const moveMonthBy = length > 0 ? 1 : -1;
+
+ startDate.setDate(1);
+ startDate.setHours(0, 0, 0, 0);
+
// Iterate and set date for the size of length
// and push date reference to timeframe list
- const timeframe = new Array(length)
- .fill()
- .map((val, i) => new Date(startDate.getFullYear(), startDate.getMonth() + i, 1));
+ const timeframe = new Array(Math.abs(length)).fill().map(() => {
+ const currentMonth = startDate.getTime();
+ startDate.setMonth(startDate.getMonth() + moveMonthBy);
+ return new Date(currentMonth);
+ });
// Change date of last timeframe item to last date of the month
- timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1]));
+ // when length is positive
+ if (length > 0) {
+ timeframe[timeframe.length - 1].setDate(totalDaysInMonth(timeframe[timeframe.length - 1]));
+ }
return timeframe;
};
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 1254ec798a6..84a617acb42 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -8,6 +8,10 @@ function selectedText(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd);
}
+function addBlockTags(blockTag, selected) {
+ return `${blockTag}\n${selected}\n${blockTag}`;
+}
+
function lineBefore(text, textarea) {
var split;
split = text
@@ -24,19 +28,45 @@ function lineAfter(text, textarea) {
.split('\n')[0];
}
+function editorBlockTagText(text, blockTag, selected, editor) {
+ const lines = text.split('\n');
+ const selectionRange = editor.getSelectionRange();
+ const shouldRemoveBlock =
+ lines[selectionRange.start.row - 1] === blockTag &&
+ lines[selectionRange.end.row + 1] === blockTag;
+
+ if (shouldRemoveBlock) {
+ if (blockTag !== null) {
+ // ace is globally defined
+ // eslint-disable-next-line no-undef
+ const { Range } = ace.require('ace/range');
+ const lastLine = lines[selectionRange.end.row + 1];
+ const rangeWithBlockTags = new Range(
+ lines[selectionRange.start.row - 1],
+ 0,
+ selectionRange.end.row + 1,
+ lastLine.length,
+ );
+ editor.getSelection().setSelectionRange(rangeWithBlockTags);
+ }
+ return selected;
+ }
+ return addBlockTags(blockTag, selected);
+}
+
function blockTagText(text, textArea, blockTag, selected) {
- const before = lineBefore(text, textArea);
- const after = lineAfter(text, textArea);
- if (before === blockTag && after === blockTag) {
+ const shouldRemoveBlock =
+ lineBefore(text, textArea) === blockTag && lineAfter(text, textArea) === blockTag;
+
+ if (shouldRemoveBlock) {
// To remove the block tag we have to select the line before & after
if (blockTag != null) {
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
}
return selected;
- } else {
- return blockTag + '\n' + selected + '\n' + blockTag;
}
+ return addBlockTags(blockTag, selected);
}
function moveCursor({
@@ -46,33 +76,48 @@ function moveCursor({
positionBetweenTags,
removedLastNewLine,
select,
+ editor,
+ editorSelectionStart,
+ editorSelectionEnd,
}) {
var pos;
- if (!textArea.setSelectionRange) {
+ if (textArea && !textArea.setSelectionRange) {
return;
}
if (select && select.length > 0) {
- // calculate the part of the text to be selected
- const startPosition = textArea.selectionStart - (tag.length - tag.indexOf(select));
- const endPosition = startPosition + select.length;
- return textArea.setSelectionRange(startPosition, endPosition);
- }
- if (textArea.selectionStart === textArea.selectionEnd) {
- if (positionBetweenTags) {
- pos = textArea.selectionStart - tag.length;
- } else {
- pos = textArea.selectionStart;
+ if (textArea) {
+ // calculate the part of the text to be selected
+ const startPosition = textArea.selectionStart - (tag.length - tag.indexOf(select));
+ const endPosition = startPosition + select.length;
+ return textArea.setSelectionRange(startPosition, endPosition);
+ } else if (editor) {
+ editor.navigateLeft(tag.length - tag.indexOf(select));
+ editor.getSelection().selectAWord();
+ return;
}
+ }
+ if (textArea) {
+ if (textArea.selectionStart === textArea.selectionEnd) {
+ if (positionBetweenTags) {
+ pos = textArea.selectionStart - tag.length;
+ } else {
+ pos = textArea.selectionStart;
+ }
- if (removedLastNewLine) {
- pos -= 1;
- }
+ if (removedLastNewLine) {
+ pos -= 1;
+ }
- if (cursorOffset) {
- pos -= cursorOffset;
- }
+ if (cursorOffset) {
+ pos -= cursorOffset;
+ }
- return textArea.setSelectionRange(pos, pos);
+ return textArea.setSelectionRange(pos, pos);
+ }
+ } else if (editor && editorSelectionStart.row === editorSelectionEnd.row) {
+ if (positionBetweenTags) {
+ editor.navigateLeft(tag.length);
+ }
}
}
@@ -85,6 +130,7 @@ export function insertMarkdownText({
selected = '',
wrap,
select,
+ editor,
}) {
var textToInsert,
selectedSplit,
@@ -92,11 +138,20 @@ export function insertMarkdownText({
removedLastNewLine,
removedFirstNewLine,
currentLineEmpty,
- lastNewLine;
+ lastNewLine,
+ editorSelectionStart,
+ editorSelectionEnd;
removedLastNewLine = false;
removedFirstNewLine = false;
currentLineEmpty = false;
+ if (editor) {
+ const selectionRange = editor.getSelectionRange();
+
+ editorSelectionStart = selectionRange.start;
+ editorSelectionEnd = selectionRange.end;
+ }
+
// check for link pattern and selected text is an URL
// if so fill in the url part instead of the text part of the pattern.
if (tag === LINK_TAG_PATTERN) {
@@ -119,14 +174,27 @@ export function insertMarkdownText({
}
// Remove the last newline
- if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
- removedLastNewLine = true;
- selected = selected.replace(/\n$/, '');
+ if (textArea) {
+ if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
+ removedLastNewLine = true;
+ selected = selected.replace(/\n$/, '');
+ }
+ } else if (editor) {
+ if (editorSelectionStart.row !== editorSelectionEnd.row) {
+ removedLastNewLine = true;
+ selected = selected.replace(/\n$/, '');
+ }
}
selectedSplit = selected.split('\n');
- if (!wrap) {
+ if (editor && !wrap) {
+ lastNewLine = editor.getValue().split('\n')[editorSelectionStart.row];
+
+ if (/^\s*$/.test(lastNewLine)) {
+ currentLineEmpty = true;
+ }
+ } else if (textArea && !wrap) {
lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n');
// Check whether the current line is empty or consists only of spaces(=handle as empty)
@@ -135,13 +203,19 @@ export function insertMarkdownText({
}
}
- startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : '';
+ const isBeginning =
+ (textArea && textArea.selectionStart === 0) ||
+ (editor && editorSelectionStart.column === 0 && editorSelectionStart.row === 0);
+
+ startChar = !wrap && !currentLineEmpty && !isBeginning ? '\n' : '';
const textPlaceholder = '{text}';
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
if (blockTag != null && blockTag !== '') {
- textToInsert = blockTagText(text, textArea, blockTag, selected);
+ textToInsert = editor
+ ? editorBlockTagText(text, blockTag, selected, editor)
+ : blockTagText(text, textArea, blockTag, selected);
} else {
textToInsert = selectedSplit
.map(function(val) {
@@ -170,7 +244,11 @@ export function insertMarkdownText({
textToInsert += '\n';
}
- insertText(textArea, textToInsert);
+ if (editor) {
+ editor.insert(textToInsert);
+ } else {
+ insertText(textArea, textToInsert);
+ }
return moveCursor({
textArea,
tag: tag.replace(textPlaceholder, selected),
@@ -178,6 +256,9 @@ export function insertMarkdownText({
positionBetweenTags: wrap && selected.length === 0,
removedLastNewLine,
select,
+ editor,
+ editorSelectionStart,
+ editorSelectionEnd,
});
}
@@ -217,6 +298,25 @@ export function addMarkdownListeners(form) {
});
}
+export function addEditorMarkdownListeners(editor) {
+ $('.js-md')
+ .off('click')
+ .on('click', function(e) {
+ const { mdTag, mdBlock, mdPrepend, mdSelect } = $(e.currentTarget).data();
+
+ insertMarkdownText({
+ tag: mdTag,
+ blockTag: mdBlock,
+ wrap: !mdPrepend,
+ select: mdSelect,
+ selected: editor.getSelectedText(),
+ text: editor.getValue(),
+ editor,
+ });
+ editor.focus();
+ });
+}
+
export function removeMarkdownListeners(form) {
return $('.js-md', form).off('click');
}
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 7cc7cd6d20e..c49b1bb5a2f 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -72,6 +72,29 @@ export const truncate = (string, maxLength) => `${string.substr(0, maxLength - 3
*/
export const truncateSha = sha => sha.substr(0, 8);
+const ELLIPSIS_CHAR = '…';
+export const truncatePathMiddleToLength = (text, maxWidth) => {
+ let returnText = text;
+ let ellipsisCount = 0;
+
+ while (returnText.length >= maxWidth) {
+ const textSplit = returnText.split('/').filter(s => s !== ELLIPSIS_CHAR);
+ const middleIndex = Math.floor(textSplit.length / 2);
+
+ returnText = textSplit
+ .slice(0, middleIndex)
+ .concat(
+ new Array(ellipsisCount + 1).fill().map(() => ELLIPSIS_CHAR),
+ textSplit.slice(middleIndex + 1),
+ )
+ .join('/');
+
+ ellipsisCount += 1;
+ }
+
+ return returnText;
+};
+
/**
* Capitalizes first character
*
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 9850f7ce782..4ba84589705 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -42,22 +42,35 @@ export function mergeUrlParams(params, url) {
return `${urlparts[1]}?${query}${urlparts[3]}`;
}
-export function removeParamQueryString(url, param) {
- const decodedUrl = decodeURIComponent(url);
- const urlVariables = decodedUrl.split('&');
-
- return urlVariables.filter(variable => variable.indexOf(param) === -1).join('&');
-}
-
-export function removeParams(params, source = window.location.href) {
- const url = document.createElement('a');
- url.href = source;
+/**
+ * Removes specified query params from the url by returning a new url string that no longer
+ * includes the param/value pair. If no url is provided, `window.location.href` is used as
+ * the default value.
+ *
+ * @param {string[]} params - the query param names to remove
+ * @param {string} [url=windowLocation().href] - url from which the query param will be removed
+ * @returns {string} A copy of the original url but without the query param
+ */
+export function removeParams(params, url = window.location.href) {
+ const [rootAndQuery, fragment] = url.split('#');
+ const [root, query] = rootAndQuery.split('?');
+
+ if (!query) {
+ return url;
+ }
- params.forEach(param => {
- url.search = removeParamQueryString(url.search, param);
- });
+ const encodedParams = params.map(param => encodeURIComponent(param));
+ const updatedQuery = query
+ .split('&')
+ .filter(paramPair => {
+ const [foundParam] = paramPair.split('=');
+ return encodedParams.indexOf(foundParam) < 0;
+ })
+ .join('&');
- return url.href;
+ const writableQuery = updatedQuery.length > 0 ? `?${updatedQuery}` : '';
+ const writableFragment = fragment ? `#${fragment}` : '';
+ return `${root}${writableQuery}${writableFragment}`;
}
export function getLocationHash(url = window.location.href) {
@@ -66,6 +79,20 @@ export function getLocationHash(url = window.location.href) {
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
}
+/**
+ * Apply the fragment to the given url by returning a new url string that includes
+ * the fragment. If the given url already contains a fragment, the original fragment
+ * will be removed.
+ *
+ * @param {string} url - url to which the fragment will be applied
+ * @param {string} fragment - fragment to append
+ */
+export const setUrlFragment = (url, fragment) => {
+ const [rootUrl] = url.split('#');
+ const encodedFragment = encodeURIComponent(fragment.replace(/^#/, ''));
+ return `${rootUrl}#${encodedFragment}`;
+};
+
export function visitUrl(url, external = false) {
if (external) {
// Simulate `target="blank" rel="noopener noreferrer"`
diff --git a/app/assets/javascripts/locale/sprintf.js b/app/assets/javascripts/locale/sprintf.js
index 5246c49842e..68b64a3a16a 100644
--- a/app/assets/javascripts/locale/sprintf.js
+++ b/app/assets/javascripts/locale/sprintf.js
@@ -4,8 +4,8 @@ import _ from 'underscore';
Very limited implementation of sprintf supporting only named parameters.
@param input (translated) text with parameters (e.g. '%{num_users} users use us')
- @param parameters object mapping parameter names to values (e.g. { num_users: 5 })
- @param escapeParameters whether parameter values should be escaped (see http://underscorejs.org/#escape)
+ @param {Object} parameters object mapping parameter names to values (e.g. { num_users: 5 })
+ @param {Boolean} escapeParameters whether parameter values should be escaped (see http://underscorejs.org/#escape)
@returns {String} the text with parameters replaces (e.g. '5 users use us')
@see https://ruby-doc.org/core-2.3.3/Kernel.html#method-i-sprintf
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index c866e8d180a..4ba3543f9b2 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -66,15 +66,11 @@ gl.lazyLoader = new LazyLoader({
observerNode: '#content-body',
});
-document.addEventListener('DOMContentLoaded', () => {
+// Put all initialisations here that can also wait after everything is rendered and ready
+function deferredInitialisation() {
const $body = $('body');
- const $document = $(document);
- const $window = $(window);
- const $sidebarGutterToggle = $('.js-sidebar-toggle');
- let bootstrapBreakpoint = bp.getBreakpointSize();
initBreadcrumbs();
- initLayoutNav();
initImporterStatus();
initTodoToggle();
initLogoAnimation();
@@ -84,34 +80,6 @@ document.addEventListener('DOMContentLoaded', () => {
if (document.querySelector('.search')) initSearchAutocomplete();
if (document.querySelector('#js-peek')) initPerformanceBar({ container: '#js-peek' });
- // Set the default path for all cookies to GitLab's root directory
- Cookies.defaults.path = gon.relative_url_root || '/';
-
- // `hashchange` is not triggered when link target is already in window.location
- $body.on('click', 'a[href^="#"]', function clickHashLinkCallback() {
- const href = this.getAttribute('href');
- if (href.substr(1) === getLocationHash()) {
- setTimeout(handleLocationHash, 1);
- }
- });
-
- if (bootstrapBreakpoint === 'xs') {
- const $rightSidebar = $('aside.right-sidebar, .layout-page');
-
- $rightSidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
- }
-
- // prevent default action for disabled buttons
- $('.btn').click(function clickDisabledButtonCallback(e) {
- if ($(this).hasClass('disabled')) {
- e.preventDefault();
- e.stopImmediatePropagation();
- return false;
- }
-
- return true;
- });
-
addSelectOnFocusBehaviour('.js-select-on-focus');
$('.remove-row').on('ajax:success', function removeRowAjaxSuccessCallback() {
@@ -164,6 +132,48 @@ document.addEventListener('DOMContentLoaded', () => {
viewport: '.layout-page',
});
+ loadAwardsHandler();
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ const $body = $('body');
+ const $document = $(document);
+ const $window = $(window);
+ const $sidebarGutterToggle = $('.js-sidebar-toggle');
+ let bootstrapBreakpoint = bp.getBreakpointSize();
+
+ initLayoutNav();
+
+ // Set the default path for all cookies to GitLab's root directory
+ Cookies.defaults.path = gon.relative_url_root || '/';
+
+ // `hashchange` is not triggered when link target is already in window.location
+ $body.on('click', 'a[href^="#"]', function clickHashLinkCallback() {
+ const href = this.getAttribute('href');
+ if (href.substr(1) === getLocationHash()) {
+ setTimeout(handleLocationHash, 1);
+ }
+ });
+
+ if (bootstrapBreakpoint === 'xs') {
+ const $rightSidebar = $('aside.right-sidebar, .layout-page');
+
+ $rightSidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+ }
+
+ // prevent default action for disabled buttons
+ $('.btn').click(function clickDisabledButtonCallback(e) {
+ if ($(this).hasClass('disabled')) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ return false;
+ }
+
+ return true;
+ });
+
+ localTimeAgo($('abbr.timeago, .js-timeago'), true);
+
// Form submitter
$('.trigger-submit').on('change', function triggerSubmitCallback() {
$(this)
@@ -171,8 +181,6 @@ document.addEventListener('DOMContentLoaded', () => {
.submit();
});
- localTimeAgo($('abbr.timeago, .js-timeago'), true);
-
// Disable form buttons while a form is submitting
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function ajaxCompleteCallback(e) {
const $buttons = $('[type="submit"], .js-disable-on-submit', this);
@@ -195,6 +203,10 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
+ $('.navbar-toggler').on('click', () => {
+ $('.header-content').toggleClass('menu-expanded');
+ });
+
// Commit show suppressed diff
$document.on('click', '.diff-content .js-show-suppressed-diff', function showDiffCallback() {
const $container = $(this).parent();
@@ -202,10 +214,6 @@ document.addEventListener('DOMContentLoaded', () => {
$container.remove();
});
- $('.navbar-toggler').on('click', () => {
- $('.header-content').toggleClass('menu-expanded');
- });
-
// Show/hide comments on diff
$body.on('click', '.js-toggle-diff-comments', function toggleDiffCommentsCallback(e) {
const $this = $(this);
@@ -250,8 +258,6 @@ document.addEventListener('DOMContentLoaded', () => {
$window.on('resize.app', fitSidebarForSize);
- loadAwardsHandler();
-
$('form.filter-form').on('submit', function filterFormSubmitCallback(event) {
const link = document.createElement('a');
link.href = this.action;
@@ -274,4 +280,6 @@ document.addEventListener('DOMContentLoaded', () => {
// initialize field errors
$('.gl-show-field-errors').each((i, form) => new GlFieldErrors(form));
+
+ requestIdleCallback(deferredInitialisation);
});
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index b0dc5697018..2f15da42271 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -428,7 +428,7 @@ export default class MergeRequestTabs {
}
diffViewType() {
- return $('.inline-parallel-buttons button.active').data('viewType');
+ return $('.js-diff-view-buttons button.active').data('viewType');
}
isDiffAction(action) {
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 12224e36ba2..e2cffe0b4b4 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -6,6 +6,7 @@ export default {
components: {
GlAreaChart,
},
+ inheritAttrs: false,
props: {
graphData: {
type: Object,
@@ -25,6 +26,11 @@ export default {
);
},
},
+ alertData: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
computed: {
chartData() {
@@ -74,9 +80,6 @@ export default {
const [date, value] = params;
return [dateFormat(date, 'dd mmm yyyy, h:MMtt'), value.toFixed(3)];
},
- onCreated(chart) {
- this.$emit('created', chart);
- },
},
};
</script>
@@ -88,10 +91,11 @@ export default {
<div class="prometheus-graph-widgets"><slot></slot></div>
</div>
<gl-area-chart
+ v-bind="$attrs"
:data="chartData"
:option="chartOptions"
:format-tooltip-text="formatTooltipText"
- @created="onCreated"
+ :thresholds="alertData"
/>
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 2d9c5050c9b..cea5c1a56ca 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -144,6 +144,9 @@ export default {
}
},
methods: {
+ getGraphAlerts(graphId) {
+ return this.alertData ? this.alertData[graphId] || {} : {};
+ },
getGraphsData() {
this.state = 'loading';
Promise.all([
@@ -223,6 +226,8 @@ export default {
:tags-path="tagsPath"
:show-legend="showLegend"
:small-graph="forceSmallGraph"
+ :alert-data="getGraphAlerts(graphData.id)"
+ group-id="monitor-area-chart"
>
<!-- EE content -->
{{ null }}
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index 64a1df80a8e..309b73f5a4d 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -257,8 +257,8 @@ export default {
<template>
<div
class="prometheus-graph"
- @mouseover="showFlagContent = true;"
- @mouseleave="showFlagContent = false;"
+ @mouseover="showFlagContent = true"
+ @mouseleave="showFlagContent = false"
>
<div class="prometheus-graph-header">
<h5 class="prometheus-graph-title">{{ graphData.title }}</h5>
@@ -300,7 +300,7 @@ export default {
:height="graphHeight - 100"
class="prometheus-graph-overlay"
transform="translate(-5, 20)"
- @mousemove="handleMouseOverGraph($event);"
+ @mousemove="handleMouseOverGraph($event)"
/>
</svg>
<svg v-else :viewBox="innerViewBox" class="js-no-data-to-display">
diff --git a/app/assets/javascripts/notebook/cells/code.vue b/app/assets/javascripts/notebook/cells/code.vue
index bd6736152f5..eefc801ed7a 100644
--- a/app/assets/javascripts/notebook/cells/code.vue
+++ b/app/assets/javascripts/notebook/cells/code.vue
@@ -1,11 +1,12 @@
<script>
-import CodeCell from './code/index.vue';
+import CodeOutput from './code/index.vue';
import OutputCell from './output/index.vue';
export default {
+ name: 'CodeCell',
components: {
- 'code-cell': CodeCell,
- 'output-cell': OutputCell,
+ CodeOutput,
+ OutputCell,
},
props: {
cell: {
@@ -29,8 +30,8 @@ export default {
hasOutput() {
return this.cell.outputs.length;
},
- output() {
- return this.cell.outputs[0];
+ outputs() {
+ return this.cell.outputs;
},
},
};
@@ -38,7 +39,7 @@ export default {
<template>
<div class="cell">
- <code-cell
+ <code-output
:raw-code="rawInputCode"
:count="cell.execution_count"
:code-css-class="codeCssClass"
@@ -47,7 +48,7 @@ export default {
<output-cell
v-if="hasOutput"
:count="cell.execution_count"
- :output="output"
+ :outputs="outputs"
:code-css-class="codeCssClass"
/>
</div>
diff --git a/app/assets/javascripts/notebook/cells/code/index.vue b/app/assets/javascripts/notebook/cells/code/index.vue
index 8bf2431c4c6..98b6cdd0944 100644
--- a/app/assets/javascripts/notebook/cells/code/index.vue
+++ b/app/assets/javascripts/notebook/cells/code/index.vue
@@ -3,8 +3,9 @@ import Prism from '../../lib/highlight';
import Prompt from '../prompt.vue';
export default {
+ name: 'CodeOutput',
components: {
- prompt: Prompt,
+ Prompt,
},
props: {
count: {
diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue
index c6fc786fa76..8dc2d73af9b 100644
--- a/app/assets/javascripts/notebook/cells/output/html.vue
+++ b/app/assets/javascripts/notebook/cells/output/html.vue
@@ -4,13 +4,21 @@ import Prompt from '../prompt.vue';
export default {
components: {
- prompt: Prompt,
+ Prompt,
},
props: {
+ count: {
+ type: Number,
+ required: true,
+ },
rawCode: {
type: String,
required: true,
},
+ index: {
+ type: Number,
+ required: true,
+ },
},
computed: {
sanitizedOutput() {
@@ -21,13 +29,16 @@ export default {
},
});
},
+ showOutput() {
+ return this.index === 0;
+ },
},
};
</script>
<template>
<div class="output">
- <prompt />
+ <prompt type="Out" :count="count" :show-output="showOutput" />
<div v-html="sanitizedOutput"></div>
</div>
</template>
diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue
index fe8c81398fb..f1130275525 100644
--- a/app/assets/javascripts/notebook/cells/output/image.vue
+++ b/app/assets/javascripts/notebook/cells/output/image.vue
@@ -6,6 +6,10 @@ export default {
prompt: Prompt,
},
props: {
+ count: {
+ type: Number,
+ required: true,
+ },
outputType: {
type: String,
required: true,
@@ -14,10 +18,24 @@ export default {
type: String,
required: true,
},
+ index: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ imgSrc() {
+ return `data:${this.outputType};base64,${this.rawCode}`;
+ },
+ showOutput() {
+ return this.index === 0;
+ },
},
};
</script>
<template>
- <div class="output"><prompt /> <img :src="'data:' + outputType + ';base64,' + rawCode" /></div>
+ <div class="output">
+ <prompt type="out" :count="count" :show-output="showOutput" /> <img :src="imgSrc" />
+ </div>
</template>
diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue
index bd0bcc0d819..c5ae7e7ee10 100644
--- a/app/assets/javascripts/notebook/cells/output/index.vue
+++ b/app/assets/javascripts/notebook/cells/output/index.vue
@@ -1,14 +1,9 @@
<script>
-import CodeCell from '../code/index.vue';
-import Html from './html.vue';
-import Image from './image.vue';
+import CodeOutput from '../code/index.vue';
+import HtmlOutput from './html.vue';
+import ImageOutput from './image.vue';
export default {
- components: {
- 'code-cell': CodeCell,
- 'html-output': Html,
- 'image-output': Image,
- },
props: {
codeCssClass: {
type: String,
@@ -20,68 +15,69 @@ export default {
required: false,
default: 0,
},
- output: {
- type: Object,
+ outputs: {
+ type: Array,
required: true,
- default: () => ({}),
},
},
- computed: {
- componentName() {
- if (this.output.text) {
- return 'code-cell';
- } else if (this.output.data['image/png']) {
- return 'image-output';
- } else if (this.output.data['text/html']) {
- return 'html-output';
- } else if (this.output.data['image/svg+xml']) {
- return 'html-output';
- }
+ data() {
+ return {
+ outputType: '',
+ };
+ },
+ methods: {
+ dataForType(output, type) {
+ let data = output.data[type];
- return 'code-cell';
- },
- rawCode() {
- if (this.output.text) {
- return this.output.text.join('');
+ if (typeof data === 'object') {
+ data = data.join('');
}
- return this.dataForType(this.outputType);
+ return data;
},
- outputType() {
- if (this.output.text) {
- return '';
- } else if (this.output.data['image/png']) {
- return 'image/png';
- } else if (this.output.data['text/html']) {
- return 'text/html';
- } else if (this.output.data['image/svg+xml']) {
- return 'image/svg+xml';
+ getComponent(output) {
+ if (output.text) {
+ return CodeOutput;
+ } else if (output.data['image/png']) {
+ this.outputType = 'image/png';
+
+ return ImageOutput;
+ } else if (output.data['text/html']) {
+ this.outputType = 'text/html';
+
+ return HtmlOutput;
+ } else if (output.data['image/svg+xml']) {
+ this.outputType = 'image/svg+xml';
+
+ return HtmlOutput;
}
- return 'text/plain';
+ this.outputType = 'text/plain';
+ return CodeOutput;
},
- },
- methods: {
- dataForType(type) {
- let data = this.output.data[type];
-
- if (typeof data === 'object') {
- data = data.join('');
+ rawCode(output) {
+ if (output.text) {
+ return output.text.join('');
}
- return data;
+ return this.dataForType(output, this.outputType);
},
},
};
</script>
<template>
- <component
- :is="componentName"
- :output-type="outputType"
- :count="count"
- :raw-code="rawCode"
- :code-css-class="codeCssClass"
- type="output"
- />
+ <div>
+ <component
+ :is="getComponent(output)"
+ v-for="(output, index) in outputs"
+ :key="index"
+ type="output"
+ :output-type="outputType"
+ :count="count"
+ :index="index"
+ :raw-code="rawCode(output)"
+ :code-css-class="codeCssClass"
+ />
+ </div>
</template>
diff --git a/app/assets/javascripts/notebook/cells/prompt.vue b/app/assets/javascripts/notebook/cells/prompt.vue
index 3f1f239a806..1eeb61844a4 100644
--- a/app/assets/javascripts/notebook/cells/prompt.vue
+++ b/app/assets/javascripts/notebook/cells/prompt.vue
@@ -11,18 +11,26 @@ export default {
required: false,
default: 0,
},
+ showOutput: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
computed: {
hasKeys() {
return this.type !== '' && this.count;
},
+ showTypeText() {
+ return this.type && this.count && this.showOutput;
+ },
},
};
</script>
<template>
<div class="prompt">
- <span v-if="hasKeys"> {{ type }} [{{ count }}]: </span>
+ <span v-if="showTypeText"> {{ type }} [{{ count }}]: </span>
</div>
</template>
diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue
index 6a54d0b3823..e7056c03e4a 100644
--- a/app/assets/javascripts/notebook/index.vue
+++ b/app/assets/javascripts/notebook/index.vue
@@ -3,8 +3,8 @@ import { MarkdownCell, CodeCell } from './cells';
export default {
components: {
- 'code-cell': CodeCell,
- 'markdown-cell': MarkdownCell,
+ CodeCell,
+ MarkdownCell,
},
props: {
notebook: {
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index dfb53c986fc..c3443c300e3 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -138,8 +138,6 @@ export default class Notes {
this.$wrapperEl.on('click', '.js-note-delete', this.removeNote);
// delete note attachment
this.$wrapperEl.on('click', '.js-note-attachment-delete', this.removeAttachment);
- // reset main target form when clicking discard
- this.$wrapperEl.on('click', '.js-note-discard', this.resetMainTargetForm);
// update the file name when an attachment is selected
this.$wrapperEl.on('change', '.js-note-attachment-input', this.updateFormAttachment);
// reply to diff/discussion notes
@@ -191,7 +189,6 @@ export default class Notes {
this.$wrapperEl.off('keyup input', '.js-note-text');
this.$wrapperEl.off('click', '.js-note-target-reopen');
this.$wrapperEl.off('click', '.js-note-target-close');
- this.$wrapperEl.off('click', '.js-note-discard');
this.$wrapperEl.off('keydown', '.js-note-text');
this.$wrapperEl.off('click', '.js-comment-resolve-button');
this.$wrapperEl.off('click', '.system-note-commit-list-toggler');
@@ -986,11 +983,9 @@ export default class Notes {
form.find('#note_position').val(dataHolder.attr('data-position'));
form
- .find('.js-note-discard')
+ .find('.js-close-discussion-note-form')
.show()
- .removeClass('js-note-discard')
- .addClass('js-close-discussion-note-form')
- .text(form.find('.js-close-discussion-note-form').data('cancelText'));
+ .removeClass('hide');
form.find('.js-note-target-close').remove();
form.find('.js-note-new-discussion').remove();
this.setupNoteForm(form);
@@ -1194,12 +1189,11 @@ export default class Notes {
}
updateTargetButtons(e) {
- var closebtn, closetext, discardbtn, form, reopenbtn, reopentext, textarea;
+ var closebtn, closetext, form, reopenbtn, reopentext, textarea;
textarea = $(e.target);
form = textarea.parents('form');
reopenbtn = form.find('.js-note-target-reopen');
closebtn = form.find('.js-note-target-close');
- discardbtn = form.find('.js-note-discard');
if (textarea.val().trim().length > 0) {
reopentext = reopenbtn.attr('data-alternative-text');
@@ -1216,9 +1210,6 @@ export default class Notes {
if (closebtn.is(':not(.btn-comment-and-close)')) {
closebtn.addClass('btn-comment-and-close');
}
- if (discardbtn.is(':hidden')) {
- return discardbtn.show();
- }
} else {
reopentext = reopenbtn.data('originalText');
closetext = closebtn.data('originalText');
@@ -1234,9 +1225,6 @@ export default class Notes {
if (closebtn.is('.btn-comment-and-close')) {
closebtn.removeClass('btn-comment-and-close');
}
- if (discardbtn.is(':visible')) {
- return discardbtn.hide();
- }
}
}
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index ce56beb1e6b..d669ba5a8fa 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -357,9 +357,9 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
- @keydown.up="editCurrentUserLastNote();"
- @keydown.meta.enter="handleSave();"
- @keydown.ctrl.enter="handleSave();"
+ @keydown.up="editCurrentUserLastNote()"
+ @keydown.meta.enter="handleSave()"
+ @keydown.ctrl.enter="handleSave()"
>
</textarea>
</markdown-field>
@@ -370,10 +370,10 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<button
:disabled="isSubmitButtonDisabled"
- class="btn btn-create comment-btn js-comment-button js-comment-submit-button
+ class="btn btn-success js-comment-button js-comment-submit-button
qa-comment-button"
type="submit"
- @click.prevent="handleSave();"
+ @click.prevent="handleSave()"
>
{{ __(commentButtonTitle) }}
</button>
@@ -381,7 +381,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
:disabled="isSubmitButtonDisabled"
name="button"
type="button"
- class="btn comment-btn note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown"
+ class="btn btn-success note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown"
data-display="static"
data-toggle="dropdown"
aria-label="Open comment type dropdown"
@@ -394,7 +394,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
<button
type="button"
class="btn btn-transparent"
- @click.prevent="setNoteType('comment');"
+ @click.prevent="setNoteType('comment')"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
@@ -408,7 +408,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
<button
type="button"
class="btn btn-transparent qa-discussion-option"
- @click.prevent="setNoteType('discussion');"
+ @click.prevent="setNoteType('discussion')"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
@@ -429,17 +429,8 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
]"
:disabled="isToggleStateButtonLoading || isSubmitting"
:label="issueActionButtonTitle"
- @click="handleSave(true);"
+ @click="handleSave(true)"
/>
-
- <button
- v-if="note.length"
- type="button"
- class="btn btn-cancel js-note-discard"
- @click="discard"
- >
- Discard draft
- </button>
</div>
</form>
</div>
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index af821df0fd2..376d4114efd 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -6,8 +6,6 @@ import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
import { GlSkeletonLoading } from '@gitlab/ui';
import { getDiffMode } from '~/diffs/store/utils';
-const FIRST_CHAR_REGEX = /^(\+|-| )/;
-
export default {
components: {
DiffFileHeader,
@@ -54,9 +52,6 @@ export default {
this.error = true;
});
},
- trimChar(line) {
- return line.replace(FIRST_CHAR_REGEX, '');
- },
},
userColorSchemeClass: window.gon.user_color_scheme,
};
@@ -85,7 +80,7 @@ export default {
>
<td class="diff-line-num old_line">{{ line.old_line }}</td>
<td class="diff-line-num new_line">{{ line.new_line }}</td>
- <td :class="line.type" class="line_content" v-html="trimChar(line.rich_text)"></td>
+ <td :class="line.type" class="line_content" v-html="line.rich_text"></td>
</tr>
</template>
<tr v-if="!hasTruncatedDiffLines" class="line_holder line-holder-placeholder">
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index f5c410211b6..e03d6e9cd02 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -1,6 +1,7 @@
<script>
import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex';
+import { getLocationHash } from '../../lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue';
import {
DISCUSSION_FILTERS_DEFAULT_VALUE,
@@ -44,29 +45,47 @@ export default {
eventHub.$on('MergeRequestTabChange', this.toggleFilters);
this.toggleFilters(currentTab);
}
+
+ window.addEventListener('hashchange', this.handleLocationHash);
+ this.handleLocationHash();
},
mounted() {
this.toggleCommentsForm();
},
+ destroyed() {
+ window.removeEventListener('hashchange', this.handleLocationHash);
+ },
methods: {
- ...mapActions(['filterDiscussion', 'setCommentsDisabled']),
+ ...mapActions(['filterDiscussion', 'setCommentsDisabled', 'setTargetNoteHash']),
selectFilter(value) {
const filter = parseInt(value, 10);
// close dropdown
- $(this.$refs.dropdownToggle).dropdown('toggle');
+ this.toggleDropdown();
if (filter === this.currentValue) return;
this.currentValue = filter;
this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter });
this.toggleCommentsForm();
},
+ toggleDropdown() {
+ $(this.$refs.dropdownToggle).dropdown('toggle');
+ },
toggleCommentsForm() {
this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE);
},
toggleFilters(tab) {
this.displayFilters = tab === DISCUSSION_TAB_LABEL;
},
+ handleLocationHash() {
+ const hash = getLocationHash();
+
+ if (/^note_/.test(hash) && this.currentValue !== DISCUSSION_FILTERS_DEFAULT_VALUE) {
+ this.selectFilter(this.defaultValue);
+ this.toggleDropdown(); // close dropdown
+ this.setTargetNoteHash(hash);
+ }
+ },
},
};
</script>
@@ -93,7 +112,7 @@ export default {
:class="{ 'is-active': filter.value === currentValue }"
class="qa-filter-options"
type="button"
- @click="selectFilter(filter.value);"
+ @click="selectFilter(filter.value)"
>
{{ filter.title }}
</button>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index 3d60eb02db8..3efdd1c5c17 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -1,6 +1,6 @@
<script>
import { mapActions, mapGetters } from 'vuex';
-import { GlTooltipDirective } from '@gitlab/ui';
+import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
import { glEmojiTag } from '../../emoji';
@@ -10,7 +10,7 @@ export default {
Icon,
},
directives: {
- GlTooltip: GlTooltipDirective,
+ tooltip,
},
props: {
awards: {
@@ -167,19 +167,21 @@ export default {
<button
v-for="(awardList, awardName, index) in groupedAwards"
:key="index"
- v-gl-tooltip.bottom="{ boundary: 'viewport' }"
+ v-tooltip
:class="getAwardClassBindings(awardList)"
:title="awardTitle(awardList)"
+ data-boundary="viewport"
+ data-placement="bottom"
class="btn award-control"
type="button"
- @click="handleAward(awardName);"
+ @click="handleAward(awardName)"
>
<span v-html="getAwardHTML(awardName)"></span>
<span class="award-control-text js-counter">{{ awardList.length }}</span>
</button>
<div v-if="canAwardEmoji" class="award-menu-holder">
<button
- v-gl-tooltip
+ v-tooltip
:class="{ 'js-user-authored': isAuthoredByMe }"
class="award-control btn js-add-award"
title="Add reaction"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index e78596f8b52..269b4a4b117 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -6,6 +6,7 @@ import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
+import { __ } from '~/locale';
export default {
name: 'NoteForm',
@@ -33,7 +34,7 @@ export default {
saveButtonTitle: {
type: String,
required: false,
- default: 'Save comment',
+ default: __('Save comment'),
},
discussion: {
type: Object,
@@ -149,6 +150,9 @@ export default {
return shouldResolve || shouldToggleState;
},
+ handleKeySubmit() {
+ this.handleUpdate();
+ },
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
@@ -216,10 +220,10 @@ export default {
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
- @keydown.meta.enter="handleUpdate();"
- @keydown.ctrl.enter="handleUpdate();"
- @keydown.up="editMyLastNote();"
- @keydown.esc="cancelHandler(true);"
+ @keydown.meta.enter="handleKeySubmit()"
+ @keydown.ctrl.enter="handleKeySubmit()"
+ @keydown.up="editMyLastNote()"
+ @keydown.esc="cancelHandler(true)"
></textarea>
</markdown-field>
<div class="note-form-actions clearfix">
@@ -227,21 +231,21 @@ export default {
:disabled="isDisabled"
type="button"
class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button"
- @click="handleUpdate();"
+ @click="handleUpdate()"
>
{{ saveButtonTitle }}
</button>
<button
v-if="discussion.resolvable"
class="btn btn-nr btn-default append-right-10 js-comment-resolve-button"
- @click.prevent="handleUpdate(true);"
+ @click.prevent="handleUpdate(true)"
>
{{ resolveButtonTitle }}
</button>
<button
class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
type="button"
- @click="cancelHandler();"
+ @click="cancelHandler()"
>
Cancel
</button>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 7c3f5d00308..1a9723de856 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -206,11 +206,25 @@ export default {
return sprintf(text, { commitId, linkStart, linkEnd }, false);
},
diffLine() {
+ if (this.line) {
+ return this.line;
+ }
+
if (this.discussion.diff_discussion && this.discussion.truncated_diff_lines) {
return this.discussion.truncated_diff_lines.slice(-1)[0];
}
- return this.line;
+ return null;
+ },
+ commit() {
+ if (!this.discussion.for_commit) {
+ return null;
+ }
+
+ return {
+ id: this.discussion.commit_id,
+ url: this.discussion.discussion_path,
+ };
},
},
watch: {
@@ -376,6 +390,7 @@ Please check your network connection and try again.`;
:is="componentName(initialDiscussion)"
:note="componentData(initialDiscussion)"
:line="line"
+ :commit="commit"
:help-page-path="helpPagePath"
@handleDeleteNote="deleteNoteHandler"
>
@@ -440,7 +455,7 @@ Please check your network connection and try again.`;
<button
type="button"
class="btn btn-default ml-sm-2"
- @click="resolveHandler();"
+ @click="resolveHandler()"
>
<i v-if="isResolving" aria-hidden="true" class="fa fa-spinner fa-spin"></i>
{{ resolveButtonTitle }}
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 4c02588127e..3c48d81ed05 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -2,7 +2,9 @@
import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex';
import { escape } from 'underscore';
+import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import { s__, sprintf } from '../../locale';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
@@ -37,6 +39,11 @@ export default {
required: false,
default: '',
},
+ commit: {
+ type: Object,
+ required: false,
+ default: () => null,
+ },
},
data() {
return {
@@ -73,6 +80,21 @@ export default {
isTarget() {
return this.targetNoteHash === this.noteAnchorId;
},
+ actionText() {
+ if (!this.commit) {
+ return '';
+ }
+
+ // We need to do this to ensure we have the currect sentence order
+ // when translating this as the sentence order may change from one
+ // language to the next. See:
+ // https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24427#note_133713771
+ const { id, url } = this.commit;
+ const commitLink = `<a class="commit-sha monospace" href="${escape(url)}">${truncateSha(
+ id,
+ )}</a>`;
+ return sprintf(s__('MergeRequests|commented on commit %{commitLink}'), { commitLink }, false);
+ },
},
created() {
@@ -200,13 +222,10 @@ export default {
</div>
<div class="timeline-content">
<div class="note-header">
- <note-header
- v-once
- :author="author"
- :created-at="note.created_at"
- :note-id="note.id"
- action-text="commented"
- />
+ <note-header v-once :author="author" :created-at="note.created_at" :note-id="note.id">
+ <span v-if="commit" v-html="actionText"></span>
+ <span v-else class="d-none d-sm-inline">&middot;</span>
+ </note-header>
<note-actions
:author-id="author.id"
:note-id="note.id"
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 8992454be2e..33d39ad2ec9 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -105,7 +105,10 @@ export default {
if (discussion.diff_file) {
diffData.file_hash = discussion.diff_file.file_hash;
- diffData.truncated_diff_lines = discussion.truncated_diff_lines || [];
+
+ diffData.truncated_diff_lines = utils.prepareDiffLines(
+ discussion.truncated_diff_lines || [],
+ );
}
// To support legacy notes, should be very rare case.
@@ -243,7 +246,7 @@ export default {
[types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
- discussion.truncated_diff_lines = diffLines;
+ discussion.truncated_diff_lines = utils.prepareDiffLines(diffLines);
},
[types.DISABLE_COMMENTS](state, value) {
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index dd57539e4d8..4b0feb0f94d 100644
--- a/app/assets/javascripts/notes/stores/utils.js
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -1,4 +1,5 @@
import AjaxCache from '~/lib/utils/ajax_cache';
+import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
@@ -28,3 +29,6 @@ export const getQuickActionText = note => {
export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim();
+
+export const prepareDiffLines = diffLines =>
+ diffLines.map(line => ({ ...trimFirstCharOfLineContent(line) }));
diff --git a/app/assets/javascripts/pages/admin/application_settings/show/index.js b/app/assets/javascripts/pages/admin/application_settings/show/index.js
new file mode 100644
index 00000000000..5ec9688a6e4
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/application_settings/show/index.js
@@ -0,0 +1,3 @@
+import initUserInternalRegexPlaceholder from '../../application_settings/account_and_limits';
+
+document.addEventListener('DOMContentLoaded', initUserInternalRegexPlaceholder());
diff --git a/app/assets/javascripts/pages/admin/index.js b/app/assets/javascripts/pages/admin/index.js
index 3aa793e47b9..8a32556f06c 100644
--- a/app/assets/javascripts/pages/admin/index.js
+++ b/app/assets/javascripts/pages/admin/index.js
@@ -1,7 +1,3 @@
import initAdmin from './admin';
-import initUserInternalRegexPlaceholder from './application_settings/account_and_limits';
-document.addEventListener('DOMContentLoaded', () => {
- initAdmin();
- initUserInternalRegexPlaceholder();
-});
+document.addEventListener('DOMContentLoaded', initAdmin());
diff --git a/app/assets/javascripts/pages/groups/clusters/update/index.js b/app/assets/javascripts/pages/groups/clusters/edit/index.js
index 8001d2dd1da..8001d2dd1da 100644
--- a/app/assets/javascripts/pages/groups/clusters/update/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/edit/index.js
diff --git a/app/assets/javascripts/pages/groups/clusters/index/index.js b/app/assets/javascripts/pages/groups/clusters/index/index.js
index 845a5f7042c..21efc4f6d00 100644
--- a/app/assets/javascripts/pages/groups/clusters/index/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/index/index.js
@@ -1,5 +1,7 @@
-import initDismissableCallout from '~/dismissable_callout';
+import PersistentUserCallout from '~/persistent_user_callout';
document.addEventListener('DOMContentLoaded', () => {
- initDismissableCallout('.gcp-signup-offer');
+ const callout = document.querySelector('.gcp-signup-offer');
+
+ if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/groups/index.js b/app/assets/javascripts/pages/groups/index.js
index bf80d8b8193..a63a0dbc6b1 100644
--- a/app/assets/javascripts/pages/groups/index.js
+++ b/app/assets/javascripts/pages/groups/index.js
@@ -1,6 +1,12 @@
-import initDismissableCallout from '~/dismissable_callout';
+import PersistentUserCallout from '~/persistent_user_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
+function initGcpSignupCallout() {
+ const callout = document.querySelector('.gcp-signup-offer');
+
+ if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+}
+
document.addEventListener('DOMContentLoaded', () => {
const { page } = document.body.dataset;
const newClusterViews = [
@@ -10,7 +16,7 @@ document.addEventListener('DOMContentLoaded', () => {
];
if (newClusterViews.indexOf(page) > -1) {
- initDismissableCallout('.gcp-signup-offer');
+ initGcpSignupCallout();
initGkeDropdowns();
}
});
diff --git a/app/assets/javascripts/pages/projects/clusters/update/index.js b/app/assets/javascripts/pages/projects/clusters/edit/index.js
index 8001d2dd1da..8001d2dd1da 100644
--- a/app/assets/javascripts/pages/projects/clusters/update/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/edit/index.js
diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js
index 845a5f7042c..21efc4f6d00 100644
--- a/app/assets/javascripts/pages/projects/clusters/index/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/index/index.js
@@ -1,5 +1,7 @@
-import initDismissableCallout from '~/dismissable_callout';
+import PersistentUserCallout from '~/persistent_user_callout';
document.addEventListener('DOMContentLoaded', () => {
- initDismissableCallout('.gcp-signup-offer');
+ const callout = document.querySelector('.gcp-signup-offer');
+
+ if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/projects/error_tracking/index.js b/app/assets/javascripts/pages/projects/error_tracking/index.js
new file mode 100644
index 00000000000..5a8fe137e9a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/error_tracking/index.js
@@ -0,0 +1,5 @@
+import ErrorTracking from '~/error_tracking';
+
+document.addEventListener('DOMContentLoaded', () => {
+ ErrorTracking();
+});
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index 5659e13981a..b0345b4e50d 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,5 +1,5 @@
-import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
+import PersistentUserCallout from '../../persistent_user_callout';
import Project from './project';
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
@@ -12,7 +12,9 @@ document.addEventListener('DOMContentLoaded', () => {
];
if (newClusterViews.indexOf(page) > -1) {
- initDismissableCallout('.gcp-signup-offer');
+ const callout = document.querySelector('.gcp-signup-offer');
+ if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+
initGkeDropdowns();
}
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 db2a4041ec0..bd4309e47ad 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
@@ -70,7 +70,7 @@ export default {
:checked="isEditable"
class="label-bold"
type="radio"
- @click="toggleCustomInput(true);"
+ @click="toggleCustomInput(true)"
/>
<label for="custom"> {{ s__('PipelineSheduleIntervalPattern|Custom') }} </label>
@@ -88,7 +88,7 @@ export default {
:value="cronIntervalPresets.everyDay"
class="label-bold"
type="radio"
- @click="toggleCustomInput(false);"
+ @click="toggleCustomInput(false)"
/>
<label class="label-bold" for="every-day"> {{ __('Every day (at 4:00am)') }} </label>
@@ -102,7 +102,7 @@ export default {
:value="cronIntervalPresets.everyWeek"
class="label-bold"
type="radio"
- @click="toggleCustomInput(false);"
+ @click="toggleCustomInput(false)"
/>
<label class="label-bold" for="every-week">
@@ -118,7 +118,7 @@ export default {
:value="cronIntervalPresets.everyMonth"
class="label-bold"
type="radio"
- @click="toggleCustomInput(false);"
+ @click="toggleCustomInput(false)"
/>
<label class="label-bold" for="every-month">
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 08c7719dcf2..19d9903c988 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
@@ -325,8 +325,8 @@ export default {
<project-setting-row
v-if="pagesAvailable && pagesAccessControlEnabled"
:help-path="pagesHelpPath"
- label="Pages"
- help-text="Static website for the project."
+ label="Pages access control"
+ help-text="Access control for the project's static website"
>
<project-feature-setting
v-model="pagesAccessLevel"
diff --git a/app/assets/javascripts/pages/projects/tags/releases/index.js b/app/assets/javascripts/pages/projects/tags/releases/index.js
new file mode 100644
index 00000000000..d6afc71fb03
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/tags/releases/index.js
@@ -0,0 +1,8 @@
+import $ from 'jquery';
+import ZenMode from '~/zen_mode';
+import GLForm from '~/gl_form';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new ZenMode(); // eslint-disable-line no-new
+ new GLForm($('.release-form')); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js
index 07f32210d93..d54bff88f70 100644
--- a/app/assets/javascripts/pages/sessions/new/index.js
+++ b/app/assets/javascripts/pages/sessions/new/index.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import UsernameValidator from './username_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer';
import OAuthRememberMe from './oauth_remember_me';
+import preserveUrlFragment from './preserve_url_fragment';
document.addEventListener('DOMContentLoaded', () => {
new UsernameValidator(); // eslint-disable-line no-new
@@ -10,4 +11,8 @@ document.addEventListener('DOMContentLoaded', () => {
new OAuthRememberMe({
container: $('.omniauth-container'),
}).bindEvents();
+
+ // Save the URL fragment from the current window location. This will be present if the user was
+ // redirected to sign-in after attempting to access a protected URL that included a fragment.
+ preserveUrlFragment(window.location.hash);
});
diff --git a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
index 761618109a4..191221a48cd 100644
--- a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
+++ b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
/**
* OAuth-based login buttons have a separate "remember me" checkbox.
@@ -24,9 +25,9 @@ export default class OAuthRememberMe {
const href = $(element).attr('href');
if (rememberMe) {
- $(element).attr('href', `${href}?remember_me=1`);
+ $(element).attr('href', mergeUrlParams({ remember_me: 1 }, href));
} else {
- $(element).attr('href', href.replace('?remember_me=1', ''));
+ $(element).attr('href', removeParams(['remember_me'], href));
}
});
}
diff --git a/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js b/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js
new file mode 100644
index 00000000000..e617fecaa0f
--- /dev/null
+++ b/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js
@@ -0,0 +1,32 @@
+import { mergeUrlParams, setUrlFragment } from '~/lib/utils/url_utility';
+
+/**
+ * Ensure the given URL fragment is preserved by appending it to sign-in/sign-up form actions and
+ * OAuth/SAML login links.
+ *
+ * @param fragment {string} - url fragment to be preserved
+ */
+export default function preserveUrlFragment(fragment = '') {
+ if (fragment) {
+ const normalFragment = fragment.replace(/^#/, '');
+
+ // Append the fragment to all sign-in/sign-up form actions so it is preserved when the user is
+ // eventually redirected back to the originally requested URL.
+ const forms = document.querySelectorAll('#signin-container form');
+ Array.prototype.forEach.call(forms, form => {
+ const actionWithFragment = setUrlFragment(form.getAttribute('action'), `#${normalFragment}`);
+ form.setAttribute('action', actionWithFragment);
+ });
+
+ // Append a redirect_fragment query param to all oauth provider links. The redirect_fragment
+ // query param will be available in the omniauth callback upon successful authentication
+ const anchors = document.querySelectorAll('#signin-container a.oauth-login');
+ Array.prototype.forEach.call(anchors, anchor => {
+ const newHref = mergeUrlParams(
+ { redirect_fragment: normalFragment },
+ anchor.getAttribute('href'),
+ );
+ anchor.setAttribute('href', newHref);
+ });
+ }
+}
diff --git a/app/assets/javascripts/pages/users/user_overview_block.js b/app/assets/javascripts/pages/users/user_overview_block.js
index e9ecec717d6..a7c3c9d104d 100644
--- a/app/assets/javascripts/pages/users/user_overview_block.js
+++ b/app/assets/javascripts/pages/users/user_overview_block.js
@@ -15,7 +15,8 @@ export default class UserOverviewBlock {
}
loadData() {
- const loadingEl = document.querySelector(`${this.container} .loading`);
+ const containerEl = document.querySelector(this.container);
+ const loadingEl = containerEl.querySelector(`.loading`);
loadingEl.classList.remove('hide');
@@ -42,7 +43,7 @@ export default class UserOverviewBlock {
const nothingHereBlock = containerEl.querySelector('.nothing-here-block');
if (nothingHereBlock) {
- nothingHereBlock.classList.add('text-left', 'p-0');
+ nothingHereBlock.classList.add('p-5');
}
}
diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js
new file mode 100644
index 00000000000..1e34e74a152
--- /dev/null
+++ b/app/assets/javascripts/persistent_user_callout.js
@@ -0,0 +1,34 @@
+import axios from './lib/utils/axios_utils';
+import { __ } from './locale';
+import Flash from './flash';
+
+export default class PersistentUserCallout {
+ constructor(container) {
+ const { dismissEndpoint, featureId } = container.dataset;
+ this.container = container;
+ this.dismissEndpoint = dismissEndpoint;
+ this.featureId = featureId;
+
+ this.init();
+ }
+
+ init() {
+ const closeButton = this.container.querySelector('.js-close');
+ closeButton.addEventListener('click', event => this.dismiss(event));
+ }
+
+ dismiss(event) {
+ event.preventDefault();
+
+ axios
+ .post(this.dismissEndpoint, {
+ feature_name: this.featureId,
+ })
+ .then(() => {
+ this.container.remove();
+ })
+ .catch(() => {
+ Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
+ });
+ }
+}
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 59cebaba717..a49dc311bd0 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,59 +1,20 @@
<script>
-import _ from 'underscore';
import { GlLoadingIcon } from '@gitlab/ui';
import StageColumnComponent from './stage_column_component.vue';
+import GraphMixin from '../../mixins/graph_component_mixin';
export default {
components: {
StageColumnComponent,
GlLoadingIcon,
},
- props: {
- isLoading: {
- type: Boolean,
- required: true,
- },
- pipeline: {
- type: Object,
- required: true,
- },
- },
- computed: {
- graph() {
- return this.pipeline.details && this.pipeline.details.stages;
- },
- },
- methods: {
- capitalizeStageName(name) {
- const escapedName = _.escape(name);
- return escapedName.charAt(0).toUpperCase() + escapedName.slice(1);
- },
- isFirstColumn(index) {
- return index === 0;
- },
- stageConnectorClass(index, stage) {
- let className;
-
- // If it's the first stage column and only has one job
- if (index === 0 && stage.groups.length === 1) {
- className = 'no-margin';
- } else if (index > 0) {
- // If it is not the first column
- className = 'left-margin';
- }
-
- return className;
- },
- refreshPipelineGraph() {
- this.$emit('refreshPipelineGraph');
- },
- },
+ mixins: [GraphMixin],
};
</script>
<template>
<div class="build-content middle-block js-pipeline-graph">
<div class="pipeline-visualization pipeline-graph pipeline-tab-content">
- <div class="text-center"><gl-loading-icon v-if="isLoading" :size="3" /></div>
+ <div v-if="isLoading" class="m-auto"><gl-loading-icon :size="3" /></div>
<ul v-if="!isLoading" class="stage-column-list">
<stage-column-component
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 7d8863dff29..918622ef8dc 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -1,8 +1,20 @@
<script>
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
+import _ from 'underscore';
+import { __, sprintf } from '~/locale';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import popover from '~/vue_shared/directives/popover';
+const popoverTitle = sprintf(
+ _.escape(
+ __(
+ `This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}`,
+ ),
+ ),
+ { strongStart: '<b>', strongEnd: '</b>' },
+ false,
+);
+
export default {
components: {
UserAvatarLink,
@@ -32,14 +44,14 @@ export default {
trigger: 'focus',
placement: 'top',
title: `<div class="autodevops-title">
- This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>
+ ${popoverTitle}
</div>`,
content: `<a
class="autodevops-link"
href="${this.autoDevopsHelpPath}"
target="_blank"
rel="noopener noreferrer nofollow">
- Learn more about Auto DevOps
+ ${_.escape(__('Learn more about Auto DevOps'))}
</a>`,
};
},
@@ -54,9 +66,9 @@ export default {
<span>by</span>
<user-avatar-link
v-if="user"
- :link-href="pipeline.user.path"
- :img-src="pipeline.user.avatar_url"
- :tooltip-text="pipeline.user.name"
+ :link-href="user.path"
+ :img-src="user.avatar_url"
+ :tooltip-text="user.name"
class="js-pipeline-url-user"
/>
<span v-if="!user" class="js-pipeline-url-api api"> API </span>
@@ -64,10 +76,10 @@ export default {
<span
v-if="pipeline.flags.latest"
v-gl-tooltip
- class="js-pipeline-url-latest badge badge-success"
:title="__('Latest pipeline for this branch')"
+ class="js-pipeline-url-latest badge badge-success"
>
- latest
+ {{ __('latest') }}
</span>
<span
v-if="pipeline.flags.yaml_errors"
@@ -75,7 +87,7 @@ export default {
:title="pipeline.yaml_errors"
class="js-pipeline-url-yaml badge badge-danger"
>
- yaml invalid
+ {{ __('yaml invalid') }}
</span>
<span
v-if="pipeline.flags.failure_reason"
@@ -83,7 +95,7 @@ export default {
:title="pipeline.failure_reason"
class="js-pipeline-url-failure badge badge-danger"
>
- error
+ {{ __('error') }}
</span>
<gl-link
v-if="pipeline.flags.auto_devops"
@@ -95,7 +107,7 @@ export default {
Auto DevOps
</gl-link>
<span v-if="pipeline.flags.stuck" class="js-pipeline-url-stuck badge badge-warning">
- stuck
+ {{ __('stuck') }}
</span>
<span
v-if="pipeline.flags.merge_request"
@@ -103,7 +115,7 @@ export default {
:title="__('This pipeline is run in a merge request context')"
class="js-pipeline-url-mergerequest badge badge-info"
>
- merge request
+ {{ __('merge request') }}
</span>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index 2e9f2519fcb..0152e2fbe04 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -77,7 +77,7 @@ export default {
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)"
class="js-pipeline-action-link no-btn btn"
- @click="onClickAction(action);"
+ @click="onClickAction(action)"
>
{{ action.name }}
<span v-if="action.scheduled_at" class="pull-right">
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index 2d3f667e73e..7426936515a 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -172,8 +172,6 @@ export default {
<span :aria-label="stage.title" aria-hidden="true" class="no-pointer-events">
<icon :name="borderlessIcon" />
</span>
-
- <i class="fa fa-caret-down" aria-hidden="true"> </i>
</button>
<div
diff --git a/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
new file mode 100644
index 00000000000..66e9476dadf
--- /dev/null
+++ b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
@@ -0,0 +1,44 @@
+import _ from 'underscore';
+
+export default {
+ props: {
+ isLoading: {
+ type: Boolean,
+ required: true,
+ },
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ graph() {
+ return this.pipeline.details && this.pipeline.details.stages;
+ },
+ },
+ methods: {
+ capitalizeStageName(name) {
+ const escapedName = _.escape(name);
+ return escapedName.charAt(0).toUpperCase() + escapedName.slice(1);
+ },
+ isFirstColumn(index) {
+ return index === 0;
+ },
+ stageConnectorClass(index, stage) {
+ let className;
+
+ // If it's the first stage column and only has one job
+ if (index === 0 && stage.groups.length === 1) {
+ className = 'no-margin';
+ } else if (index > 0) {
+ // If it is not the first column
+ className = 'left-margin';
+ }
+
+ return className;
+ },
+ refreshPipelineGraph() {
+ this.$emit('refreshPipelineGraph');
+ },
+ },
+};
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
index 21095fcba16..83811ab489a 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
@@ -108,9 +108,7 @@ export default {
</span>
</li>
<li v-for="result in results" :key="result.id">
- <button type="button" @click.prevent="setItem(result.name);">
- {{ result.name }}
- </button>
+ <button type="button" @click.prevent="setItem(result.name)">{{ result.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
index 056584c8865..a2eb79af4f9 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
@@ -169,7 +169,7 @@ export default {
</span>
</li>
<li v-for="result in results" :key="result.project_number">
- <button type="button" @click.prevent="setItem(result);">{{ result.name }}</button>
+ <button type="button" @click.prevent="setItem(result)">{{ result.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
index 728616a441f..5f8a4946f4a 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
@@ -82,9 +82,7 @@ export default {
</span>
</li>
<li v-for="result in results" :key="result.id">
- <button type="button" @click.prevent="setItem(result.name);">
- {{ result.name }}
- </button>
+ <button type="button" @click.prevent="setItem(result.name)">{{ result.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 78c7671856a..81fe0a48c06 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -70,7 +70,7 @@ export default {
</thead>
<tbody>
<tr v-for="item in repo.list" :key="item.tag">
- <td>
+ <td class="monospace">
{{ item.tag }}
<clipboard-button
v-if="item.location"
@@ -80,7 +80,9 @@ export default {
/>
</td>
<td>
- <span v-gl-tooltip.bottom :title="item.revision">{{ item.shortRevision }}</span>
+ <span v-gl-tooltip.bottom class="monospace" :title="item.revision">{{
+ item.shortRevision
+ }}</span>
</td>
<td>
{{ formatSize(item.size) }}
@@ -104,7 +106,7 @@ export default {
:aria-label="s__('ContainerRegistry|Remove tag')"
variant="danger"
class="js-delete-registry d-none d-sm-block float-right"
- @click="handleDeleteRegistry(item);"
+ @click="handleDeleteRegistry(item)"
>
<icon name="remove" />
</gl-button>
diff --git a/app/assets/javascripts/releases/components/app.vue b/app/assets/javascripts/releases/components/app.vue
index 0ad5ee2915c..5a06c4fec58 100644
--- a/app/assets/javascripts/releases/components/app.vue
+++ b/app/assets/javascripts/releases/components/app.vue
@@ -1,12 +1,12 @@
<script>
import { mapState, mapActions } from 'vuex';
-import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
+import { GlSkeletonLoading, GlEmptyState } from '@gitlab/ui';
import ReleaseBlock from './release_block.vue';
export default {
name: 'ReleasesApp',
components: {
- GlLoadingIcon,
+ GlSkeletonLoading,
GlEmptyState,
ReleaseBlock,
},
@@ -43,7 +43,7 @@ export default {
</script>
<template>
<div class="prepend-top-default">
- <gl-loading-icon v-if="isLoading" :size="2" class="js-loading prepend-top-20" />
+ <gl-skeleton-loading v-if="isLoading" class="js-loading" />
<gl-empty-state
v-else-if="shouldRenderEmptyState"
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index 34b97826cdb..7ed1b407ddd 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -45,7 +45,7 @@ export default {
return this.release.author || {};
},
hasAuthor() {
- return _.isEmpty(this.author);
+ return !_.isEmpty(this.author);
},
},
};
@@ -87,7 +87,7 @@ export default {
<div
v-if="assets.links.length || assets.sources.length"
- Sclass="card-text prepend-top-default"
+ class="card-text prepend-top-default"
>
<b>
{{ __('Assets') }}
@@ -98,7 +98,7 @@ export default {
<li v-for="link in assets.links" :key="link.name" class="append-bottom-8">
<gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.url">
<icon name="package" class="align-middle append-right-4 align-text-bottom" />
- {{ link.name }}
+ {{ link.name }} <span v-if="link.external"> {{ __('(external source)') }}</span>
</gl-link>
</li>
</ul>
diff --git a/app/assets/javascripts/reports/components/modal_open_name.vue b/app/assets/javascripts/reports/components/modal_open_name.vue
index 118e4b02c46..4f81cee2a38 100644
--- a/app/assets/javascripts/reports/components/modal_open_name.vue
+++ b/app/assets/javascripts/reports/components/modal_open_name.vue
@@ -26,7 +26,7 @@ export default {
<button
type="button"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
- @click="handleIssueClick();"
+ @click="handleIssueClick()"
>
{{ issue.title }}
</button>
diff --git a/app/assets/javascripts/reports/components/test_issue_body.vue b/app/assets/javascripts/reports/components/test_issue_body.vue
index 938e83de546..7700f49bf7d 100644
--- a/app/assets/javascripts/reports/components/test_issue_body.vue
+++ b/app/assets/javascripts/reports/components/test_issue_body.vue
@@ -30,7 +30,7 @@ export default {
<button
type="button"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
- @click="openModal({ issue });"
+ @click="openModal({ issue })"
>
<div v-if="isNew" class="badge badge-danger append-right-5">{{ s__('New') }}</div>
{{ issue.name }}
diff --git a/app/assets/javascripts/serverless/components/function_details.vue b/app/assets/javascripts/serverless/components/function_details.vue
new file mode 100644
index 00000000000..2b1c21f041b
--- /dev/null
+++ b/app/assets/javascripts/serverless/components/function_details.vue
@@ -0,0 +1,73 @@
+<script>
+import PodBox from './pod_box.vue';
+import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ PodBox,
+ ClipboardButton,
+ },
+ props: {
+ func: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ name() {
+ return this.func.name;
+ },
+ description() {
+ return this.func.description;
+ },
+ funcUrl() {
+ return this.func.url;
+ },
+ podCount() {
+ return this.func.podcount || 0;
+ },
+ },
+};
+</script>
+
+<template>
+ <section id="serverless-function-details">
+ <h3>{{ name }}</h3>
+ <div class="append-bottom-default">
+ <div v-for="line in description.split('\n')" :key="line">{{ line }}<br /></div>
+ </div>
+ <div class="clipboard-group append-bottom-default">
+ <div class="label label-monospace">{{ funcUrl }}</div>
+ <clipboard-button
+ :text="String(funcUrl)"
+ :title="s__('ServerlessDetails|Copy URL to clipboard')"
+ class="input-group-text js-clipboard-btn"
+ />
+ <a
+ :href="funcUrl"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="input-group-text btn btn-default"
+ >
+ <icon name="external-link" />
+ </a>
+ </div>
+
+ <h4>{{ s__('ServerlessDetails|Kubernetes Pods') }}</h4>
+ <div v-if="podCount > 0">
+ <p>
+ <b v-if="podCount == 1">{{ podCount }} {{ s__('ServerlessDetails|pod in use') }}</b>
+ <b v-else>{{ podCount }} {{ s__('ServerlessDetails|pods in use') }}</b>
+ </p>
+ <pod-box :count="podCount" />
+ <p>
+ {{
+ s__('ServerlessDetails|Number of Kubernetes pods in use over time based on necessity.')
+ }}
+ </p>
+ </div>
+ <div v-else><p>No pods loaded at this time.</p></div>
+ </section>
+</template>
diff --git a/app/assets/javascripts/serverless/components/function_row.vue b/app/assets/javascripts/serverless/components/function_row.vue
index 31f5427c771..44bfae388cb 100644
--- a/app/assets/javascripts/serverless/components/function_row.vue
+++ b/app/assets/javascripts/serverless/components/function_row.vue
@@ -15,8 +15,14 @@ export default {
name() {
return this.func.name;
},
- url() {
- return this.func.url;
+ description() {
+ return this.func.description;
+ },
+ detailUrl() {
+ return this.func.detail_url;
+ },
+ environment() {
+ return this.func.environment_scope;
},
image() {
return this.func.image;
@@ -30,11 +36,20 @@ export default {
<template>
<div class="gl-responsive-table-row">
- <div class="table-section section-20">{{ name }}</div>
- <div class="table-section section-50">
- <a :href="url">{{ url }}</a>
+ <div class="table-section section-20 section-wrap">
+ <a :href="detailUrl">{{ name }}</a>
+ </div>
+ <div class="table-section section-10">{{ environment }}</div>
+ <div class="table-section section-40 section-wrap">
+ <span class="line-break">{{ description }}</span>
</div>
<div class="table-section section-20">{{ image }}</div>
<div class="table-section section-10"><timeago :time="timestamp" /></div>
</div>
</template>
+
+<style>
+.line-break {
+ white-space: pre;
+}
+</style>
diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue
index 349e14670b1..9606a78410e 100644
--- a/app/assets/javascripts/serverless/components/functions.vue
+++ b/app/assets/javascripts/serverless/components/functions.vue
@@ -50,8 +50,11 @@ export default {
<div class="table-section section-20" role="rowheader">
{{ s__('Serverless|Function') }}
</div>
- <div class="table-section section-50" role="rowheader">
- {{ s__('Serverless|Domain') }}
+ <div class="table-section section-10" role="rowheader">
+ {{ s__('Serverless|Cluster Env') }}
+ </div>
+ <div class="table-section section-40" role="rowheader">
+ {{ s__('Serverless|Description') }}
</div>
<div class="table-section section-20" role="rowheader">
{{ s__('Serverless|Runtime') }}
diff --git a/app/assets/javascripts/serverless/components/pod_box.vue b/app/assets/javascripts/serverless/components/pod_box.vue
new file mode 100644
index 00000000000..04d3641bce3
--- /dev/null
+++ b/app/assets/javascripts/serverless/components/pod_box.vue
@@ -0,0 +1,36 @@
+<script>
+export default {
+ props: {
+ count: {
+ type: Number,
+ required: true,
+ },
+ color: {
+ type: String,
+ required: false,
+ default: 'green',
+ },
+ },
+ methods: {
+ boxOffset(i) {
+ return 20 * (i - 1);
+ },
+ },
+};
+</script>
+
+<template>
+ <svg :width="boxOffset(count + 1)" :height="20">
+ <rect
+ v-for="i in count"
+ :key="i"
+ width="15"
+ height="15"
+ rx="5"
+ ry="5"
+ :fill="color"
+ :x="boxOffset(i)"
+ y="0"
+ />
+ </svg>
+</template>
diff --git a/app/assets/javascripts/serverless/serverless_bundle.js b/app/assets/javascripts/serverless/serverless_bundle.js
index 3e3b81ba247..47a510d5fb5 100644
--- a/app/assets/javascripts/serverless/serverless_bundle.js
+++ b/app/assets/javascripts/serverless/serverless_bundle.js
@@ -4,23 +4,65 @@ import { s__ } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
import ServerlessStore from './stores/serverless_store';
+import ServerlessDetailsStore from './stores/serverless_details_store';
import GetFunctionsService from './services/get_functions_service';
import Functions from './components/functions.vue';
+import FunctionDetails from './components/function_details.vue';
export default class Serverless {
constructor() {
- const { statusPath, clustersPath, helpPath, installed } = document.querySelector(
- '.js-serverless-functions-page',
- ).dataset;
+ if (document.querySelector('.js-serverless-function-details-page') != null) {
+ const {
+ serviceName,
+ serviceDescription,
+ serviceEnvironment,
+ serviceUrl,
+ serviceNamespace,
+ servicePodcount,
+ } = document.querySelector('.js-serverless-function-details-page').dataset;
+ const el = document.querySelector('#js-serverless-function-details');
+ this.store = new ServerlessDetailsStore();
+ const { store } = this;
- this.service = new GetFunctionsService(statusPath);
- this.knativeInstalled = installed !== undefined;
- this.store = new ServerlessStore(this.knativeInstalled, clustersPath, helpPath);
- this.initServerless();
- this.functionLoadCount = 0;
+ const service = {
+ name: serviceName,
+ description: serviceDescription,
+ environment: serviceEnvironment,
+ url: serviceUrl,
+ namespace: serviceNamespace,
+ podcount: servicePodcount,
+ };
- if (statusPath && this.knativeInstalled) {
- this.initPolling();
+ this.store.updateDetailedFunction(service);
+ this.functionDetails = new Vue({
+ el,
+ data() {
+ return {
+ state: store.state,
+ };
+ },
+ render(createElement) {
+ return createElement(FunctionDetails, {
+ props: {
+ func: this.state.functionDetail,
+ },
+ });
+ },
+ });
+ } else {
+ const { statusPath, clustersPath, helpPath, installed } = document.querySelector(
+ '.js-serverless-functions-page',
+ ).dataset;
+
+ this.service = new GetFunctionsService(statusPath);
+ this.knativeInstalled = installed !== undefined;
+ this.store = new ServerlessStore(this.knativeInstalled, clustersPath, helpPath);
+ this.initServerless();
+ this.functionLoadCount = 0;
+
+ if (statusPath && this.knativeInstalled) {
+ this.initPolling();
+ }
}
}
@@ -55,7 +97,7 @@ export default class Serverless {
resource: this.service,
method: 'fetchData',
successCallback: data => this.handleSuccess(data),
- errorCallback: () => this.handleError(),
+ errorCallback: () => Serverless.handleError(),
});
if (!Visibility.hidden()) {
@@ -64,7 +106,7 @@ export default class Serverless {
this.service
.fetchData()
.then(data => this.handleSuccess(data))
- .catch(() => this.handleError());
+ .catch(() => Serverless.handleError());
}
Visibility.change(() => {
@@ -102,5 +144,6 @@ export default class Serverless {
}
this.functions.$destroy();
+ this.functionDetails.$destroy();
}
}
diff --git a/app/assets/javascripts/serverless/stores/serverless_details_store.js b/app/assets/javascripts/serverless/stores/serverless_details_store.js
new file mode 100644
index 00000000000..5394d2cded1
--- /dev/null
+++ b/app/assets/javascripts/serverless/stores/serverless_details_store.js
@@ -0,0 +1,11 @@
+export default class ServerlessDetailsStore {
+ constructor() {
+ this.state = {
+ functionDetail: {},
+ };
+ }
+
+ updateDetailedFunction(func) {
+ this.state.functionDetail = func;
+ }
+}
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 f04f7606976..7f86741ed29 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
@@ -219,7 +219,7 @@ export default {
name="button"
type="button"
class="js-clear-user-status-button clear-user-status btn"
- @click="clearStatusInputs();"
+ @click="clearStatusInputs()"
>
<icon name="close" />
</button>
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index faea64c9841..c5cfa92f3c8 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -104,9 +104,7 @@ export default {
</div>
<div class="title hide-collapsed">
- {{
- sprintf(__('Lock %{issuableDisplayName}'), { issuableDisplayName: issuableDisplayName })
- }}
+ {{ sprintf(__('Lock %{issuableDisplayName}'), { issuableDisplayName: issuableDisplayName }) }}
<button
v-if="isEditable"
class="float-right lock-edit"
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 d3a4f9c81e0..c03b2a68c78 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -102,13 +102,13 @@ export default {
/>
<div class="title hide-collapsed">
{{ __('Time tracking') }}
- <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true);">
+ <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true)">
<i class="fa fa-question-circle" aria-hidden="true"> </i>
</div>
<div
v-if="showHelpState"
class="close-help-button float-right"
- @click="toggleHelpState(false);"
+ @click="toggleHelpState(false)"
>
<i class="fa fa-close" aria-hidden="true"> </i>
</div>
diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js
index 560f50ebf8f..e5dd7a465ea 100644
--- a/app/assets/javascripts/terminal/terminal.js
+++ b/app/assets/javascripts/terminal/terminal.js
@@ -2,11 +2,13 @@ import _ from 'underscore';
import $ from 'jquery';
import { Terminal } from 'xterm';
import * as fit from 'xterm/lib/addons/fit/fit';
+import * as webLinks from 'xterm/lib/addons/webLinks/webLinks';
import { canScrollUp, canScrollDown } from '~/lib/utils/dom_utils';
const SCROLL_MARGIN = 5;
Terminal.applyAddon(fit);
+Terminal.applyAddon(webLinks);
export default class GLTerminal {
constructor(element, options = {}) {
@@ -48,6 +50,7 @@ export default class GLTerminal {
this.terminal.open(this.container);
this.terminal.fit();
+ this.terminal.webLinksInit();
this.terminal.focus();
this.socket.onopen = () => {
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 dd940548e30..780ecdcdac4 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
@@ -7,7 +7,7 @@ export default {
tooltip,
},
created() {
- this.removesBranchText = __('<strong>Removes</strong> source branch');
+ this.removesBranchText = __('<strong>Deletes</strong> source branch');
this.tooltipTitle = __('A user with write access to the source branch selected this option');
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
index 27352e0b2b1..f6f445c1cef 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -1,10 +1,14 @@
<script>
-import statusIcon from '../mr_widget_status_icon.vue';
+import $ from 'jquery';
+import _ from 'underscore';
+import { s__, sprintf } from '~/locale';
+import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover';
+import StatusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetConflicts',
components: {
- statusIcon,
+ StatusIcon,
},
props: {
/* TODO: This is providing all store and service down when it
@@ -15,6 +19,52 @@ export default {
default: () => ({}),
},
},
+ computed: {
+ popoverTitle() {
+ return s__(
+ 'mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected.',
+ );
+ },
+ showResolveButton() {
+ return this.mr.conflictResolutionPath && this.mr.canMerge;
+ },
+ showPopover() {
+ return this.showResolveButton && this.mr.sourceBranchProtected;
+ },
+ },
+ mounted() {
+ if (this.showPopover) {
+ const $el = $(this.$refs.popover);
+
+ $el
+ .popover({
+ html: true,
+ trigger: 'focus',
+ container: 'body',
+ placement: 'top',
+ template:
+ '<div class="popover" role="tooltip"><div class="arrow"></div><p class="popover-header"></p><div class="popover-body"></div></div>',
+ title: s__(
+ 'mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected.',
+ ),
+ content: sprintf(
+ s__('mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}'),
+ {
+ link_start: `<a href="${_.escape(
+ this.mr.conflictsDocsPath,
+ )}" target="_blank" rel="noopener noreferrer">`,
+ link_end: '</a>',
+ },
+ false,
+ ),
+ })
+ .on('mouseenter', mouseenter)
+ .on('mouseleave', debouncedMouseleave(300))
+ .on('show.bs.popover', () => {
+ window.addEventListener('scroll', togglePopover.bind($el, false), { once: true });
+ });
+ }
+ },
};
</script>
<template>
@@ -38,13 +88,15 @@ To merge this request, first rebase locally.`)
}}
</span>
</span>
- <a
- v-if="mr.canMerge && mr.conflictResolutionPath"
- :href="mr.conflictResolutionPath"
- class="js-resolve-conflicts-button btn btn-default btn-sm"
- >
- {{ s__('mrWidget|Resolve conflicts') }}
- </a>
+ <span v-if="showResolveButton" ref="popover">
+ <a
+ :href="mr.conflictResolutionPath"
+ :disabled="mr.sourceBranchProtected"
+ class="js-resolve-conflicts-button btn btn-default btn-sm"
+ >
+ {{ s__('mrWidget|Resolve conflicts') }}
+ </a>
+ </span>
<button
v-if="mr.canMerge"
class="js-merge-locally-button btn btn-default btn-sm"
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 02c76db4a50..1b3af2fccf2 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
@@ -106,11 +106,11 @@ export default {
<a :href="mr.targetBranchPath" class="label-branch"> {{ mr.targetBranch }} </a>
</p>
<p v-if="mr.shouldRemoveSourceBranch">
- {{ s__('mrWidget|The source branch will be removed') }}
+ {{ s__('mrWidget|The source branch will be deleted') }}
</p>
<p v-else class="d-flex align-items-start">
<span class="append-right-10">
- {{ s__('mrWidget|The source branch will not be removed') }}
+ {{ s__('mrWidget|The source branch will not be deleted') }}
</span>
<a
v-if="canRemoveSourceBranch"
@@ -121,7 +121,7 @@ export default {
@click.prevent="removeSourceBranch"
>
<i v-if="isRemovingSourceBranch" class="fa fa-spinner fa-spin" aria-hidden="true"> </i>
- {{ s__('mrWidget|Remove source branch') }}
+ {{ s__('mrWidget|Delete source branch') }}
</a>
</p>
</section>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
index fe83fe58b67..b9562fbc260 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
@@ -84,7 +84,7 @@ export default {
.removeSourceBranch()
.then(res => res.data)
.then(data => {
- if (data.message === 'Branch was removed') {
+ if (data.message === 'Branch was deleted') {
eventHub.$emit('MRWidgetUpdateRequested', () => {
this.isMakingRequest = false;
});
@@ -174,22 +174,22 @@ export default {
</template>
</p>
<p v-if="mr.sourceBranchRemoved">
- {{ s__('mrWidget|The source branch has been removed') }}
+ {{ s__('mrWidget|The source branch has been deleted') }}
</p>
<p v-if="shouldShowRemoveSourceBranch" class="space-children">
- <span>{{ s__('mrWidget|You can remove source branch now') }}</span>
+ <span>{{ s__('mrWidget|You can delete the source branch now') }}</span>
<button
:disabled="isMakingRequest"
type="button"
class="btn btn-sm btn-default js-remove-branch-button"
@click="removeSourceBranch"
>
- {{ s__('mrWidget|Remove Source Branch') }}
+ {{ s__('mrWidget|Delete source branch') }}
</button>
</p>
<p v-if="shouldShowSourceBranchRemoving">
<gl-loading-icon :inline="true" />
- <span> {{ s__('mrWidget|The source branch is being removed') }} </span>
+ <span> {{ s__('mrWidget|The source branch is being deleted') }} </span>
</p>
</section>
</div>
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 84c8a3464a5..b8f29649eb5 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
@@ -12,7 +12,7 @@ export default {
name: 'ReadyToMerge',
components: {
statusIcon,
- 'squash-before-merge': SquashBeforeMerge,
+ SquashBeforeMerge,
},
props: {
mr: { type: Object, required: true },
@@ -28,6 +28,7 @@ export default {
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
+ squashBeforeMerge: this.mr.squash,
successSvg,
warningSvg,
};
@@ -110,12 +111,6 @@ export default {
return enableSquashBeforeMerge && commitsCount > 1;
},
},
- created() {
- eventHub.$on('MRWidgetUpdateSquash', this.handleUpdateSquash);
- },
- beforeDestroy() {
- eventHub.$off('MRWidgetUpdateSquash', this.handleUpdateSquash);
- },
methods: {
shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
@@ -143,7 +138,7 @@ export default {
commit_message: this.commitMessage,
merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
should_remove_source_branch: this.removeSourceBranch === true,
- squash: this.mr.squash,
+ squash: this.squashBeforeMerge,
};
this.isMakingRequest = true;
@@ -166,9 +161,6 @@ export default {
new Flash('Something went wrong. Please try again.'); // eslint-disable-line
});
},
- handleUpdateSquash(val) {
- this.mr.squash = val;
- },
initiateMergePolling() {
simplePoll((continuePolling, stopPolling) => {
this.handleMergePolling(continuePolling, stopPolling);
@@ -231,7 +223,7 @@ export default {
}
})
.catch(() => {
- new Flash('Something went wrong while removing the source branch. Please try again.'); // eslint-disable-line
+ new Flash('Something went wrong while deleting the source branch. Please try again.'); // eslint-disable-line
});
},
},
@@ -249,7 +241,7 @@ export default {
:class="mergeButtonClass"
type="button"
class="qa-merge-button"
- @click="handleMergeButtonClick();"
+ @click="handleMergeButtonClick()"
>
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
{{ mergeButtonText }}
@@ -273,7 +265,7 @@ export default {
<a
class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
href="#"
- @click.prevent="handleMergeButtonClick(true);"
+ @click.prevent="handleMergeButtonClick(true)"
>
<span class="media">
<span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
@@ -285,7 +277,7 @@ export default {
<a
class="accept-merge-request qa-merge-immediately-option"
href="#"
- @click.prevent="handleMergeButtonClick(false, true);"
+ @click.prevent="handleMergeButtonClick(false, true)"
>
<span class="media">
<span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
@@ -305,14 +297,15 @@ export default {
class="js-remove-source-branch-checkbox"
type="checkbox"
/>
- Remove source branch
+ Delete source branch
</label>
<!-- Placeholder for EE extension of this component -->
<squash-before-merge
v-if="shouldShowSquashBeforeMerge"
- :mr="mr"
- :is-merge-button-disabled="isMergeButtonDisabled"
+ v-model="squashBeforeMerge"
+ :help-path="mr.squashBeforeMergeHelpPath"
+ :is-disabled="isMergeButtonDisabled"
/>
<span v-if="mr.ffOnlyEnabled" class="js-fast-forward-message">
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 e71acf0d7dd..b1f5655a15a 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
@@ -1,6 +1,5 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
-import eventHub from '~/vue_merge_request_widget/event_hub';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
@@ -11,23 +10,19 @@ export default {
tooltip,
},
props: {
- mr: {
- type: Object,
- required: true,
- },
- isMergeButtonDisabled: {
+ value: {
type: Boolean,
required: true,
},
- },
- data() {
- return {
- squashBeforeMerge: this.mr.squash,
- };
- },
- methods: {
- updateSquashModel() {
- eventHub.$emit('MRWidgetUpdateSquash', this.squashBeforeMerge);
+ helpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ isDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
},
},
};
@@ -37,18 +32,19 @@ export default {
<div class="accept-control inline">
<label class="merge-param-checkbox">
<input
- v-model="squashBeforeMerge"
- :disabled="isMergeButtonDisabled"
+ :checked="value"
+ :disabled="isDisabled"
type="checkbox"
name="squash"
class="qa-squash-checkbox"
- @change="updateSquashModel"
+ @change="$emit('input', $event.target.checked)"
/>
{{ __('Squash commits') }}
</label>
<a
+ v-if="helpPath"
v-tooltip
- :href="mr.squashBeforeMergeHelpPath"
+ :href="helpPath"
data-title="About this feature"
data-placement="bottom"
target="_blank"
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 b7f12076958..5a9d86594b1 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
@@ -32,7 +32,6 @@ import MRWidgetStore from './stores/ee_switch_mr_widget_store';
import MRWidgetService from './services/ee_switch_mr_widget_service';
import eventHub from './event_hub';
import stateMaps from './stores/ee_switch_state_maps';
-import SquashBeforeMerge from './components/states/squash_before_merge.vue';
import notify from '~/lib/utils/notify';
import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue';
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
@@ -59,7 +58,6 @@ export default {
'mr-widget-missing-branch': MissingBranchState,
'mr-widget-ready-to-merge': ReadyToMergeState,
'sha-mismatch': ShaMismatchState,
- 'mr-widget-squash-before-merge': SquashBeforeMerge,
'mr-widget-checking': CheckingState,
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
'mr-widget-pipeline-blocked': PipelineBlockedState,
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 066a3b833d7..0cc4fd59f5e 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
@@ -13,6 +13,8 @@ export default function deviseState(data) {
return stateKey.conflicts;
} else if (data.work_in_progress) {
return stateKey.workInProgress;
+ } else if (this.shouldBeRebased) {
+ return stateKey.rebase;
} else if (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed) {
return stateKey.pipelineFailed;
} else if (this.hasMergeableDiscussionsState) {
@@ -25,8 +27,6 @@ export default function deviseState(data) {
return this.mergeError ? stateKey.autoMergeFailed : stateKey.mergeWhenPipelineSucceeds;
} else if (!this.canMerge) {
return stateKey.notAllowedToMerge;
- } else if (this.shouldBeRebased) {
- return stateKey.rebase;
} else if (this.canBeMerged) {
return stateKey.readyToMerge;
}
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 c777bcca0fa..e5a52c6a7f6 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
@@ -29,6 +29,8 @@ export default class MergeRequestStore {
this.title = data.title;
this.targetBranch = data.target_branch;
this.sourceBranch = data.source_branch;
+ this.sourceBranchProtected = data.source_branch_protected;
+ this.conflictsDocsPath = data.conflicts_docs_path;
this.mergeStatus = data.merge_status;
this.commitMessage = data.merge_commit_message;
this.shortMergeCommitSha = data.short_merge_commit_sha;
diff --git a/app/assets/javascripts/vue_shared/components/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue
index 4abf795f7bd..eabf5d4bf60 100644
--- a/app/assets/javascripts/vue_shared/components/bar_chart.vue
+++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue
@@ -293,8 +293,8 @@ export default {
:title="setTooltipTitle(data)"
class="bar-rect"
data-placement="top"
- @mouseover="barHoveredIn(index);"
- @mouseout="barHoveredOut(index);"
+ @mouseover="barHoveredIn(index)"
+ @mouseout="barHoveredOut(index)"
/>
</template>
</g>
diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
index 2129f90d497..36b3ee05456 100644
--- a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
@@ -95,7 +95,7 @@ export default {
class="close float-right"
data-dismiss="modal"
aria-label="Close"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
<span aria-hidden="true">&times;</span>
</button>
@@ -112,7 +112,7 @@ export default {
type="button"
class="btn"
data-dismiss="modal"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
{{ closeButtonLabel }}
</button>
@@ -130,7 +130,7 @@ export default {
type="button"
class="btn js-primary-button"
data-dismiss="modal"
- @click="emitSubmit($event);"
+ @click="emitSubmit($event)"
>
{{ primaryButtonLabel }}
</button>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue
index d5fda7e4ed3..cab92297ca7 100644
--- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue
@@ -75,7 +75,7 @@ export default {
:class="{
active: mode === $options.imageViewMode.twoup,
}"
- @click="changeMode($options.imageViewMode.twoup);"
+ @click="changeMode($options.imageViewMode.twoup)"
>
{{ s__('ImageDiffViewer|2-up') }}
</li>
@@ -83,7 +83,7 @@ export default {
:class="{
active: mode === $options.imageViewMode.swipe,
}"
- @click="changeMode($options.imageViewMode.swipe);"
+ @click="changeMode($options.imageViewMode.swipe)"
>
{{ s__('ImageDiffViewer|Swipe') }}
</li>
@@ -91,7 +91,7 @@ export default {
:class="{
active: mode === $options.imageViewMode.onion,
}"
- @click="changeMode($options.imageViewMode.onion);"
+ @click="changeMode($options.imageViewMode.onion)"
>
{{ s__('ImageDiffViewer|Onion skin') }}
</li>
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 9e713673678..f54033efc54 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -1,11 +1,13 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
+import FileHeader from '~/vue_shared/components/file_row_header.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
export default {
name: 'FileRow',
components: {
+ FileHeader,
FileIcon,
Icon,
ChangedFileIcon,
@@ -34,21 +36,10 @@ export default {
required: false,
default: false,
},
- displayTextKey: {
- type: String,
- required: false,
- default: 'name',
- },
- shouldTruncateStart: {
- type: Boolean,
- required: false,
- default: false,
- },
},
data() {
return {
mouseOver: false,
- truncateStart: 0,
};
},
computed: {
@@ -60,7 +51,7 @@ export default {
},
levelIndentation() {
return {
- marginLeft: `${this.level * 16}px`,
+ marginLeft: this.level ? `${this.level * 16}px` : null,
};
},
fileClass() {
@@ -71,14 +62,8 @@ export default {
'is-open': this.file.opened,
};
},
- outputText() {
- const text = this.file[this.displayTextKey];
-
- if (this.truncateStart === 0) {
- return text;
- }
-
- return `...${text.substring(this.truncateStart, text.length)}`;
+ childFilesLevel() {
+ return this.file.isHeader ? 0 : this.level + 1;
},
},
watch: {
@@ -92,15 +77,6 @@ export default {
if (this.hasPathAtCurrentRoute()) {
this.scrollIntoView(true);
}
-
- if (this.shouldTruncateStart) {
- const { scrollWidth, offsetWidth } = this.$refs.textOutput;
- const textOverflow = scrollWidth - offsetWidth;
-
- if (textOverflow > 0) {
- this.truncateStart = Math.ceil(textOverflow / 5) + 3;
- }
- }
},
methods: {
toggleTreeOpen(path) {
@@ -156,13 +132,15 @@ export default {
<template>
<div>
+ <file-header v-if="file.isHeader" :path="file.path" />
<div
+ v-else
:class="fileClass"
class="file-row"
role="button"
@click="clickFile"
- @mouseover="toggleHover(true);"
- @mouseout="toggleHover(false);"
+ @mouseover="toggleHover(true)"
+ @mouseout="toggleHover(false)"
>
<div class="file-row-name-container">
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
@@ -175,7 +153,7 @@ export default {
:size="16"
/>
<changed-file-icon v-else :file="file" :size="16" class="append-right-5" />
- {{ outputText }}
+ {{ file.name }}
</span>
<component
:is="extraComponent"
@@ -185,17 +163,15 @@ export default {
/>
</div>
</div>
- <template v-if="file.opened">
+ <template v-if="file.opened || file.isHeader">
<file-row
v-for="childFile in file.tree"
:key="childFile.key"
:file="childFile"
- :level="level + 1"
+ :level="childFilesLevel"
:hide-extra-on-tree="hideExtraOnTree"
:extra-component="extraComponent"
:show-changed-icon="showChangedIcon"
- :display-text-key="displayTextKey"
- :should-truncate-start="shouldTruncateStart"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="clickedFile"
/>
diff --git a/app/assets/javascripts/vue_shared/components/file_row_header.vue b/app/assets/javascripts/vue_shared/components/file_row_header.vue
new file mode 100644
index 00000000000..301fd8116a9
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/file_row_header.vue
@@ -0,0 +1,25 @@
+<script>
+import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
+
+const MAX_PATH_LENGTH = 40;
+
+export default {
+ props: {
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ truncatedPath() {
+ return truncatePathMiddleToLength(this.path, MAX_PATH_LENGTH);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="file-row-header bg-white sticky-top p-2 js-file-row-header">
+ <span class="bold">{{ truncatedPath }}</span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
index 834c39a5ee0..4e5dfbf3bf8 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
@@ -1,15 +1,21 @@
<script>
import $ from 'jquery';
+import { GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
/**
* Renders a split dropdown with
* an input that allows to search through the given
* array of options.
+ *
+ * When there are no results and `showCreateMode` is true
+ * it renders a create button with the value typed.
*/
export default {
name: 'FilteredSearchDropdown',
components: {
Icon,
+ GlButton,
},
props: {
title: {
@@ -43,6 +49,16 @@ export default {
type: String,
required: true,
},
+ showCreateMode: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ createButtonText: {
+ type: String,
+ required: false,
+ default: __('Create'),
+ },
},
data() {
return {
@@ -64,6 +80,12 @@ export default {
return this.items.slice(0, this.visibleItems);
},
+ computedCreateButtonText() {
+ return `${this.createButtonText} ${this.filter}`;
+ },
+ shouldRenderCreateButton() {
+ return this.showCreateMode && this.filteredResults.length === 0 && this.filter !== '';
+ },
},
mounted() {
/**
@@ -112,10 +134,20 @@ export default {
<div class="dropdown-content">
<ul>
<li v-for="(result, i) in filteredResults" :key="i" class="js-filtered-dropdown-result">
- <slot name="result" :result="result"> {{ result[filterKey] }} </slot>
+ <slot name="result" :result="result">{{ result[filterKey] }}</slot>
</li>
</ul>
</div>
+
+ <div v-if="shouldRenderCreateButton" class="dropdown-footer">
+ <slot name="footer" :filter="filter">
+ <gl-button
+ class="js-dropdown-create-button btn-transparent"
+ @click="$emit('createItem', filter)"
+ >{{ computedCreateButtonText }}</gl-button
+ >
+ </slot>
+ </div>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
index faf4181bbaf..438851e5ac7 100644
--- a/app/assets/javascripts/vue_shared/components/gl_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -81,7 +81,7 @@ export default {
type="button"
class="close js-modal-close-action"
data-dismiss="modal"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
<span aria-hidden="true">&times;</span>
</button>
@@ -96,7 +96,7 @@ export default {
type="button"
class="btn js-modal-cancel-action qa-modal-cancel-button"
data-dismiss="modal"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
{{ s__('Modal|Cancel') }}
</button>
@@ -105,7 +105,7 @@ export default {
type="button"
class="btn js-modal-primary-action qa-modal-primary-button"
data-dismiss="modal"
- @click="emitSubmit($event);"
+ @click="emitSubmit($event)"
>
{{ footerPrimaryButtonText }}
</button>
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue
new file mode 100644
index 00000000000..df6fadf10cd
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/gl_modal_vuex.vue
@@ -0,0 +1,69 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import { GlModal } from '@gitlab/ui';
+
+/**
+ * This component keeps the GlModal's visibility in sync with the given vuex module.
+ */
+export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ modalId: {
+ type: String,
+ required: true,
+ },
+ modalModule: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState({
+ isVisible(state) {
+ return state[this.modalModule].isVisible;
+ },
+ }),
+ attrs() {
+ const { modalId, modalModule, ...attrs } = this.$attrs;
+
+ return attrs;
+ },
+ },
+ watch: {
+ isVisible(val) {
+ return val ? this.bsShow() : this.bsHide();
+ },
+ },
+ methods: {
+ ...mapActions({
+ syncShow(dispatch) {
+ return dispatch(`${this.modalModule}/show`);
+ },
+ syncHide(dispatch) {
+ return dispatch(`${this.modalModule}/hide`);
+ },
+ }),
+ bsShow() {
+ this.$root.$emit('bv::show::modal', this.modalId);
+ },
+ bsHide() {
+ // $root.$emit is a workaround because other b-modal approaches don't work yet with gl-modal
+ this.$root.$emit('bv::hide::modal', this.modalId);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ v-bind="attrs"
+ :modal-id="modalId"
+ v-on="$listeners"
+ @shown="syncShow"
+ @hidden="syncHide"
+ >
+ <slot></slot>
+ </gl-modal>
+</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 c830f5b49b6..3f45dc7853b 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -148,7 +148,7 @@ export default {
:class="action.cssClass"
container-class="d-inline"
:label="action.label"
- @click="onClickAction(action);"
+ @click="onClickAction(action)"
/>
</template>
</section>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 2f7ed4a982c..cc07ef46064 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -89,7 +89,6 @@ export default {
return this.referencedUsers.length >= referencedUsersThreshold;
},
lineContent() {
- const FIRST_CHAR_REGEX = /^(\+|-)/;
const [firstSuggestion] = this.suggestions;
if (firstSuggestion) {
return firstSuggestion.from_content;
@@ -99,7 +98,7 @@ export default {
const { rich_text: richText, text } = this.line;
if (text) {
- return text.replace(FIRST_CHAR_REGEX, '');
+ return text;
}
return _.unescape(stripHtml(richText).replace(/\n/g, ''));
@@ -183,9 +182,9 @@ export default {
this.hasSuggestion = data.references.suggestions && data.references.suggestions.length;
}
- this.$nextTick(() => {
- $(this.$refs['markdown-preview']).renderGFM();
- });
+ this.$nextTick()
+ .then(() => $(this.$refs['markdown-preview']).renderGFM())
+ .catch(() => new Flash(__('Error rendering markdown preview')));
},
versionedPreviewPath() {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index bf4d42670ee..dbfa32cd0ce 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -78,12 +78,7 @@ export default {
<div class="md-header">
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }" class="md-header-tab">
- <button
- class="js-write-link"
- tabindex="-1"
- type="button"
- @click="writeMarkdownTab($event);"
- >
+ <button class="js-write-link" tabindex="-1" type="button" @click="writeMarkdownTab($event)">
Write
</button>
</li>
@@ -92,7 +87,7 @@ export default {
class="js-preview-link js-md-preview-button"
tabindex="-1"
type="button"
- @click="previewMarkdownTab($event);"
+ @click="previewMarkdownTab($event)"
>
Preview
</button>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
index f98560f7336..b9f884074d0 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
@@ -50,7 +50,7 @@ export default {
:help-page-path="helpPagePath"
@apply="applySuggestion"
/>
- <table class="mb-3 md-suggestion-diff">
+ <table class="mb-3 md-suggestion-diff js-syntax-highlight code">
<tbody>
<!-- Old Line -->
<tr class="line_holder old">
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index 563e2f94fcc..c5a2aa1f2af 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -42,7 +42,7 @@ export default {
<div class="md-suggestion-header border-bottom-0 mt-2">
<div class="qa-suggestion-diff-header font-weight-bold">
{{ __('Suggested change') }}
- <a v-if="helpPagePath" :href="helpPagePath" :aria-label="__('Help')">
+ <a v-if="helpPagePath" :href="helpPagePath" :aria-label="__('Help')" class="js-help-btn">
<icon name="question-o" css-classes="link-highlight" />
</a>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index 7c6dbee3e19..dcda701f049 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -82,13 +82,12 @@ export default {
// extracts the suggested lines from the markdown
// calculates a line number for each line
- const FIRST_CHAR_REGEX = /^(\+|-)/;
const newLines = suggestionEl.querySelectorAll('.line');
const fromLine = this.suggestions.length ? this.suggestions[0].from_line : this.fromLine;
const lines = [];
newLines.forEach((line, i) => {
- const content = `${line.innerText.replace(FIRST_CHAR_REGEX, '')}\n`;
+ const content = `${line.innerText}\n`;
const lineNumber = fromLine + i;
lines.push({ content, lineNumber });
});
@@ -130,7 +129,7 @@ export default {
<template>
<div>
- <div class="flash-container mt-3"></div>
+ <div class="flash-container js-suggestions-flash"></div>
<div v-show="isRendered" ref="container" v-html="noteHtml"></div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
index 09a64502819..f8983a3d29a 100644
--- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
@@ -58,7 +58,7 @@ export default {
active: tab.isActive,
}"
>
- <a :class="`js-${scope}-tab-${tab.scope}`" role="button" @click="onTabClick(tab);">
+ <a :class="`js-${scope}-tab-${tab.scope}`" role="button" @click="onTabClick(tab)">
{{ tab.name }}
<span v-if="shouldRenderBadge(tab.count)" class="badge badge-pill"> {{ tab.count }} </span>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 31df26f7b05..b0af8399955 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -97,7 +97,7 @@ export default {
v-html="note.note_html"
></div>
<div v-if="hasMoreCommits" class="flex-list">
- <div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded;">
+ <div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded">
<icon :name="toggleIcon" :size="8" class="append-right-5" />
<span>Toggle commit list</span>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
index 1c6c3fc4734..df19906309c 100644
--- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -19,7 +19,7 @@ export default {
data() {
return {
script: {},
- scriptSrc: 'https://www.google.com/recaptcha/api.js',
+ scriptSrc: 'https://www.recaptcha.net/recaptcha/api.js',
};
},
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
index 82067129c57..6c0c7f15943 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
@@ -134,7 +134,7 @@ export default {
<button
type="button"
class="btn-blank btn-link btn-secondary-hover-link"
- @click="newDateSelected(null);"
+ @click="newDateSelected(null)"
>
remove
</button>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 01e655d27e5..2a34b4630f2 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -149,7 +149,7 @@ export default {
}"
class="page-item"
>
- <a class="page-link" @click.prevent="changePage(item.title, item.disabled);">
+ <a class="page-link" @click.prevent="changePage(item.title, item.disabled)">
{{ item.title }}
</a>
</li>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
index e833a8e0483..95f4395ac13 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -67,6 +67,7 @@ export default {
// In both cases we should render the defaultAvatarUrl
sanitizedSource() {
let baseSrc = this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
+ // Only adds the width to the URL if its not a base64 data image
if (!baseSrc.startsWith('data:') && !baseSrc.includes('?')) baseSrc += `?width=${this.size}`;
return baseSrc;
},
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue
new file mode 100644
index 00000000000..8eaf8386b99
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_list.vue
@@ -0,0 +1,89 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { sprintf, __ } from '~/locale';
+import UserAvatarLink from './user_avatar_link.vue';
+
+export default {
+ components: {
+ UserAvatarLink,
+ GlButton,
+ },
+ props: {
+ items: {
+ type: Array,
+ required: true,
+ },
+ breakpoint: {
+ type: Number,
+ required: false,
+ default: 10,
+ },
+ imgSize: {
+ type: Number,
+ required: false,
+ default: 20,
+ },
+ emptyText: {
+ type: String,
+ required: false,
+ default: __('None'),
+ },
+ },
+ data() {
+ return {
+ isExpanded: false,
+ };
+ },
+ computed: {
+ visibleItems() {
+ if (!this.hasHiddenItems) {
+ return this.items;
+ }
+
+ return this.items.slice(0, this.breakpoint);
+ },
+ hasHiddenItems() {
+ return this.hasBreakpoint && !this.isExpanded && this.items.length > this.breakpoint;
+ },
+ hasBreakpoint() {
+ return this.breakpoint > 0 && this.items.length > this.breakpoint;
+ },
+ expandText() {
+ if (!this.hasHiddenItems) {
+ return '';
+ }
+
+ const count = this.items.length - this.breakpoint;
+
+ return sprintf(__('%{count} more'), { count });
+ },
+ },
+ methods: {
+ expand() {
+ this.isExpanded = true;
+ },
+ collapse() {
+ this.isExpanded = false;
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="!items.length">{{ emptyText }}</div>
+ <div v-else>
+ <user-avatar-link
+ v-for="item in visibleItems"
+ :key="item.id"
+ :link-href="item.web_url"
+ :img-src="item.avatar_url"
+ :img-alt="item.name"
+ :tooltip-text="item.name"
+ :img-size="imgSize"
+ />
+ <template v-if="hasBreakpoint">
+ <gl-button v-if="hasHiddenItems" variant="link" @click="expand"> {{ expandText }} </gl-button>
+ <gl-button v-else variant="link" @click="collapse"> {{ __('show less') }} </gl-button>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vuex_shared/modules/modal/actions.js b/app/assets/javascripts/vuex_shared/modules/modal/actions.js
new file mode 100644
index 00000000000..7b209909f69
--- /dev/null
+++ b/app/assets/javascripts/vuex_shared/modules/modal/actions.js
@@ -0,0 +1,20 @@
+import * as types from './mutation_types';
+
+export const open = ({ commit }, data) => {
+ commit(types.OPEN, data);
+};
+
+export const close = ({ commit }) => {
+ commit(types.CLOSE);
+};
+
+export const show = ({ commit }) => {
+ commit(types.SHOW);
+};
+
+export const hide = ({ commit }) => {
+ commit(types.HIDE);
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/vuex_shared/modules/modal/index.js b/app/assets/javascripts/vuex_shared/modules/modal/index.js
new file mode 100644
index 00000000000..c349d875c24
--- /dev/null
+++ b/app/assets/javascripts/vuex_shared/modules/modal/index.js
@@ -0,0 +1,10 @@
+import state from './state';
+import mutations from './mutations';
+import * as actions from './actions';
+
+export default () => ({
+ namespaced: true,
+ state: state(),
+ mutations,
+ actions,
+});
diff --git a/app/assets/javascripts/vuex_shared/modules/modal/mutation_types.js b/app/assets/javascripts/vuex_shared/modules/modal/mutation_types.js
new file mode 100644
index 00000000000..f8259736009
--- /dev/null
+++ b/app/assets/javascripts/vuex_shared/modules/modal/mutation_types.js
@@ -0,0 +1,4 @@
+export const HIDE = 'HIDE';
+export const SHOW = 'SHOW';
+export const OPEN = 'OPEN';
+export const CLOSE = 'CLOSE';
diff --git a/app/assets/javascripts/vuex_shared/modules/modal/mutations.js b/app/assets/javascripts/vuex_shared/modules/modal/mutations.js
new file mode 100644
index 00000000000..9e96ae8b5a9
--- /dev/null
+++ b/app/assets/javascripts/vuex_shared/modules/modal/mutations.js
@@ -0,0 +1,18 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SHOW](state) {
+ state.isVisible = true;
+ },
+ [types.HIDE](state) {
+ state.isVisible = false;
+ },
+ [types.OPEN](state, data) {
+ state.data = data;
+ state.isVisible = true;
+ },
+ [types.CLOSE](state) {
+ state.data = null;
+ state.isVisible = false;
+ },
+};
diff --git a/app/assets/javascripts/vuex_shared/modules/modal/state.js b/app/assets/javascripts/vuex_shared/modules/modal/state.js
new file mode 100644
index 00000000000..5d0955aa9b0
--- /dev/null
+++ b/app/assets/javascripts/vuex_shared/modules/modal/state.js
@@ -0,0 +1,4 @@
+export default () => ({
+ isVisible: false,
+ data: null,
+});
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index 587127bb059..c8357f7751c 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -225,7 +225,7 @@ h3.popover-header {
}
.info-well {
- background: $theme-gray-50;
+ background: $gray-50;
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: 4px;
diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss
new file mode 100644
index 00000000000..048a5c0300c
--- /dev/null
+++ b/app/assets/stylesheets/components/related_items_list.scss
@@ -0,0 +1,381 @@
+$item-path-max-width: 160px;
+$item-milestone-max-width: 120px;
+$item-weight-max-width: 48px;
+
+.related-items-list {
+ padding: $gl-padding-4;
+
+ &,
+ .list-item:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.item-body {
+ display: flex;
+ position: relative;
+ align-items: center;
+ padding: $gl-padding-8;
+ line-height: $gl-line-height;
+
+ .item-contents {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ flex-grow: 1;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed,
+ .confidential-icon,
+ .item-milestone .icon,
+ .item-weight .board-card-info-icon {
+ min-width: $gl-padding;
+ cursor: help;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ margin-right: $gl-padding-4;
+ }
+
+ .confidential-icon {
+ align-self: baseline;
+ color: $orange-600;
+ margin-right: $gl-padding-4;
+ }
+
+ .item-title {
+ flex-basis: 100%;
+ margin-bottom: $gl-padding-8;
+ font-size: $gl-font-size-small;
+
+ &.mr-title {
+ font-weight: $gl-font-weight-bold;
+ }
+
+ .sortable-link {
+ max-width: 85%;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: none;
+ }
+ }
+
+ .item-meta {
+ display: flex;
+ flex-wrap: wrap;
+ flex-basis: 100%;
+ font-size: $gl-font-size-small;
+ color: $gl-text-color-secondary;
+
+ .item-meta-child {
+ order: 0;
+ display: flex;
+ flex-wrap: wrap;
+ flex-basis: 100%;
+
+ .item-due-date,
+ .item-weight {
+ margin-left: $gl-padding-8;
+ }
+
+ .item-milestone,
+ .item-weight {
+ cursor: help;
+ }
+
+ .item-milestone {
+ text-decoration: none;
+ max-width: $item-milestone-max-width;
+ }
+
+ .item-due-date {
+ margin-right: 0;
+ }
+
+ .item-weight {
+ margin-right: 0;
+ max-width: $item-weight-max-width;
+ }
+ }
+
+ .item-path-id .path-id-text,
+ .item-milestone .milestone-title,
+ .item-due-date,
+ .item-weight .board-card-info-text {
+ color: $gl-text-color-secondary;
+ display: inline-block;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+
+ .item-path-id {
+ margin-top: $gl-padding-4;
+ font-size: $gl-font-size-xs;
+ white-space: nowrap;
+
+ .path-id-text {
+ font-weight: $gl-font-weight-bold;
+ max-width: $item-path-max-width;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: block;
+ }
+
+ &:not(.mr-item-path) {
+ order: 1;
+ }
+ }
+
+ .item-milestone .ic-clock {
+ color: $gl-text-color-tertiary;
+ margin-right: $gl-padding-4;
+ }
+
+ .item-assignees {
+ order: 2;
+ align-self: flex-end;
+ align-items: center;
+ margin-left: auto;
+
+ .user-avatar-link {
+ margin-right: -$gl-padding-4;
+
+ &:nth-of-type(1) {
+ z-index: 2;
+ }
+
+ &:nth-of-type(2) {
+ z-index: 1;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ .avatar {
+ height: $gl-padding;
+ width: $gl-padding;
+ margin-right: 0;
+ vertical-align: bottom;
+ }
+
+ .avatar-counter {
+ height: $gl-padding;
+ border: 1px solid transparent;
+ background-color: $gl-text-color-tertiary;
+ font-weight: $gl-font-weight-bold;
+ padding: 0 $gl-padding-4;
+ line-height: $gl-padding;
+ }
+ }
+ }
+
+ .btn-item-remove {
+ position: absolute;
+ right: 0;
+ top: $gl-padding-4 / 2;
+ padding: $gl-padding-4;
+ margin-right: $gl-padding-4 / 2;
+ line-height: 0;
+ border-color: transparent;
+ color: $gl-text-color-secondary;
+
+ &:hover {
+ color: $gl-text-color;
+ }
+ }
+}
+
+.mr-status-wrapper,
+.mr-ci-status
+ {
+ line-height: 0;
+}
+
+@include media-breakpoint-up(sm) {
+ .item-body {
+ .item-contents .item-title {
+ .mr-title-link,
+ .sortable-link {
+ max-width: 90%;
+ }
+ }
+ }
+}
+
+/* Small devices (landscape phones, 768px and up) */
+@include media-breakpoint-up(md) {
+ .item-body {
+ .item-contents {
+ min-width: 0;
+
+ .item-title {
+ flex-basis: unset;
+ // 95% because we compensate
+ // for remove button which is
+ // positioned absolutely
+ width: 95%;
+ margin-bottom: $gl-padding-4;
+
+ .mr-title-link,
+ .sortable-link {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ max-width: 100%;
+ }
+ }
+
+ .item-meta {
+ .item-path-id {
+ order: 0;
+ margin-top: 0;
+ }
+
+ .item-meta-child {
+ flex-basis: unset;
+ margin-left: auto;
+ margin-right: $gl-padding-4;
+
+ ~ .item-assignees {
+ margin-left: $gl-padding-4;
+ }
+ }
+
+ .item-assignees {
+ margin-bottom: 0;
+ margin-left: 0;
+ order: 2;
+ }
+ }
+ }
+
+ .btn-item-remove {
+ order: 1;
+ }
+ }
+}
+
+/* Medium devices (desktops, 992px and up) */
+@include media-breakpoint-up(lg) {
+ .item-body {
+ padding: $gl-padding;
+
+ .item-title {
+ font-size: $gl-font-size;
+ }
+
+ .item-meta .item-path-id {
+ font-size: inherit; // Base size given to `item-meta` is `$gl-font-size-small`
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ margin-right: $gl-padding-4;
+ }
+ }
+}
+
+/* Large devices (large desktops, 1200px and up) */
+@include media-breakpoint-up(xl) {
+ .item-body {
+ padding: $gl-padding-8;
+ padding-left: $gl-padding;
+
+ .item-contents {
+ flex-wrap: nowrap;
+ overflow: hidden;
+
+ .item-title {
+ display: flex;
+ margin-bottom: 0;
+ min-width: 0;
+ width: auto;
+ flex-basis: unset;
+ font-weight: $gl-font-weight-normal;
+
+ .mr-title-link,
+ .sortable-link {
+ display: block;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: block;
+ margin-right: $gl-padding-8;
+ }
+
+ .confidential-icon {
+ align-self: auto;
+ margin-top: 0;
+ }
+ }
+
+ .item-meta {
+ margin-top: 0;
+ justify-content: flex-end;
+ flex: 1;
+ flex-wrap: nowrap;
+
+ .item-path-id {
+ order: 0;
+ margin-top: 0;
+ margin-left: $gl-padding-8;
+ margin-right: auto;
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: none;
+ }
+ }
+
+ .item-meta-child {
+ margin-left: $gl-padding-8;
+ flex-wrap: nowrap;
+ }
+
+ .item-assignees {
+ flex-grow: 0;
+ margin-top: 0;
+ margin-right: $gl-padding-4;
+
+ .avatar {
+ height: $gl-padding-24;
+ width: $gl-padding-24;
+ }
+
+ .avatar-counter {
+ height: $gl-padding-24;
+ min-width: $gl-padding-24;
+ line-height: $gl-padding-24;
+ border-radius: $gl-padding-24;
+ }
+ }
+ }
+ }
+
+ .btn-item-remove {
+ position: relative;
+ align-self: center;
+ top: initial;
+ right: 0;
+ margin-right: 0;
+ padding: $btn-sm-side-margin;
+
+ &:hover {
+ border-color: $border-color;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 43d4044033f..4fb787887a1 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -204,7 +204,7 @@ a {
[class^="skeleton-line-"] {
position: relative;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
height: 10px;
overflow: hidden;
@@ -220,10 +220,10 @@ a {
background-size: cover;
background-image: linear-gradient(
to right,
- $theme-gray-100 0%,
- $theme-gray-50 20%,
- $theme-gray-100 40%,
- $theme-gray-100 100%
+ $gray-100 0%,
+ $gray-50 20%,
+ $gray-100 40%,
+ $gray-100 100%
);
height: 10px;
}
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 7a95db5976d..ad650d45314 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -176,7 +176,7 @@
&.user-authored {
cursor: default;
background-color: $gray-light;
- border-color: $theme-gray-200;
+ border-color: $gray-200;
color: $gl-text-color-disabled;
gl-emoji {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index a4a9276c580..5d2cbdde8dc 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -483,7 +483,7 @@
// All disabled buttons, regardless of color, type, etc
%disabled {
background-color: $gray-light !important;
- border-color: $theme-gray-200 !important;
+ border-color: $gray-200 !important;
color: $gl-text-color-disabled !important;
opacity: 1 !important;
cursor: default !important;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index e037b02a30c..0fb9bde1785 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -381,13 +381,21 @@ img.emoji {
.inline { display: inline-block; }
.center { text-align: center; }
.vertical-align-middle { vertical-align: middle; }
+.vertical-align-sub { vertical-align: sub; }
.flex-align-self-center { align-self: center; }
.flex-grow { flex-grow: 1; }
.flex-no-shrink { flex-shrink: 0; }
-.mw-460 { max-width: 460px; }
.ws-initial { white-space: initial; }
+.overflow-auto { overflow: auto; }
+
+/** COMMON SIZING CLASSES **/
+.w-0 { width: 0; }
+.h-13em { height: 13em; }
+.mw-460 { max-width: 460px; }
+.mw-6em { max-width: 6em; }
.min-height-0 { min-height: 0; }
+/** COMMON SPACING CLASSES **/
.gl-pl-0 { padding-left: 0; }
.gl-pl-1 { padding-left: #{0.5 * $grid-size}; }
.gl-pl-2 { padding-left: $grid-size; }
@@ -401,3 +409,14 @@ img.emoji {
.gl-pr-3 { padding-right: #{2 * $grid-size}; }
.gl-pr-4 { padding-right: #{3 * $grid-size}; }
.gl-pr-5 { padding-right: #{4 * $grid-size}; }
+
+/**
+ * Removes browser specific clear icon from input fields in
+ * Internet Explorer 10, Internet Explorer 11, and Microsoft Edge.
+ * This is intended for elements which add a customized clear icon.
+ *
+ * see also https://developer.mozilla.org/en-US/docs/Web/CSS/::-ms-clear
+ */
+.ms-no-clear ::-ms-clear {
+ display: none;
+}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index afcb230797a..b90db135b4a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -32,6 +32,10 @@
max-height: $dropdown-max-height;
overflow-y: auto;
+ &.dropdown-extended-height {
+ max-height: 400px;
+ }
+
@include media-breakpoint-down(xs) {
width: 100%;
}
@@ -125,7 +129,7 @@
@extend .dropdown-toggle;
padding-right: 25px;
position: relative;
- width: 163px;
+ width: 160px;
text-overflow: ellipsis;
overflow: hidden;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 4da2243981e..cbf9ee24ec5 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -36,6 +36,15 @@ label {
}
}
+.label-wrapper {
+ display: block;
+ margin: 0;
+}
+
+.form-label {
+ @extend label;
+}
+
.form-control-label {
@extend .col-md-2;
}
@@ -252,7 +261,7 @@ label {
right: 0.8em;
top: 50%;
transform: translateY(-50%);
- color: $theme-gray-600;
+ color: $gray-600;
}
}
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index 0ef50e139f2..418eafa153c 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -282,31 +282,31 @@ body {
&.ui-dark {
@include gitlab-theme(
- $theme-gray-200,
- $theme-gray-500,
- $theme-gray-700,
- $theme-gray-800,
- $theme-gray-900,
+ $gray-200,
+ $gray-500,
+ $gray-700,
+ $gray-800,
+ $gray-900,
$white-light
);
}
&.ui-light {
@include gitlab-theme(
- $theme-gray-700,
- $theme-gray-800,
- $theme-gray-700,
- $theme-gray-700,
- $theme-gray-100,
- $theme-gray-700
+ $gray-700,
+ $gray-800,
+ $gray-700,
+ $gray-700,
+ $gray-100,
+ $gray-700
);
.navbar-gitlab {
- background-color: $theme-gray-100;
+ background-color: $gray-100;
box-shadow: 0 1px 0 0 $border-color;
.logo-text svg {
- fill: $theme-gray-900;
+ fill: $gray-900;
}
.navbar-sub-nav,
@@ -315,7 +315,7 @@ body {
> a:hover,
> a:focus,
> button:hover {
- color: $theme-gray-900;
+ color: $gray-900;
}
&.active > a,
@@ -329,8 +329,8 @@ body {
.container-fluid {
.navbar-toggler,
.navbar-toggler:hover {
- color: $theme-gray-700;
- border-left: 1px solid $theme-gray-200;
+ color: $gray-700;
+ border-left: 1px solid $gray-200;
}
}
}
@@ -348,7 +348,7 @@ body {
.search-input-wrap {
.search-icon {
- fill: $theme-gray-200;
+ fill: $gray-200;
}
.search-input {
@@ -359,16 +359,16 @@ body {
.nav-sidebar li.active {
> a {
- color: $theme-gray-900;
+ color: $gray-900;
}
svg {
- fill: $theme-gray-900;
+ fill: $gray-900;
}
}
.sidebar-top-level-items > li.active .badge.badge-pill {
- color: $theme-gray-900;
+ color: $gray-900;
}
}
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 5574873fa22..36dd1cee4de 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -596,6 +596,10 @@
.emoji-menu-toggle-button {
@include emoji-menu-toggle-button;
}
+
+ .input-group {
+ height: 34px;
+ }
}
.nav-links > li > a {
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 8db7d63266e..49b9b7014ae 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -87,8 +87,8 @@
display: flex;
align-items: center;
justify-content: center;
- border: $border-size solid $theme-gray-400;
+ border: $border-size solid $gray-400;
border-radius: 50%;
padding: $gl-padding-8 - $border-size;
- color: $theme-gray-700;
+ color: $gray-700;
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index c674d4c23ea..679148ddf7b 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -158,7 +158,7 @@
max-height: calc(100vh - 100px);
}
- table {
+ table:not(.js-syntax-highlight) {
@include markdown-table;
}
}
@@ -173,7 +173,7 @@
svg {
width: 14px;
height: 14px;
- margin-top: 3px;
+ vertical-align: middle;
fill: $gl-text-color-secondary;
}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 46d40ea7aa5..ace46e32b18 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -101,3 +101,41 @@ body.modal-open {
margin: 0;
}
}
+
+.issues-import-modal,
+.issues-export-modal {
+ .modal-header {
+ justify-content: flex-start;
+
+ .import-export-svg-container {
+ flex-grow: 1;
+ height: 56px;
+ padding: $gl-btn-padding $gl-btn-padding 0;
+
+ > svg {
+ float: right;
+ height: 100%;
+ }
+ }
+ }
+
+ .modal-body {
+ padding: 0;
+
+ .modal-subheader {
+ justify-content: flex-start;
+ align-items: center;
+ border-bottom: 1px solid $modal-border-color;
+ padding: 14px;
+ }
+
+ .modal-text {
+ padding: $gl-padding-24 $gl-padding;
+ min-height: $modal-body-height;
+ }
+ }
+
+ .checkmark {
+ color: $green-400;
+ }
+}
diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss
index 29a9c076cdf..6bd44ee19bd 100644
--- a/app/assets/stylesheets/framework/responsive_tables.scss
+++ b/app/assets/stylesheets/framework/responsive_tables.scss
@@ -39,7 +39,7 @@
.table-section {
white-space: nowrap;
- $section-widths: 5 10 15 20 25 30 40 50 60 100;
+ $section-widths: 5 10 15 20 25 30 40 50 60 70 80 100;
@each $width in $section-widths {
&.section-#{$width} {
flex: 0 0 #{$width + '%'};
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index a68f1e4e570..bcd601e198a 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -57,6 +57,16 @@
color: $gl-text-color;
}
}
+
+ &.is-invalid {
+ ~ .invalid-feedback {
+ display: block;
+ }
+
+ .select2-choices {
+ border-color: $red-500;
+ }
+ }
}
.select2-drop,
@@ -67,10 +77,18 @@
min-width: 175px;
color: $gl-text-color;
z-index: 999;
+
+ .modal-open & {
+ z-index: $zindex-modal + 200;
+ }
}
.select2-drop-mask {
z-index: 998;
+
+ .modal-open & {
+ z-index: $zindex-modal + 100;
+ }
}
.select2-drop.select2-drop-above.select2-drop-active {
diff --git a/app/assets/stylesheets/framework/stacked_progress_bar.scss b/app/assets/stylesheets/framework/stacked_progress_bar.scss
index 29a2d5881f7..2255d3be75b 100644
--- a/app/assets/stylesheets/framework/stacked_progress_bar.scss
+++ b/app/assets/stylesheets/framework/stacked_progress_bar.scss
@@ -3,7 +3,7 @@
height: 16px;
border-radius: 10px;
overflow: hidden;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
.status-unavailable,
.status-green,
@@ -24,7 +24,7 @@
.status-unavailable {
padding: 0 10px;
- color: $theme-gray-700;
+ color: $gray-700;
}
.status-green {
@@ -36,11 +36,11 @@
}
.status-neutral {
- background-color: $theme-gray-200;
+ background-color: $gray-200;
color: $gl-gray-dark;
&:hover {
- background-color: $theme-gray-300;
+ background-color: $gray-300;
}
}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 6954e6599b1..295a5b5ee7a 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -50,6 +50,14 @@ table {
border-color: $white-normal;
}
}
+
+ .thead-white {
+ th {
+ background-color: $white-light;
+ color: $gl-text-color-secondary;
+ border-top: 0;
+ }
+ }
}
&.responsive-table {
@@ -153,3 +161,4 @@ table {
border-top: 0;
}
}
+
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 0c81dc2e156..a08639936c0 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -143,11 +143,12 @@
margin: 0 0 16px;
}
- table {
+ table:not(.js-syntax-highlight) {
@extend .table;
@extend .table-bordered;
margin: 16px 0;
color: $gl-text-color;
+ border: 0;
th {
background: $label-gray-bg;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 7e53f1ec48d..e886a54dc99 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -32,6 +32,15 @@ $gray-dark: darken($gray-light, $darken-dark-factor);
$gray-darker: #eee;
$gray-darkest: #c4c4c4;
+$black: #000;
+$black-transparent: rgba(0, 0, 0, 0.3);
+$almost-black: #242424;
+
+$t-gray-a-02: rgba($black, 0.02);
+$t-gray-a-04: rgba($black, 0.04);
+$t-gray-a-06: rgba($black, 0.06);
+$t-gray-a-08: rgba($black, 0.08);
+
$gl-gray-100: #dddddd;
$gl-gray-200: #cccccc;
$gl-gray-350: #aaaaaa;
@@ -88,6 +97,18 @@ $red-800: #8b2615;
$red-900: #711e11;
$red-950: #4b140b;
+$gray-50: #fafafa;
+$gray-100: #f2f2f2;
+$gray-200: #dfdfdf;
+$gray-300: #cccccc;
+$gray-400: #bababa;
+$gray-500: #a7a7a7;
+$gray-600: #919191;
+$gray-700: #707070;
+$gray-800: #4f4f4f;
+$gray-900: #2e2e2e;
+$gray-950: #1f1f1f;
+
// GitLab themes
$indigo-50: #f7f7ff;
@@ -102,18 +123,6 @@ $indigo-800: #393982;
$indigo-900: #292961;
$indigo-950: #1a1a40;
-$theme-gray-50: #fafafa;
-$theme-gray-100: #f2f2f2;
-$theme-gray-200: #dfdfdf;
-$theme-gray-300: #cccccc;
-$theme-gray-400: #bababa;
-$theme-gray-500: #a7a7a7;
-$theme-gray-600: #919191;
-$theme-gray-700: #707070;
-$theme-gray-800: #4f4f4f;
-$theme-gray-900: #2e2e2e;
-$theme-gray-950: #1f1f1f;
-
$theme-blue-50: #f4f8fc;
$theme-blue-100: #e6edf5;
$theme-blue-200: #c8d7e6;
@@ -170,11 +179,6 @@ $theme-light-red-500: #c24b38;
$theme-light-red-600: #b03927;
$theme-light-red-700: #a62e21;
-$black: #000;
-$black-transparent: rgba(0, 0, 0, 0.3);
-$shadow-color: rgba($black, 0.1);
-$almost-black: #242424;
-
$border-white-light: darken($white-light, $darken-border-factor);
$border-white-normal: darken($white-normal, $darken-border-factor);
@@ -187,6 +191,7 @@ $border-gray-dark: darken($white-normal, $darken-border-factor);
* UI elements
*/
$border-color: #e5e5e5;
+$shadow-color: $t-gray-a-08;
$well-expand-item: #e8f2f7;
$well-inner-border: #eef0f2;
$well-light-border: #f1f1f1;
@@ -651,6 +656,7 @@ $border-color-settings: #e1e1e1;
Modals
*/
$modal-body-height: 134px;
+$modal-border-color: #e9ecef;
$priority-label-empty-state-width: 114px;
@@ -658,3 +664,8 @@ $priority-label-empty-state-width: 114px;
Issues Analytics
*/
$issues-analytics-popover-boarder-color: rgba(0, 0, 0, 0.15);
+/*
+Merge Requests
+*/
+$mr-tabs-height: 51px;
+$mr-version-controls-height: 56px;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index 069f45bff49..1dfe2a69a2f 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -5,7 +5,7 @@
$secondary: $gray-light;
$input-disabled-bg: $gray-light;
-$input-border-color: $theme-gray-200;
+$input-border-color: $gray-200;
$input-color: $gl-text-color;
$font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font;
@@ -20,7 +20,7 @@ $warning: $orange-500;
$danger: $red-500;
$zindex-modal-backdrop: 1040;
$nav-divider-margin-y: ($grid-size / 2);
-$dropdown-divider-bg: $theme-gray-200;
+$dropdown-divider-bg: $gray-200;
$dropdown-item-padding-y: 8px;
$dropdown-item-padding-x: 12px;
$popover-max-width: 300px;
@@ -34,3 +34,12 @@ $h3-font-size: 14px * 1.75;
$h4-font-size: 14px * 1.5;
$h5-font-size: 14px * 1.25;
$h6-font-size: 14px;
+$spacer: $grid-size;
+$spacers: (
+ 0: 0,
+ 1: ($spacer * .5),
+ 2: ($spacer),
+ 3: ($spacer * 2),
+ 4: ($spacer * 3),
+ 5: ($spacer * 4)
+);
diff --git a/app/assets/stylesheets/highlight/none.scss b/app/assets/stylesheets/highlight/none.scss
index 7d692a87e33..7ced4e82e66 100644
--- a/app/assets/stylesheets/highlight/none.scss
+++ b/app/assets/stylesheets/highlight/none.scss
@@ -38,7 +38,7 @@
$none-over-bg: #ded7fc;
$none-expanded-border: #e0e0e0;
- $none-expanded-bg: #f7f7f7;
+ $none-expanded-bg: #e0e0e0;
.line_holder {
@@ -50,18 +50,12 @@
.diff-line-num {
&.old {
- background-color: $line-number-old;
- border-color: $line-removed-dark;
-
a {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.new {
- background-color: $line-number-new;
- border-color: $line-added-dark;
-
a {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
@@ -78,8 +72,8 @@
}
&.hll:not(.empty-cell) {
- background-color: $line-number-select;
- border-color: $line-select-yellow-dark;
+ background-color: $white-light;
+ border-color: $white-normal;
}
}
@@ -101,26 +95,28 @@
.line_content {
&.old {
- background-color: $line-removed;
+ background-color: $white-normal;
&::before {
- color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
+ color: $gl-text-color;
}
span.idiff {
- background-color: $line-removed-dark;
+ background-color: $white-normal;
+ text-decoration: underline;
}
}
&.new {
- background-color: $line-added;
+ background-color: $white-normal;
&::before {
- color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
+ color: $gl-text-color;
}
span.idiff {
- background-color: $line-added-dark;
+ background-color: $white-normal;
+ text-decoration: underline;
}
}
@@ -129,7 +125,7 @@
}
&.hll:not(.empty-cell) {
- background-color: $line-select-yellow;
+ background-color: $white-normal;
}
}
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 98d0a2d43ea..553cc44fe83 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -130,7 +130,7 @@ $ide-commit-header-height: 48px;
background: none;
border: 0;
border-radius: $border-radius-default;
- color: $theme-gray-900;
+ color: $gray-900;
svg {
position: relative;
@@ -145,7 +145,7 @@ $ide-commit-header-height: 48px;
}
&:not([disabled]):hover {
- background-color: $theme-gray-200;
+ background-color: $gray-200;
}
&:not([disabled]):focus {
@@ -265,7 +265,7 @@ $ide-commit-header-height: 48px;
.margin {
background-color: $white-light;
- border-right: 1px solid $theme-gray-100;
+ border-right: 1px solid $gray-100;
.line-insert {
border-right: 1px solid $line-added-dark;
@@ -292,7 +292,7 @@ $ide-commit-header-height: 48px;
.monaco-editor,
.monaco-editor-background,
.monaco-editor .inputarea.ime-input {
- background-color: $theme-gray-50;
+ background-color: $gray-50;
}
}
}
@@ -527,7 +527,7 @@ $ide-commit-header-height: 48px;
display: block;
margin-left: auto;
margin-right: auto;
- color: $theme-gray-700;
+ color: $gray-700;
}
.file-status-icon {
@@ -551,7 +551,7 @@ $ide-commit-header-height: 48px;
&:hover,
&:focus {
- background: $theme-gray-100;
+ background: $gray-100;
outline: 0;
@@ -563,7 +563,7 @@ $ide-commit-header-height: 48px;
}
&:active {
- background: $theme-gray-200;
+ background: $gray-200;
}
&.is-active {
@@ -767,12 +767,12 @@ $ide-commit-header-height: 48px;
&:hover {
color: $gl-text-color;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
}
&:focus {
color: $gl-text-color;
- background-color: $theme-gray-200;
+ background-color: $gray-200;
}
&.active {
@@ -1273,10 +1273,10 @@ $ide-commit-header-height: 48px;
.ide-entry-dropdown-toggle {
padding: $gl-padding-4;
color: $gl-text-color;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
&:hover {
- background-color: $theme-gray-200;
+ background-color: $gray-200;
}
&:active,
@@ -1331,7 +1331,7 @@ $ide-commit-header-height: 48px;
&:focus {
outline: 0;
box-shadow: none;
- border-color: $theme-gray-200;
+ border-color: $gray-200;
}
}
@@ -1358,7 +1358,7 @@ $ide-commit-header-height: 48px;
.ide-commit-editor-header {
height: 65px;
padding: 8px 16px;
- background-color: $theme-gray-50;
+ background-color: $gray-50;
box-shadow: inset 0 -1px $white-dark;
}
@@ -1370,7 +1370,7 @@ $ide-commit-header-height: 48px;
.ide-file-icon-holder {
display: flex;
align-items: center;
- color: $theme-gray-700;
+ color: $gray-700;
}
.file-row:hover,
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 37984a8666f..bc28ffb3a92 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -26,13 +26,15 @@
opacity: 0.3;
}
+.dropdown-projects {
+ .dropdown-content {
+ max-height: 200px;
+ }
+}
+
.dropdown-menu-issues-board-new {
width: 320px;
- .open & {
- max-height: 400px;
- }
-
.dropdown-content {
max-height: 162px;
}
@@ -171,6 +173,7 @@
background: $gray-light;
border: 1px solid $border-color;
border-radius: $border-radius-default;
+ flex: 1;
}
.board-header {
@@ -232,9 +235,11 @@
}
.board-blank-state {
- height: calc(100% - 49px);
padding: $gl-padding;
background-color: $white-light;
+ flex: 1;
+ overflow-y: auto;
+ overflow-x: hidden;
}
.board-blank-state-list {
@@ -256,9 +261,9 @@
}
.board-list-component {
- height: calc(100% - 49px);
- overflow: hidden;
position: relative;
+ flex: 1;
+ min-height: 0; // firefox fix
}
.board-list {
@@ -281,7 +286,7 @@
padding: $gl-padding;
background: $white-light;
border-radius: $border-radius-default;
- border: 1px solid $theme-gray-200;
+ border: 1px solid $gray-200;
box-shadow: 0 1px 2px $issue-boards-card-shadow;
list-style: none;
line-height: $gl-padding;
@@ -673,7 +678,7 @@
}
.board-card-info-icon {
- color: $theme-gray-600;
+ color: $gray-600;
margin-right: $gl-padding-4;
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 09235661cea..65f46e3852a 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -55,16 +55,16 @@
@include build-trace();
}
- .archived-sticky {
+ .archived-job {
top: $header-height;
border-radius: 2px 2px 0 0;
color: $orange-600;
background-color: $orange-100;
border: 1px solid $border-gray-normal;
- border-bottom: 0;
padding: 3px 12px;
margin: auto;
align-items: center;
+ z-index: 1;
.with-performance-bar & {
top: $header-height + $performance-bar-height;
@@ -261,7 +261,7 @@
.trigger-variables-table-cell {
font-size: $gl-font-size-small;
line-height: $gl-line-height;
- border: 1px solid $theme-gray-200;
+ border: 1px solid $gray-200;
padding: $gl-padding-4 6px;
width: 50%;
vertical-align: top;
@@ -272,7 +272,13 @@
}
.retry-link {
- display: none;
+ display: block;
+
+ .btn {
+ i {
+ margin-left: 5px;
+ }
+ }
.btn-inverted-secondary {
color: $blue-500;
@@ -281,16 +287,6 @@
color: $white-light;
}
}
-
- @include media-breakpoint-down(sm) {
- display: block;
-
- .btn {
- i {
- margin-left: 5px;
- }
- }
- }
}
.stage-item {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 18c62cb4f1e..02aac58a475 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -9,7 +9,7 @@
@media (min-width: map-get($grid-breakpoints, md)) {
position: -webkit-sticky;
position: sticky;
- top: 92px;
+ top: $mr-version-controls-height + $header-height + $mr-tabs-height;
margin-left: -1px;
border-left: 1px solid $border-color;
z-index: 102;
@@ -19,6 +19,7 @@
.with-performance-bar & {
top: $header-height + 36px + $performance-bar-height;
+
}
}
@@ -34,7 +35,7 @@
}
.with-performance-bar & {
- top: 127px;
+ top: $header-height + $performance-bar-height + $mr-version-controls-height + $mr-tabs-height;
}
}
@@ -1026,8 +1027,9 @@
.tree-list-holder {
position: -webkit-sticky;
position: sticky;
- top: 100px;
- max-height: calc(100vh - 100px);
+ $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
+ top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
+ max-height: calc(100vh - #{$top-pos});
padding-right: $gl-padding;
.file-row {
@@ -1036,8 +1038,9 @@
}
.with-performance-bar & {
- top: 135px;
- max-height: calc(100vh - 135px);
+ $performance-bar-top-pos: $performance-bar-height + $top-pos;
+ top: $performance-bar-top-pos;
+ max-height: calc(100vh - #{$performance-bar-top-pos});
}
}
@@ -1092,12 +1095,6 @@
}
}
-.tree-list-view-toggle {
- svg {
- top: 0;
- }
-}
-
.image-diff-overlay,
.image-diff-overlay-add-comment {
top: 0;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index f46ff360496..5a988b184b6 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -128,6 +128,10 @@
width: 100%;
}
}
+
+ @media(max-width: map-get($grid-breakpoints, md)-1) {
+ clear: both;
+ }
}
.editor-ref {
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 75166ffcada..b6abb792709 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -267,7 +267,7 @@
.prometheus-graph-cursor {
position: absolute;
- background: $theme-gray-600;
+ background: $gray-600;
width: 1px;
}
@@ -309,7 +309,7 @@
> .arrow::after {
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
- border-left: 4px solid $theme-gray-50;
+ border-left: 4px solid $gray-50;
}
.arrow-shadow {
@@ -331,7 +331,7 @@
> .arrow::after {
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
- border-right: 4px solid $theme-gray-50;
+ border-right: 4px solid $gray-50;
}
.arrow-shadow {
@@ -364,7 +364,7 @@
}
> .popover-title {
- background-color: $theme-gray-50;
+ background-color: $gray-50;
border-radius: $border-radius-default $border-radius-default 0 0;
}
}
@@ -439,7 +439,7 @@
}
> text {
- fill: $theme-gray-600;
+ fill: $gray-600;
font-size: 10px;
}
}
@@ -482,5 +482,5 @@
}
.prometheus-table-row-highlight {
- background-color: $theme-gray-100;
+ background-color: $gray-100;
}
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 4fb1a956fab..83b1680512d 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -44,7 +44,7 @@
}
.x-axis-text {
- fill: $theme-gray-900;
+ fill: $gray-900;
}
.bar-rect {
@@ -64,7 +64,7 @@
text {
font-weight: bold;
font-size: 12px;
- fill: $theme-gray-800;
+ fill: $gray-800;
}
}
}
@@ -87,5 +87,5 @@
.animate-flicker {
animation: flickerAnimation 1.5s infinite;
- fill: $theme-gray-500;
+ fill: $gray-500;
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index f0cb81e0bc3..ebbb5beed81 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -78,7 +78,7 @@
&:hover {
background-color: $gray-darker;
- color: $theme-gray-900;
+ color: $gray-900;
}
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index a1069aa9783..e0bdc1341b1 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -934,7 +934,7 @@
.sidebar-collapsed-divider {
line-height: 5px;
font-size: 12px;
- color: $theme-gray-700;
+ color: $gray-700;
+ .sidebar-collapsed-icon {
padding-top: 0;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index bb6b6f84849..0037364978c 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -80,7 +80,6 @@ ul.related-merge-requests > li {
}
}
-.merge-requests-title,
.related-branches-title {
font-size: 16px;
font-weight: $gl-font-weight-bold;
@@ -91,23 +90,16 @@ ul.related-merge-requests > li {
}
.merge-request-status {
- font-size: 13px;
- padding: 0 5px;
- color: $white-light;
- height: 20px;
- border-radius: 3px;
- line-height: 18px;
-
&.merged {
- background: $blue-500;
+ color: $blue-500;
}
&.closed {
- background: $red-500;
+ color: $red-500;
}
&.open {
- background: $green-500;
+ color: $green-500;
}
}
@@ -155,6 +147,14 @@ ul.related-merge-requests > li {
}
}
+.issues-nav-controls {
+ font-size: 0;
+
+ .btn-group:empty {
+ display: none;
+ }
+}
+
.issuable-email-modal-btn {
padding: 0;
color: $blue-600;
diff --git a/app/assets/stylesheets/pages/issues/issue_count_badge.scss b/app/assets/stylesheets/pages/issues/issue_count_badge.scss
index 4fba89e956b..64ca61f7094 100644
--- a/app/assets/stylesheets/pages/issues/issue_count_badge.scss
+++ b/app/assets/stylesheets/pages/issues/issue_count_badge.scss
@@ -1,11 +1,13 @@
-.issue-count-badge {
+.issue-count-badge,
+.mr-count-badge {
display: inline-flex;
border-radius: $border-radius-base;
border: 1px solid $border-color;
padding: 5px $gl-padding-8;
}
-.issue-count-badge-count {
+.issue-count-badge-count,
+.mr-count-badge-count {
display: inline-flex;
align-items: center;
}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index d2b9470be69..2372640277e 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -82,7 +82,7 @@
justify-content: space-between;
padding: $gl-padding;
border-radius: $border-radius-default;
- border: 1px solid $theme-gray-100;
+ border: 1px solid $gray-100;
&:last-child {
margin-bottom: 0;
@@ -257,7 +257,7 @@
}
.label-badge {
- color: $theme-gray-900;
+ color: $gray-900;
font-weight: $gl-font-weight-normal;
padding: $gl-padding-4 $gl-padding-8;
border-radius: $border-radius-default;
@@ -269,7 +269,7 @@
}
.label-badge-gray {
- background-color: $theme-gray-100;
+ background-color: $gray-100;
}
.label-links {
@@ -326,11 +326,11 @@
}
.label-action {
- color: $theme-gray-800;
+ color: $gray-800;
cursor: pointer;
svg {
- fill: $theme-gray-800;
+ fill: $gray-800;
}
&:hover {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 221b4e934ff..53afb182b54 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -64,7 +64,7 @@
&::before {
content: '';
- border-left: 1px solid $theme-gray-200;
+ border-left: 1px solid $gray-200;
position: absolute;
left: 32px;
top: -17px;
@@ -708,6 +708,7 @@
.mr-version-controls {
position: relative;
+ z-index: 103;
background: $gray-light;
color: $gl-text-color;
@@ -755,13 +756,37 @@
color: $orange-500;
padding-right: 5px;
}
+
+ @include media-breakpoint-up(md) {
+ position: -webkit-sticky;
+ position: sticky;
+ top: $header-height + $mr-tabs-height;
+ width: 100%;
+ border-top: 1px solid $border-color;
+
+ &.is-fileTreeOpen {
+ margin-left: -16px;
+ width: calc(100% + 32px);
+ }
+
+ .mr-version-menus-container {
+ flex-wrap: nowrap;
+ }
+
+ .with-performance-bar & {
+ top: $header-height + $performance-bar-height + $mr-tabs-height;
+ }
+ }
}
.merge-request-tabs-holder {
top: $header-height;
z-index: 200;
background-color: $white-light;
- border-bottom: 1px solid $border-color;
+
+ @include media-breakpoint-down(md) {
+ border-bottom: 1px solid $border-color;
+ }
@include media-breakpoint-up(sm) {
position: sticky;
@@ -816,7 +841,7 @@
display: flex;
justify-content: space-between;
- @include media-breakpoint-down(md) {
+ @include media-breakpoint-down(sm) {
flex-direction: column-reverse;
}
@@ -907,7 +932,7 @@
}
.btn svg {
- fill: $theme-gray-700;
+ fill: $gray-700;
}
.dropdown-menu {
@@ -951,7 +976,7 @@
.coverage {
font-size: 12px;
- color: $theme-gray-700;
+ color: $gray-700;
line-height: initial;
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 94bf32945fc..15f3a2ef4a8 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -8,7 +8,7 @@ $status-box-line-height: 26px;
padding: $gl-padding-8;
margin-top: $gl-padding-8;
border-radius: $border-radius-default;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
.milestone {
border: 0;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 5b30295adf9..51f755c67af 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -1,10 +1,6 @@
/**
* Note Form
*/
-.comment-btn {
- @extend .btn-success;
-}
-
.diff-file .diff-content {
tr.line_holder:hover > td .line_note_link {
opacity: 1;
@@ -151,7 +147,7 @@
}
.sidebar-item-value & {
- fill: $theme-gray-700;
+ fill: $gray-700;
}
}
@@ -386,7 +382,7 @@ table {
}
.comment-type-dropdown {
- .comment-btn {
+ .btn-success {
width: auto;
}
@@ -417,7 +413,7 @@ table {
width: 100%;
margin-bottom: 10px;
- .comment-btn {
+ .btn-success {
flex-grow: 1;
flex-shrink: 0;
width: auto;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index a5b1eff3e1d..23b9e4f9416 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -5,7 +5,7 @@ $note-form-margin-left: 72px;
@mixin vertical-line($left) {
&::before {
content: '';
- border-left: 2px solid $theme-gray-100;
+ border-left: 2px solid $gray-100;
position: absolute;
top: 0;
bottom: 0;
@@ -153,12 +153,12 @@ $note-form-margin-left: 72px;
position: relative;
.timeline-discussion-body {
- margin-top: -8px;
+ margin-top: -$gl-padding-8;
overflow-x: auto;
overflow-y: hidden;
- .discussion-resolved-text {
- margin-bottom: 8px;
+ .note-body {
+ margin-top: $gl-padding-8;
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 7a47e0a2836..a28921592ec 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -256,14 +256,25 @@
}
}
- .mini-pipeline-graph-dropdown-toggle svg {
- height: $ci-action-icon-size;
- width: $ci-action-icon-size;
- position: absolute;
- top: -1px;
- left: -1px;
- z-index: 2;
- overflow: visible;
+ .mini-pipeline-graph-dropdown-toggle {
+ svg {
+ height: $ci-action-icon-size;
+ width: $ci-action-icon-size;
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ z-index: 2;
+ overflow: visible;
+ }
+
+ &:hover,
+ &:active,
+ &:focus {
+ svg {
+ top: -2px;
+ left: -2px;
+ }
+ }
}
.stage-container {
@@ -293,7 +304,7 @@
width: 7px;
position: absolute;
right: -7px;
- top: 10px;
+ top: 11px;
border-bottom: 2px solid $border-color;
}
}
@@ -433,6 +444,7 @@
}
.pipeline-tab-content {
+ display: flex;
width: 100%;
background-color: $gray-light;
padding: $gl-padding;
@@ -707,21 +719,43 @@
font-weight: $gl-font-weight-normal;
}
-@mixin mini-pipeline-graph-color($color-light, $color-main, $color-dark) {
- border-color: $color-main;
- color: $color-main;
+@mixin mini-pipeline-graph-color(
+ $color-background-default,
+ $color-background-hover-focus,
+ $color-background-active,
+ $color-foreground-default,
+ $color-foreground-hover-focus,
+ $color-foreground-active
+) {
+ background-color: $color-background-default;
+ border-color: $color-foreground-default;
+
+ svg {
+ fill: $color-foreground-default;
+ }
&:hover,
- &:focus,
+ &:focus {
+ background-color: $color-background-hover-focus;
+ border-color: $color-foreground-hover-focus;
+
+ svg {
+ fill: $color-foreground-hover-focus;
+ }
+ }
+
&:active {
- background-color: $color-light;
- border-color: $color-dark;
- color: $color-dark;
+ background-color: $color-background-active;
+ border-color: $color-foreground-active;
svg {
- fill: $color-dark;
+ fill: $color-foreground-active;
}
}
+
+ &:focus {
+ box-shadow: 0 0 4px 1px $blue-300;
+ }
}
@mixin mini-pipeline-item() {
@@ -733,26 +767,32 @@
height: $ci-action-icon-size;
margin: 0;
padding: 0;
- transition: all 0.2s linear;
position: relative;
vertical-align: middle;
+ &:hover,
+ &:active,
+ &:focus {
+ outline: none;
+ border-width: 2px;
+ }
+
// Dropdown button animation in mini pipeline graph
&.ci-status-icon-success {
- @include mini-pipeline-graph-color($green-100, $green-500, $green-600);
+ @include mini-pipeline-graph-color($white, $green-100, $green-200, $green-500, $green-600, $green-700);
}
&.ci-status-icon-failed {
- @include mini-pipeline-graph-color($red-100, $red-500, $red-600);
+ @include mini-pipeline-graph-color($white, $red-100, $red-200, $red-500, $red-600, $red-700);
}
&.ci-status-icon-pending,
&.ci-status-icon-success_with_warnings {
- @include mini-pipeline-graph-color($orange-100, $orange-500, $orange-600);
+ @include mini-pipeline-graph-color($white, $orange-100, $orange-200, $orange-500, $orange-600, $orange-700);
}
&.ci-status-icon-running {
- @include mini-pipeline-graph-color($blue-100, $blue-400, $blue-600);
+ @include mini-pipeline-graph-color($white, $blue-100, $blue-200, $blue-500, $blue-600, $blue-700);
}
&.ci-status-icon-canceled,
@@ -760,42 +800,18 @@
&.ci-status-icon-disabled,
&.ci-status-icon-not-found,
&.ci-status-icon-manual {
- @include mini-pipeline-graph-color(rgba($gl-text-color, 0.1), $gl-text-color, $gl-text-color);
+ @include mini-pipeline-graph-color($white, $gray-700, $gray-800, $gray-900, $gray-950, $black);
}
&.ci-status-icon-created,
&.ci-status-icon-skipped {
- @include mini-pipeline-graph-color(rgba($gray-darkest, 0.1), $gray-darkest, $gray-darkest);
+ @include mini-pipeline-graph-color($white, $gray-200, $gray-300, $gray-500, $gray-600, $gray-700);
}
}
// Dropdown button in mini pipeline graph
button.mini-pipeline-graph-dropdown-toggle {
@include mini-pipeline-item();
-
- > .fa.fa-caret-down {
- position: absolute;
- left: 20px;
- top: 5px;
- display: inline-block;
- visibility: hidden;
- opacity: 0;
- color: inherit;
- font-size: 12px;
- transition: visibility 0.1s, opacity 0.1s linear;
- }
-
- &:active,
- &:focus,
- &:hover {
- outline: none;
- width: 35px;
-
- .fa.fa-caret-down {
- visibility: visible;
- opacity: 1;
- }
- }
}
/**
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index b813eb16dad..a1e847009fc 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -249,6 +249,16 @@
.event-item {
padding-left: 40px;
}
+
+ @include media-breakpoint-up(lg) {
+ margin-right: 5px;
+ }
+ }
+
+ .projects-block {
+ @include media-breakpoint-up(lg) {
+ margin-left: 5px;
+ }
}
@include media-breakpoint-down(xs) {
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
index a353f301d07..45e62913f37 100644
--- a/app/assets/stylesheets/pages/profiles/preferences.scss
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -60,11 +60,11 @@
}
&.ui-dark {
- background-color: $theme-gray-900;
+ background-color: $gray-900;
}
&.ui-light {
- background-color: $theme-gray-200;
+ background-color: $gray-200;
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 004c49dd226..505f6e036e3 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -219,7 +219,7 @@
color: $gl-text-color-secondary;
}
- .project-tag-list {
+ .project-topic-list {
font-size: $gl-font-size;
font-weight: $gl-font-weight-normal;
@@ -251,7 +251,7 @@
line-height: $gl-font-size-large;
}
- .project-tag-list,
+ .project-topic-list,
.project-metadata {
font-size: $gl-font-size-small;
}
@@ -273,7 +273,7 @@
}
.access-request-link,
- .project-tag-list {
+ .project-topic-list {
padding-left: $gl-padding-8;
border-left: 1px solid $gl-text-color-secondary;
}
@@ -459,7 +459,7 @@
margin-right: $gl-padding-4;
margin-bottom: $gl-padding-4;
color: $gl-text-color-secondary;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
line-height: $gl-btn-line-height;
&:hover {
@@ -914,7 +914,7 @@
}
.repository-language-bar-tooltip-share {
- color: $theme-gray-400;
+ color: $gray-400;
}
pre.light-well {
@@ -1025,8 +1025,10 @@ pre.light-well {
margin: 0;
}
- @include media-breakpoint-up(md) {
- .description {
+ .description {
+ line-height: 1.5;
+
+ @include media-breakpoint-up(md) {
color: $gl-text-color;
}
}
diff --git a/app/assets/stylesheets/pages/reports.scss b/app/assets/stylesheets/pages/reports.scss
index ecd51aa06a4..f7619ccbd20 100644
--- a/app/assets/stylesheets/pages/reports.scss
+++ b/app/assets/stylesheets/pages/reports.scss
@@ -96,7 +96,7 @@
}
&.neutral svg {
- color: $theme-gray-700;
+ color: $gray-700;
}
.ci-status-icon {
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index ccfa4e00a5b..c5b9d1f6885 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -297,7 +297,7 @@
.btn-clipboard {
background-color: $white-light;
- border: 1px solid $theme-gray-200;
+ border: 1px solid $gray-200;
}
.deploy-token-help-block {
diff --git a/app/controllers/admin/requests_profiles_controller.rb b/app/controllers/admin/requests_profiles_controller.rb
index 57f7d3e3951..89d4c4f18d9 100644
--- a/app/controllers/admin/requests_profiles_controller.rb
+++ b/app/controllers/admin/requests_profiles_controller.rb
@@ -11,7 +11,7 @@ class Admin::RequestsProfilesController < Admin::ApplicationController
profile = Gitlab::RequestProfiler::Profile.find(clean_name)
if profile
- render html: profile.content
+ render html: profile.content.html_safe
else
redirect_to admin_requests_profiles_path, alert: 'Profile not found'
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a8fc848c879..26cd5dc801f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -177,11 +177,17 @@ class ApplicationController < ActionController::Base
# hide existence of the resource, rather tell them they cannot access it using
# the provided message
status ||= message.present? ? :forbidden : :not_found
+ template =
+ if status == :not_found
+ "errors/not_found"
+ else
+ "errors/access_denied"
+ end
respond_to do |format|
format.any { head status }
format.html do
- render "errors/access_denied",
+ render template,
layout: "errors",
status: status,
locals: { message: message }
diff --git a/app/controllers/concerns/boards_responses.rb b/app/controllers/concerns/boards_responses.rb
index 3cdf4ddf8bb..8b191c86397 100644
--- a/app/controllers/concerns/boards_responses.rb
+++ b/app/controllers/concerns/boards_responses.rb
@@ -34,15 +34,11 @@ module BoardsResponses
end
def authorize_read_list
- ability = board.group_board? ? :read_group : :read_list
-
- authorize_action_for!(board.parent, ability)
+ authorize_action_for!(board, :read_list)
end
def authorize_read_issue
- ability = board.group_board? ? :read_group : :read_issue
-
- authorize_action_for!(board.parent, ability)
+ authorize_action_for!(board, :read_issue)
end
def authorize_update_issue
@@ -57,7 +53,7 @@ module BoardsResponses
end
def authorize_admin_list
- authorize_action_for!(board.parent, :admin_list)
+ authorize_action_for!(board, :admin_list)
end
def authorize_action_for!(resource, ability)
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 0eea0cdd50f..4ec0e94df9a 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -7,12 +7,12 @@ module UploadsActions
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
def create
- link_to_file = UploadService.new(model, params[:file], uploader_class).execute
+ uploader = UploadService.new(model, params[:file], uploader_class).execute
respond_to do |format|
- if link_to_file
+ if uploader
format.json do
- render json: { link: link_to_file }
+ render json: { link: uploader.to_h }
end
else
format.json do
@@ -29,7 +29,13 @@ module UploadsActions
def show
return render_404 unless uploader&.exists?
- expires_in 0.seconds, must_revalidate: true, private: true
+ if cache_publicly?
+ # We need to reset caching from the applications controller to get rid of the no-store value
+ headers['Cache-Control'] = ''
+ expires_in 5.minutes, public: true, must_revalidate: false
+ else
+ expires_in 0.seconds, must_revalidate: true, private: true
+ end
disposition = uploader.image_or_video? ? 'inline' : 'attachment'
@@ -114,6 +120,10 @@ module UploadsActions
nil
end
+ def cache_publicly?
+ false
+ end
+
def model
strong_memoize(:model) { find_model }
end
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
index 3802aa5f40f..9484e4d30cd 100644
--- a/app/controllers/dashboard/milestones_controller.rb
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -27,7 +27,7 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
def group_milestones
groups = GroupsFinder.new(current_user, all_available: false).execute
- DashboardGroupMilestone.build_collection(groups)
+ DashboardGroupMilestone.build_collection(groups, params)
end
# See [#39545](https://gitlab.com/gitlab-org/gitlab-ce/issues/39545) for info about the deprecation of dynamic milestones
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index f073b6de444..b1d224d026f 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -53,6 +53,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def load_projects(finder_params)
+ @total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute
+ @total_starred_projects_count = ProjectsFinder.new(params: { starred: true }, current_user: current_user).execute
+
projects = ProjectsFinder
.new(params: finder_params, current_user: current_user)
.execute
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 778fdda8dbd..9f074690cbc 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -55,6 +55,9 @@ class Explore::ProjectsController < Explore::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def load_projects
+ @total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute
+ @total_starred_projects_count = ProjectsFinder.new(params: { starred: true }, current_user: current_user).execute
+
projects = ProjectsFinder.new(current_user: current_user, params: params)
.execute
.includes(:route, :creator, :group, namespace: [:route, :owner])
diff --git a/app/controllers/groups/children_controller.rb b/app/controllers/groups/children_controller.rb
index d549f793ad7..236a19a8dc4 100644
--- a/app/controllers/groups/children_controller.rb
+++ b/app/controllers/groups/children_controller.rb
@@ -35,7 +35,7 @@ module Groups
def setup_children(parent)
@children = GroupDescendantsFinder.new(current_user: current_user,
parent_group: parent,
- params: params).execute
+ params: params.to_unsafe_h).execute
@children = @children.page(params[:page])
end
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 868deea3f01..7ed4384089b 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -115,6 +115,6 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def search_params
- params.permit(:state).merge(group_ids: group.id)
+ params.permit(:state, :search_title).merge(group_ids: group.id)
end
end
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
index 042b6b1264f..9b45be6db99 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -18,6 +18,7 @@ class Import::BaseController < ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
+ # deprecated: being replaced by app/services/import/base_service.rb
def find_or_create_namespace(names, owner)
names = params[:target_namespace].presence || names
@@ -32,6 +33,7 @@ class Import::BaseController < ApplicationController
current_user.namespace
end
+ # deprecated: being replaced by app/services/import/base_service.rb
def project_save_error(project)
project.errors.full_messages.join(', ')
end
diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb
index 575c40d5f6f..87338488eba 100644
--- a/app/controllers/import/bitbucket_server_controller.rb
+++ b/app/controllers/import/bitbucket_server_controller.rb
@@ -40,7 +40,7 @@ class Import::BitbucketServerController < Import::BaseController
else
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
end
- rescue BitbucketServer::Client::ServerError => e
+ rescue BitbucketServer::Connection::ConnectionError => e
render json: { errors: "Unable to connect to server: #{e}" }, status: :unprocessable_entity
end
@@ -62,7 +62,7 @@ class Import::BitbucketServerController < Import::BaseController
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
- rescue BitbucketServer::Connection::ConnectionError, BitbucketServer::Client::ServerError => e
+ rescue BitbucketServer::Connection::ConnectionError => e
flash[:alert] = "Unable to connect to server: #{e}"
clear_session_data
redirect_to new_import_bitbucket_server_path
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index d4c26fa0709..34c7dbdc2fe 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -39,28 +39,25 @@ class Import::GithubController < Import::BaseController
end
def create
- repo = client.repo(params[:repo_id].to_i)
- project_name = params[:new_name].presence || repo.name
- namespace_path = params[:target_namespace].presence || current_user.namespace_path
- target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
-
- if can?(current_user, :create_projects, target_namespace)
- project = Gitlab::LegacyGithubImport::ProjectCreator
- .new(repo, project_name, target_namespace, current_user, access_params, type: provider)
- .execute(extra_project_attrs)
-
- if project.persisted?
- render json: ProjectSerializer.new.represent(project)
- else
- render json: { errors: project_save_error(project) }, status: :unprocessable_entity
- end
+ result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider)
+
+ if result[:status] == :success
+ render json: ProjectSerializer.new.represent(result[:project])
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: result[:message] }, status: result[:http_status]
end
end
private
+ def import_params
+ params.permit(permitted_import_params)
+ end
+
+ def permitted_import_params
+ [:repo_id, :new_name, :target_namespace]
+ end
+
def client
@client ||= Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
end
@@ -124,10 +121,6 @@ class Import::GithubController < Import::BaseController
{}
end
- def extra_project_attrs
- {}
- end
-
def extra_import_params
{}
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 30be50d4595..f8e482937d5 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -75,6 +75,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
private
def omniauth_flow(auth_module, identity_linker: nil)
+ if fragment = request.env.dig('omniauth.params', 'redirect_fragment').presence
+ store_redirect_fragment(fragment)
+ end
+
if current_user
log_audit_event(current_user, with: oauth['provider'])
@@ -189,4 +193,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
request_params = request.env['omniauth.params']
(request_params['remember_me'] == '1') if request_params.present?
end
+
+ def store_redirect_fragment(redirect_fragment)
+ key = stored_location_key_for(:user)
+ location = session[key]
+ if uri = parse_uri(location)
+ uri.fragment = redirect_fragment
+ store_location_for(:user, uri.to_s)
+ end
+ end
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 2912a22411e..28f113b5cbe 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -5,7 +5,7 @@ class PasswordsController < Devise::PasswordsController
before_action :resource_from_email, only: [:create]
before_action :check_password_authentication_available, only: [:create]
- before_action :throttle_reset, only: [:create]
+ before_action :throttle_reset, only: [:create]
# rubocop: disable CodeReuse/ActiveRecord
def edit
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 1a91e07b97f..2ef18d900f2 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -85,20 +85,15 @@ class Projects::ArtifactsController < Projects::ApplicationController
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def build_from_id
- project.builds.find_by(id: params[:job_id]) if params[:job_id]
+ project.builds.find_by_id(params[:job_id]) if params[:job_id]
end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def build_from_ref
return unless @ref_name
- builds = project.latest_successful_builds_for(@ref_name)
- builds.find_by(name: params[:job])
+ project.latest_successful_build_for(params[:job], @ref_name)
end
- # rubocop: enable CodeReuse/ActiveRecord
def artifacts_file
@artifacts_file ||= build&.artifacts_file_for_type(params[:file_type] || :archive)
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index c24bf211760..09a384e89ab 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -21,11 +21,22 @@ class Projects::BadgesController < Projects::ApplicationController
private
+ def badge_layout
+ case params[:style]
+ when 'flat'
+ 'badge'
+ when 'flat-square'
+ 'badge_flat-square'
+ else
+ 'badge'
+ end
+ end
+
def render_badge(badge)
respond_to do |format|
format.html { render_404 }
format.svg do
- render 'badge', locals: { badge: badge.template }
+ render badge_layout, locals: { badge: badge.template }
end
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index ff286c0ccf0..77672e7d9fc 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -93,7 +93,7 @@ class Projects::BlobController < Projects::ApplicationController
@blob.load_all_data!
@lines = @blob.present.highlight.lines
- @form = UnfoldForm.new(params)
+ @form = UnfoldForm.new(params.to_unsafe_h)
@lines = @lines[@form.since - 1..@form.to - 1].map(&:html_safe)
diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
index 7d4d566499c..4274c356227 100644
--- a/app/controllers/projects/build_artifacts_controller.rb
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -44,18 +44,13 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
@job ||= job_from_id || job_from_ref
end
- # rubocop: disable CodeReuse/ActiveRecord
def job_from_id
- project.builds.find_by(id: params[:build_id]) if params[:build_id]
+ project.builds.find_by_id(params[:build_id]) if params[:build_id]
end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def job_from_ref
return unless @ref_name
- jobs = project.latest_successful_builds_for(@ref_name)
- jobs.find_by(name: params[:job])
+ project.latest_successful_build_for(params[:job], @ref_name)
end
- # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/projects/ci/lints_controller.rb b/app/controllers/projects/ci/lints_controller.rb
index 2090af0a111..d7a0b7ece14 100644
--- a/app/controllers/projects/ci/lints_controller.rb
+++ b/app/controllers/projects/ci/lints_controller.rb
@@ -8,7 +8,7 @@ class Projects::Ci::LintsController < Projects::ApplicationController
def create
@content = params[:content]
- @error = Gitlab::Ci::YamlProcessor.validation_message(@content, yaml_processor_options)
+ @error = Gitlab::Ci::YamlProcessor.validation_message(@content, yaml_processor_options)
@status = @error.blank?
if @error.blank?
@@ -24,6 +24,10 @@ class Projects::Ci::LintsController < Projects::ApplicationController
private
def yaml_processor_options
- { project: @project, sha: project.repository.commit.sha }
+ {
+ project: @project,
+ user: current_user,
+ sha: project.repository.commit.sha
+ }
end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 32fc5140366..b13c0ae3967 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -24,10 +24,10 @@ class Projects::CommitController < Projects::ApplicationController
apply_diff_view_cookie!
respond_to do |format|
- format.html do
+ format.html do
render
end
- format.diff do
+ format.diff do
send_git_diff(@project.repository, @commit.diff_refs)
end
format.patch do
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index b62606067c0..028390c7e2a 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -40,7 +40,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
def render_json_with_discussions_serializer
render json:
- DiscussionSerializer.new(project: project, noteable: discussion.noteable, current_user: current_user, note_entity: ProjectNoteEntity)
+ DiscussionSerializer.new(project: project, noteable: discussion.noteable, current_user: current_user, note_entity: ProjectNoteEntity)
.represent(discussion, context: self, render_truncated_diff_lines: true)
end
diff --git a/app/controllers/projects/error_tracking_controller.rb b/app/controllers/projects/error_tracking_controller.rb
new file mode 100644
index 00000000000..9e403e1d25b
--- /dev/null
+++ b/app/controllers/projects/error_tracking_controller.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+class Projects::ErrorTrackingController < Projects::ApplicationController
+ before_action :authorize_read_sentry_issue!
+
+ POLLING_INTERVAL = 10_000
+
+ def index
+ respond_to do |format|
+ format.html
+ format.json do
+ set_polling_interval
+ render_index_json
+ end
+ end
+ end
+
+ private
+
+ def render_index_json
+ service = ErrorTracking::ListIssuesService.new(project, current_user)
+ result = service.execute
+
+ unless result[:status] == :success
+ return render json: { message: result[:message] },
+ status: result[:http_status] || :bad_request
+ end
+
+ render json: {
+ errors: serialize_errors(result[:issues]),
+ external_url: service.external_url
+ }
+ end
+
+ def set_polling_interval
+ Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
+ end
+
+ def serialize_errors(errors)
+ ErrorTracking::ErrorSerializer
+ .new(project: project, user: current_user)
+ .represent(errors)
+ end
+end
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index c0aa39d87c6..30e436365de 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -80,9 +80,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end
def access_check
- # Use the magic string '_any' to indicate we do not know what the
- # changes are. This is also what gitlab-shell does.
- access.check(git_command, '_any')
+ access.check(git_command, Gitlab::GitAccess::ANY)
@project ||= access.project
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 5ed46fc0545..e3e60665506 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -10,7 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController
include SpammableActions
def self.issue_except_actions
- %i[index calendar new create bulk_update]
+ %i[index calendar new create bulk_update import_csv]
end
def self.set_issuables_index_only_actions
@@ -37,6 +37,8 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
+ before_action :authorize_import_issues!, only: [:import_csv]
+
before_action :set_suggested_issues_feature_flags, only: [:new]
respond_to :html
@@ -175,6 +177,18 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
+ def import_csv
+ if uploader = UploadService.new(project, params[:file]).execute
+ ImportIssuesCsvWorker.perform_async(current_user.id, project.id, uploader.upload.id)
+
+ flash[:notice] = _("Your issues are being imported. Once finished, you'll get a confirmation email.")
+ else
+ flash[:alert] = _("File upload error.")
+ end
+
+ redirect_to project_issues_path(project)
+ end
+
protected
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index bfbbcba883f..d5ce790e2d9 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -4,10 +4,10 @@ class Projects::JobsController < Projects::ApplicationController
include SendFileUpload
include ContinueParams
- before_action :build, except: [:index, :cancel_all]
+ before_action :build, except: [:index]
before_action :authorize_read_build!
before_action :authorize_update_build!,
- except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase]
+ except: [:index, :show, :status, :raw, :trace, :erase]
before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
@@ -39,16 +39,6 @@ class Projects::JobsController < Projects::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
- def cancel_all
- return access_denied! unless can?(current_user, :update_build, project)
-
- @project.builds.running_or_pending.each do |build|
- build.cancel if can?(current_user, :update_build, build)
- end
-
- redirect_to project_jobs_path(project)
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def show
@pipeline = @build.pipeline
diff --git a/app/controllers/projects/lfs_locks_api_controller.rb b/app/controllers/projects/lfs_locks_api_controller.rb
index fc67cd72faa..6aacb9d9a56 100644
--- a/app/controllers/projects/lfs_locks_api_controller.rb
+++ b/app/controllers/projects/lfs_locks_api_controller.rb
@@ -4,19 +4,19 @@ class Projects::LfsLocksApiController < Projects::GitHttpClientController
include LfsRequest
def create
- @result = Lfs::LockFileService.new(project, user, params).execute
+ @result = Lfs::LockFileService.new(project, user, lfs_params).execute
render_json(@result[:lock])
end
def unlock
- @result = Lfs::UnlockFileService.new(project, user, params).execute
+ @result = Lfs::UnlockFileService.new(project, user, lfs_params).execute
render_json(@result[:lock])
end
def index
- @result = Lfs::LocksFinderService.new(project, user, params).execute
+ @result = Lfs::LocksFinderService.new(project, user, lfs_params).execute
render_json(@result[:locks])
end
@@ -69,4 +69,8 @@ class Projects::LfsLocksApiController < Projects::GitHttpClientController
def upload_request?
%w(create unlock verify).include?(params[:action])
end
+
+ def lfs_params
+ params.permit(:id, :path, :force)
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 162c2636641..accc37557b0 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -55,7 +55,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
render json: serializer.represent(@merge_request, serializer: params[:serializer])
end
- format.patch do
+ format.patch do
break render_404 unless @merge_request.diff_refs
send_git_patch @project.repository, @merge_request.diff_refs
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 8e68014a30d..f6f61b6e5fb 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -144,9 +144,9 @@ class Projects::MilestonesController < Projects::ApplicationController
def search_params
if request.format.json? && project_group && can?(current_user, :read_group, project_group)
- groups = project_group.self_and_ancestors_ids
+ groups = project_group.self_and_ancestors.select(:id)
end
- params.permit(:state).merge(project_ids: @project.id, group_ids: groups)
+ params.permit(:state, :search_title).merge(project_ids: @project.id, group_ids: groups)
end
end
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index c1ad6707c97..d0e35bee986 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -18,7 +18,7 @@ class Projects::PagesController < Projects::ApplicationController
project.pages_domains.destroy_all # rubocop: disable DestroyAll
respond_to do |format|
- format.html do
+ format.html do
redirect_to project_pages_path(@project),
status: 302,
notice: 'Pages were removed'
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index 192e6d38f36..7f988110977 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -4,6 +4,6 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
before_action :authorize_admin_pipeline!
def show
- redirect_to project_settings_ci_cd_path(@project, params: params)
+ redirect_to project_settings_ci_cd_path(@project, params: params.to_unsafe_h)
end
end
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 62bdc84b41a..4c39ee4045f 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -4,16 +4,7 @@ class Projects::ReleasesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_read_release!
- before_action :check_releases_page_feature_flag
def index
end
-
- private
-
- def check_releases_page_feature_flag
- return render_404 unless Feature.enabled?(:releases_page, @project)
-
- push_frontend_feature_flag(:releases_page, @project)
- end
end
diff --git a/app/controllers/projects/serverless/functions_controller.rb b/app/controllers/projects/serverless/functions_controller.rb
index 0af2b7ef343..39eca10134f 100644
--- a/app/controllers/projects/serverless/functions_controller.rb
+++ b/app/controllers/projects/serverless/functions_controller.rb
@@ -7,19 +7,17 @@ module Projects
before_action :authorize_read_cluster!
- INDEX_PRIMING_INTERVAL = 10_000
- INDEX_POLLING_INTERVAL = 30_000
+ INDEX_PRIMING_INTERVAL = 15_000
+ INDEX_POLLING_INTERVAL = 60_000
def index
- finder = Projects::Serverless::FunctionsFinder.new(project.clusters)
-
respond_to do |format|
format.json do
functions = finder.execute
if functions.any?
Gitlab::PollingInterval.set_header(response, interval: INDEX_POLLING_INTERVAL)
- render json: Projects::Serverless::ServiceSerializer.new(current_user: @current_user).represent(functions)
+ render json: serialize_function(functions)
else
Gitlab::PollingInterval.set_header(response, interval: INDEX_PRIMING_INTERVAL)
head :no_content
@@ -32,6 +30,29 @@ module Projects
end
end
end
+
+ def show
+ @service = serialize_function(finder.service(params[:environment_id], params[:id]))
+ return not_found if @service.nil?
+
+ respond_to do |format|
+ format.json do
+ render json: @service
+ end
+
+ format.html
+ end
+ end
+
+ private
+
+ def finder
+ Projects::Serverless::FunctionsFinder.new(project.clusters)
+ end
+
+ def serialize_function(function)
+ Projects::Serverless::ServiceSerializer.new(current_user: @current_user, project: project).represent(function)
+ end
end
end
end
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
new file mode 100644
index 00000000000..521ec2acebb
--- /dev/null
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Projects
+ module Settings
+ class OperationsController < Projects::ApplicationController
+ before_action :check_license
+ before_action :authorize_update_environment!
+
+ helper_method :error_tracking_setting
+
+ def show
+ end
+
+ def update
+ result = ::Projects::Operations::UpdateService.new(project, current_user, update_params).execute
+
+ if result[:status] == :success
+ flash[:notice] = _('Your changes have been saved')
+ redirect_to project_settings_operations_path(@project)
+ else
+ render 'show'
+ end
+ end
+
+ private
+
+ def error_tracking_setting
+ @error_tracking_setting ||= project.error_tracking_setting ||
+ project.build_error_tracking_setting
+ end
+
+ def update_params
+ params.require(:project).permit(permitted_project_params)
+ end
+
+ # overridden in EE
+ def permitted_project_params
+ { error_tracking_setting_attributes: [:enabled, :api_url, :token] }
+ end
+
+ def check_license
+ render_404 unless helpers.settings_operations_available?
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 878816475b2..d3af35723ac 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -10,10 +10,10 @@ class ProjectsController < Projects::ApplicationController
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :whitelist_query_limiting, only: [:create]
- before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
+ before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :resolve]
before_action :redirect_git_extension, only: [:show]
- before_action :project, except: [:index, :new, :create]
- before_action :repository, except: [:index, :new, :create]
+ before_action :project, except: [:index, :new, :create, :resolve]
+ before_action :repository, except: [:index, :new, :create, :resolve]
before_action :assign_ref_vars, only: [:show], if: :repo_exists?
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?]
@@ -442,4 +442,14 @@ class ProjectsController < Projects::ApplicationController
def present_project
@project = @project.present(current_user: current_user)
end
+
+ def resolve
+ @project = Project.find(params[:id])
+
+ if can?(current_user, :read_project, @project)
+ redirect_to @project
+ else
+ render_404
+ end
+ end
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index fa5d84633b5..519e7439205 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -70,6 +70,10 @@ class UploadsController < ApplicationController
end
end
+ def cache_publicly?
+ User === model || Appearance === model
+ end
+
def upload_model_class
MODEL_CLASSES[params[:model]] || raise(UnknownUploadModelError)
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index b73a3fa6e01..1a69ec85d18 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -149,6 +149,18 @@ class IssuableFinder
end
end
+ def related_groups
+ if project? && project && project.group && Ability.allowed?(current_user, :read_group, project.group)
+ project.group.self_and_ancestors
+ elsif group
+ [group]
+ elsif current_user
+ Gitlab::ObjectHierarchy.new(current_user.authorized_groups, current_user.groups).all_objects
+ else
+ []
+ end
+ end
+
def project?
params[:project_id].present?
end
@@ -163,8 +175,10 @@ class IssuableFinder
end
# rubocop: disable CodeReuse/ActiveRecord
- def projects(items = nil)
- return @projects = project if project?
+ def projects
+ return @projects if defined?(@projects)
+
+ return @projects = [project] if project?
projects =
if current_user && params[:authorized_only].presence && !current_user_related?
@@ -459,7 +473,7 @@ class IssuableFinder
elsif filter_by_any_milestone?
items = items.any_milestone
elsif filter_by_upcoming_milestone?
- upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items))
+ upcoming_ids = Milestone.upcoming_ids(projects, related_groups)
items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
elsif filter_by_started_milestone?
items = items.left_joins_milestones.where('milestones.start_date <= NOW()')
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index 9c477978f60..77b55cbb838 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -3,8 +3,8 @@
# Search for milestones
#
# params - Hash
-# project_ids: Array of project ids or single project id.
-# group_ids: Array of group ids or single group id.
+# project_ids: Array of project ids or single project id or ActiveRecord relation.
+# group_ids: Array of group ids or single group id or ActiveRecord relation.
# order - Orders by field default due date asc.
# title - filter by title.
# state - filters by state.
@@ -12,20 +12,17 @@
class MilestonesFinder
include FinderMethods
- attr_reader :params, :project_ids, :group_ids
+ attr_reader :params
def initialize(params = {})
- @project_ids = Array(params[:project_ids])
- @group_ids = Array(params[:group_ids])
@params = params
end
def execute
- return Milestone.none if project_ids.empty? && group_ids.empty?
-
items = Milestone.all
items = by_groups_and_projects(items)
items = by_title(items)
+ items = by_search_title(items)
items = by_state(items)
order(items)
@@ -34,7 +31,7 @@ class MilestonesFinder
private
def by_groups_and_projects(items)
- items.for_projects_and_groups(project_ids, group_ids)
+ items.for_projects_and_groups(params[:project_ids], params[:group_ids])
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -47,6 +44,14 @@ class MilestonesFinder
end
# rubocop: enable CodeReuse/ActiveRecord
+ def by_search_title(items)
+ if params[:search_title].present?
+ items.search_title(params[:search_title])
+ else
+ items
+ end
+ end
+
def by_state(items)
Milestone.filter_by_state(items, params[:state])
end
diff --git a/app/finders/projects/serverless/functions_finder.rb b/app/finders/projects/serverless/functions_finder.rb
index 2b5d67e79d7..2f2816a4a08 100644
--- a/app/finders/projects/serverless/functions_finder.rb
+++ b/app/finders/projects/serverless/functions_finder.rb
@@ -15,11 +15,40 @@ module Projects
clusters_with_knative_installed.exists?
end
+ def service(environment_scope, name)
+ knative_service(environment_scope, name)&.first
+ end
+
private
+ def knative_service(environment_scope, name)
+ clusters_with_knative_installed.preload_knative.map do |cluster|
+ next if environment_scope != cluster.environment_scope
+
+ services = cluster.application_knative.services_for(ns: cluster.platform_kubernetes&.actual_namespace)
+ .select { |svc| svc["metadata"]["name"] == name }
+
+ add_metadata(cluster, services).first unless services.nil?
+ end
+ end
+
def knative_services
clusters_with_knative_installed.preload_knative.map do |cluster|
- cluster.application_knative.services_for(ns: cluster.platform_kubernetes&.actual_namespace)
+ services = cluster.application_knative.services_for(ns: cluster.platform_kubernetes&.actual_namespace)
+ add_metadata(cluster, services) unless services.nil?
+ end
+ end
+
+ def add_metadata(cluster, services)
+ services.each do |s|
+ s["environment_scope"] = cluster.environment_scope
+ s["cluster_id"] = cluster.id
+
+ if services.length == 1
+ s["podcount"] = cluster.application_knative.service_pod_details(
+ cluster.platform_kubernetes&.actual_namespace,
+ s["metadata"]["name"]).length
+ end
end
end
diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb
index ab37c282fe5..e9a4ea9157b 100644
--- a/app/graphql/types/permission_types/project.rb
+++ b/app/graphql/types/permission_types/project.rb
@@ -8,7 +8,7 @@ module Types
abilities :change_namespace, :change_visibility_level, :rename_project,
:remove_project, :archive_project, :remove_fork_project,
:remove_pages, :read_project, :create_merge_request_in,
- :read_wiki, :read_project_member, :create_issue, :upload_file,
+ :read_wiki, :read_project_member, :create_issue, :upload_file,
:read_cycle_analytics, :download_code, :download_wiki_code,
:fork_project, :create_project_snippet, :read_commit_status,
:request_access, :create_pipeline, :create_pipeline_schedule,
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 82bb2d1a805..9efa84b02f0 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -268,6 +268,17 @@ module ApplicationHelper
_('You are on a read-only GitLab instance.')
end
+ def client_class_list
+ "gl-browser-#{browser.id} gl-platform-#{browser.platform.id}"
+ end
+
+ def client_js_flags
+ {
+ "is#{browser.id.to_s.titlecase}": true,
+ "is#{browser.platform.id.to_s.titlecase}": true
+ }
+ end
+
def autocomplete_data_sources(object, noteable_type)
return {} unless object && noteable_type
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 5a7c005fd06..c8e4e2e3df9 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -20,7 +20,7 @@ module ApplicationSettingsHelper
def enabled_protocol
case Gitlab::CurrentSettings.enabled_git_access_protocol
when 'http'
- gitlab_config.protocol
+ Gitlab.config.gitlab.protocol
when 'ssh'
'ssh'
end
@@ -35,7 +35,7 @@ module ApplicationSettingsHelper
end
def http_enabled?
- all_protocols_enabled? || enabled_protocol == 'http'
+ all_protocols_enabled? || Gitlab::CurrentSettings.enabled_git_access_protocol == 'http'
end
def enabled_project_button(project, protocol)
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 654fb9d9987..01a0fb34484 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -31,7 +31,7 @@ module AuthHelper
def form_based_provider_with_highest_priority
@form_based_provider_with_highest_priority ||= begin
form_based_provider_priority.each do |provider_regexp|
- highest_priority = form_based_providers.find { |provider| provider.match?(provider_regexp) }
+ highest_priority = form_based_providers.find { |provider| provider.match?(provider_regexp) }
break highest_priority unless highest_priority.nil?
end
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 3dea0975beb..23d6684a8e6 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -177,7 +177,8 @@ module BlobHelper
'relative-url-root' => Rails.application.config.relative_url_root,
'assets-prefix' => Gitlab::Application.config.assets.prefix,
'blob-filename' => @blob && @blob.path,
- 'project-id' => project.id
+ 'project-id' => project.id,
+ 'is-markdown' => @blob && @blob.path && Gitlab::MarkupHelper.gitlab_markdown?(@blob.path)
}
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index d52cfd6e37a..d58f634425b 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -154,7 +154,7 @@ module CommitsHelper
if user.nil?
mail_to(source_email, text, link_options)
else
- link_to(text, user_path(user), link_options)
+ link_to(text, user_path(user), { class: "commit-#{options[:source]}-link js-user-link", data: { user_id: user.id } })
end
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 7b22bc8f98f..b3935ae350d 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -7,6 +7,15 @@ module EnvironmentsHelper
}
end
+ def environments_folder_list_view_data
+ {
+ "endpoint" => folder_project_environments_path(@project, @folder, format: :json),
+ "folder-name" => @folder,
+ "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
+ "can-read-environment" => can?(current_user, :read_environment, @project).to_s
+ }
+ end
+
def metrics_data(project, environment)
{
"settings-path" => edit_project_service_path(project, 'prometheus'),
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 5f7147508c7..f8176facce9 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -209,7 +209,7 @@ module IssuablesHelper
end
def issuable_labels_tooltip(labels, limit: 5)
- first, last = labels.partition.with_index { |_, i| i < limit }
+ first, last = labels.partition.with_index { |_, i| i < limit }
if labels && labels.any?
label_names = first.collect { |label| label.fetch(:title) }
diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb
index 5a21403bc5e..ab4a1ccc0d1 100644
--- a/app/helpers/members_helper.rb
+++ b/app/helpers/members_helper.rb
@@ -32,7 +32,7 @@ module MembersHelper
end
def filter_group_project_member_path(options = {})
- options = params.slice(:search, :sort).merge(options)
+ options = params.slice(:search, :sort).merge(options).permit!
"#{request.path}?#{options.to_param}"
end
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 033686823a2..293dd20ad49 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -85,7 +85,7 @@ module NotesHelper
diffs_project_merge_request_path(discussion.project, discussion.noteable, path_params)
elsif discussion.for_commit?
- anchor = discussion.line_code if discussion.diff_discussion?
+ anchor = discussion.diff_discussion? ? discussion.line_code : "note_#{discussion.first_note.id}"
project_commit_path(discussion.project, discussion.noteable, anchor: anchor)
end
diff --git a/app/helpers/projects/error_tracking_helper.rb b/app/helpers/projects/error_tracking_helper.rb
new file mode 100644
index 00000000000..6daf2e21ca2
--- /dev/null
+++ b/app/helpers/projects/error_tracking_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Projects::ErrorTrackingHelper
+ def error_tracking_data(project)
+ error_tracking_enabled = !!project.error_tracking_setting&.enabled?
+
+ {
+ 'index-path' => project_error_tracking_index_path(project,
+ format: :json),
+ 'enable-error-tracking-link' => project_settings_operations_path(project),
+ 'error-tracking-enabled' => error_tracking_enabled.to_s,
+ 'illustration-path' => image_path('illustrations/cluster_popover.svg')
+ }
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 0cfc2db3285..eceee054ede 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -204,12 +204,10 @@ module ProjectsHelper
current_user.require_extra_setup_for_git_auth?
end
- def show_auto_devops_implicitly_enabled_banner?(project)
- cookie_key = "hide_auto_devops_implicitly_enabled_banner_#{project.id}"
+ def show_auto_devops_implicitly_enabled_banner?(project, user)
+ return false unless user_can_see_auto_devops_implicitly_enabled_banner?(project, user)
- project.has_auto_devops_implicitly_enabled? &&
- cookies[cookie_key.to_sym].blank? &&
- (project.owner == current_user || project.team.maintainer?(current_user))
+ cookies["hide_auto_devops_implicitly_enabled_banner_#{project.id}".to_sym].blank?
end
def link_to_set_password
@@ -285,6 +283,11 @@ module ProjectsHelper
!disabled && !compact_mode && Feature.enabled?(:project_list_show_issue_count, default_enabled: true)
end
+ # overridden in EE
+ def settings_operations_available?
+ can?(current_user, :read_environment, @project)
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -332,6 +335,7 @@ module ProjectsHelper
builds: :read_build,
clusters: :read_cluster,
serverless: :read_cluster,
+ error_tracking: :read_sentry_issue,
labels: :read_label,
issues: :read_issue,
project_members: :read_project_member,
@@ -485,7 +489,7 @@ module ProjectsHelper
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'),
pagesAvailable: Gitlab.config.pages.enabled,
pagesAccessControlEnabled: Gitlab.config.pages.access_control,
- pagesHelpPath: help_page_path('user/project/pages/index.md')
+ pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control-core-only')
}
end
@@ -546,6 +550,7 @@ module ProjectsHelper
services#edit
repository#show
ci_cd#show
+ operations#show
badges#index
pages#show
]
@@ -575,8 +580,16 @@ module ProjectsHelper
environments
clusters
functions
+ error_tracking
user
gcp
]
end
+
+ def user_can_see_auto_devops_implicitly_enabled_banner?(project, user)
+ Ability.allowed?(user, :admin_project, project) &&
+ project.has_auto_devops_implicitly_enabled? &&
+ project.builds_enabled? &&
+ !project.repository.gitlab_ci_yml
+ end
end
diff --git a/app/helpers/release_blog_post_helper.rb b/app/helpers/release_blog_post_helper.rb
new file mode 100644
index 00000000000..31b5b7edc39
--- /dev/null
+++ b/app/helpers/release_blog_post_helper.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module ReleaseBlogPostHelper
+ def blog_post_url
+ Gitlab::ReleaseBlogPost.instance.blog_post_url
+ end
+end
diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb
index cb21f922401..0d880c38a7b 100644
--- a/app/helpers/runners_helper.rb
+++ b/app/helpers/runners_helper.rb
@@ -28,4 +28,14 @@ module RunnersHelper
display_name + id
end
end
+
+ # Due to inability of performing sorting of runners by cached "contacted_at" values we have to show uncached values if sorting by "contacted_asc" is requested.
+ # Please refer to the following issue for more details: https://gitlab.com/gitlab-org/gitlab-ce/issues/55920
+ def runner_contacted_at(runner)
+ if params[:sort] == 'contacted_asc'
+ runner.uncached_contacted_at
+ else
+ runner.contacted_at
+ end
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 80cc568820a..0ee76a51f7d 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -24,10 +24,10 @@ module SearchHelper
end
def search_entries_info(collection, scope, term)
- return unless collection.count > 0
+ return if collection.to_a.empty?
from = collection.offset_value + 1
- to = collection.offset_value + collection.count
+ to = collection.offset_value + collection.to_a.size
count = collection.total_count
"Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb
index 4aba48061ba..1ad7bb81784 100644
--- a/app/helpers/user_callouts_helper.rb
+++ b/app/helpers/user_callouts_helper.rb
@@ -13,6 +13,10 @@ module UserCalloutsHelper
!user_dismissed?(GCP_SIGNUP_OFFER)
end
+ def render_flash_user_callout(flash_type, message, feature_name)
+ render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name
+ end
+
private
def user_dismissed?(feature_name)
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 370e6d2f90b..654ae211310 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -77,6 +77,17 @@ module Emails
mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason))
end
+ def import_issues_csv_email(user_id, project_id, results)
+ @user = User.find(user_id)
+ @project = Project.find(project_id)
+ @results = results
+
+ mail(to: @user.notification_email, subject: subject('Imported issues')) do |format|
+ format.html { render layout: 'mailer' }
+ format.text { render layout: 'mailer' }
+ end
+ end
+
private
def setup_issue_mail(issue_id, recipient_id)
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 2ac4610967d..80e0a17c312 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -76,6 +76,10 @@ class NotifyPreview < ActionMailer::Preview
Notify.changed_milestone_issue_email(user.id, issue.id, milestone, user.id)
end
+ def import_issues_csv_email
+ Notify.import_issues_csv_email(user, project, { success: 3, errors: [5, 6, 7], valid_file: true })
+ end
+
def closed_merge_request_email
Notify.closed_merge_request_email(user.id, issue.id, user.id).message
end
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index e114c435b67..ff1ecfda684 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -44,7 +44,11 @@ class Appearance < ActiveRecord::Base
private
def logo_system_path(logo, mount_type)
- return unless logo&.upload
+ # Legacy attachments may not have have an associated Upload record,
+ # so fallback to the AttachmentUploader#url if this is the
+ # case. AttachmentUploader#path doesn't work because for a local
+ # file, this is an absolute path to the file.
+ return logo&.url unless logo&.upload
# If we're using a CDN, we need to use the full URL
asset_host = ActionController::Base.asset_host
diff --git a/app/models/blob_viewer/gitlab_ci_yml.rb b/app/models/blob_viewer/gitlab_ci_yml.rb
index 655241c2808..11228e620c9 100644
--- a/app/models/blob_viewer/gitlab_ci_yml.rb
+++ b/app/models/blob_viewer/gitlab_ci_yml.rb
@@ -10,16 +10,16 @@ module BlobViewer
self.file_types = %i(gitlab_ci)
self.binary = false
- def validation_message(project, sha)
+ def validation_message(opts)
return @validation_message if defined?(@validation_message)
prepare!
- @validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data, { project: project, sha: sha })
+ @validation_message = Gitlab::Ci::YamlProcessor.validation_message(blob.data, opts)
end
- def valid?(project, sha)
- validation_message(project, sha).blank?
+ def valid?(opts)
+ validation_message(opts).blank?
end
end
end
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index 29aa00a66d9..5450d40ea95 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -2,11 +2,13 @@
module Ci
class Bridge < CommitStatus
+ include Ci::Processable
include Importable
include AfterCommitQueue
include Gitlab::Utils::StrongMemoize
belongs_to :project
+ belongs_to :trigger_request
validates :ref, presence: true
def self.retry(bridge, current_user)
@@ -23,6 +25,21 @@ module Ci
.fabricate!
end
+ def schedulable?
+ false
+ end
+
+ def action?
+ false
+ end
+
+ def artifacts?
+ false
+ end
+
+ def expanded_environment_name
+ end
+
def predefined_variables
raise NotImplementedError
end
@@ -30,5 +47,9 @@ module Ci
def execute_hooks
raise NotImplementedError
end
+
+ def to_partial_path
+ 'projects/generic_commit_statuses/generic_commit_status'
+ end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index aeb35538d67..35cf4f8d277 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -3,15 +3,21 @@
module Ci
class Build < CommitStatus
prepend ArtifactMigratable
+ include Ci::Processable
include TokenAuthenticatable
include AfterCommitQueue
include ObjectStorage::BackgroundMove
include Presentable
include Importable
+ include IgnorableColumn
include Gitlab::Utils::StrongMemoize
include Deployable
include HasRef
+ BuildArchivedError = Class.new(StandardError)
+
+ ignore_column :commands
+
belongs_to :project, inverse_of: :builds
belongs_to :runner
belongs_to :trigger_request
@@ -31,7 +37,7 @@ module Ci
has_one :"job_artifacts_#{key}", -> { where(file_type: value) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
end
- has_one :metadata, class_name: 'Ci::BuildMetadata'
+ has_one :metadata, class_name: 'Ci::BuildMetadata', autosave: true
has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build
accepts_nested_attributes_for :runner_session
@@ -219,8 +225,15 @@ module Ci
before_transition any => [:failed] do |build|
next unless build.project
+ next unless build.deployment
+
+ begin
+ build.deployment.drop!
+ rescue => e
+ Gitlab::Sentry.track_exception(e, extra: { build_id: build.id })
+ end
- build.deployment&.drop
+ true
end
after_transition any => [:failed] do |build|
@@ -273,11 +286,14 @@ module Ci
# degenerated build is one that cannot be run by Runner
def degenerated?
- self.options.nil?
+ self.options.blank?
end
def degenerate!
- self.update!(options: nil, yaml_variables: nil, commands: nil)
+ Build.transaction do
+ self.update!(options: nil, yaml_variables: nil)
+ self.metadata&.destroy
+ end
end
def archived?
@@ -623,12 +639,20 @@ module Ci
super || project.try(:build_coverage_regex)
end
- def when
- read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
+ def options
+ read_metadata_attribute(:options, :config_options, {})
end
def yaml_variables
- read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || []
+ read_metadata_attribute(:yaml_variables, :config_variables, [])
+ end
+
+ def options=(value)
+ write_metadata_attribute(:options, :config_options, value)
+ end
+
+ def yaml_variables=(value)
+ write_metadata_attribute(:yaml_variables, :config_variables, value)
end
def user_variables
@@ -904,8 +928,11 @@ module Ci
# have the old integer only format. This method returns the retry option
# normalized as a hash in 11.5+ format.
def normalized_retry
- value = options&.dig(:retry)
- value.is_a?(Integer) ? { max: value } : value.to_h
+ strong_memoize(:normalized_retry) do
+ value = options&.dig(:retry)
+ value = value.is_a?(Integer) ? { max: value } : value.to_h
+ value.with_indifferent_access
+ end
end
def build_attributes_from_config
@@ -929,5 +956,20 @@ module Ci
def project_destroyed?
project.pending_delete?
end
+
+ def read_metadata_attribute(legacy_key, metadata_key, default_value = nil)
+ read_attribute(legacy_key) || metadata&.read_attribute(metadata_key) || default_value
+ end
+
+ def write_metadata_attribute(legacy_key, metadata_key, value)
+ # save to metadata or this model depending on the state of feature flag
+ if Feature.enabled?(:ci_build_metadata_config)
+ ensure_metadata.write_attribute(metadata_key, value)
+ write_attribute(legacy_key, nil)
+ else
+ write_attribute(legacy_key, value)
+ metadata&.write_attribute(metadata_key, nil)
+ end
+ end
end
end
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index 9d588b862bd..38390f49217 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -13,8 +13,12 @@ module Ci
belongs_to :build, class_name: 'Ci::Build'
belongs_to :project
+ before_create :set_build_project
+
validates :build, presence: true
- validates :project, presence: true
+
+ serialize :config_options, Serializers::JSON # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :config_variables, Serializers::JSON # rubocop:disable Cop/ActiveRecordSerialize
chronic_duration_attr_reader :timeout_human_readable, :timeout
@@ -33,5 +37,11 @@ module Ci
update(timeout: timeout, timeout_source: timeout_source)
end
+
+ private
+
+ def set_build_project
+ self.project_id ||= self.build.project_id
+ end
end
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 11c88200c37..789bb293811 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -73,6 +73,8 @@ module Ci
where(file_type: types)
end
+ scope :expired, -> (limit) { where('expire_at < ?', Time.now).limit(limit) }
+
delegate :filename, :exists?, :open, to: :file
enum file_type: {
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 1f5017cc3c3..acef5d2e643 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -25,6 +25,8 @@ module Ci
has_many :stages, -> { order(position: :asc) }, inverse_of: :pipeline
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
+ has_many :processables, -> { processables },
+ class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
has_many :variables, class_name: 'Ci::PipelineVariable'
@@ -178,6 +180,15 @@ module Ci
scope :for_user, -> (user) { where(user: user) }
+ scope :for_merge_request, -> (merge_request, ref, sha) do
+ ##
+ # We have to filter out unrelated MR pipelines.
+ # When merge request is empty, it selects general pipelines, such as push sourced pipelines.
+ # When merge request is matched, it selects MR pipelines.
+ where(merge_request: [nil, merge_request], ref: ref, sha: sha)
+ .sort_by_merge_request_pipelines
+ end
+
# Returns the pipelines in descending order (= newest first), optionally
# limited to a number of references.
#
@@ -265,6 +276,10 @@ module Ci
sources.reject { |source| source == "external" }.values
end
+ def self.latest_for_merge_request(merge_request, ref, sha)
+ for_merge_request(merge_request, ref, sha).first
+ end
+
def self.ci_sources_values
config_sources.values_at(:repository_source, :auto_devops_source, :unknown_source)
end
@@ -496,7 +511,7 @@ module Ci
return @config_processor if defined?(@config_processor)
@config_processor ||= begin
- ::Gitlab::Ci::YamlProcessor.new(ci_yaml_file, { project: project, sha: sha })
+ ::Gitlab::Ci::YamlProcessor.new(ci_yaml_file, { project: project, sha: sha, user: user })
rescue Gitlab::Ci::YamlProcessor::ValidationError => e
self.yaml_errors = e.message
nil
@@ -635,7 +650,7 @@ module Ci
def all_merge_requests
@all_merge_requests ||=
if merge_request?
- project.merge_requests.where(id: merge_request.id)
+ project.merge_requests.where(id: merge_request_id)
else
project.merge_requests.where(source_branch: ref)
end
@@ -714,6 +729,12 @@ module Ci
def git_ref
if merge_request?
+ ##
+ # In the future, we're going to change this ref to
+ # merge request's merged reference, such as "refs/merge-requests/:iid/merge".
+ # In order to do that, we have to update GitLab-Runner's source pulling
+ # logic.
+ # See https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1092
Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
else
super
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 8249199e76f..5aae31de6e2 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -256,6 +256,10 @@ module Ci
end
end
+ def uncached_contacted_at
+ read_attribute(:contacted_at)
+ end
+
private
def cleanup_runner_queue
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index 58f3fe2460a..0389945191e 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -14,6 +14,7 @@ module Ci
has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :builds, foreign_key: :stage_id
+ has_many :bridges, foreign_key: :stage_id
with_options unless: :importing? do
validates :project, presence: true
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 7799f069742..7c15aaa4825 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Ingress < ActiveRecord::Base
- VERSION = '0.23.0'.freeze
+ VERSION = '1.1.2'.freeze
self.table_name = 'clusters_applications_ingress'
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 5ac152278da..8d79b041b64 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -5,7 +5,7 @@ module Clusters
class Knative < ActiveRecord::Base
VERSION = '0.2.2'.freeze
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
-
+ METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze
FETCH_IP_ADDRESS_DELAY = 30.seconds
self.table_name = 'clusters_applications_knative'
@@ -19,6 +19,13 @@ module Clusters
self.reactive_cache_key = ->(knative) { [knative.class.model_name.singular, knative.id] }
+ def set_initial_status
+ return unless not_installable?
+ return unless verify_cluster?
+
+ self.status = 'installable'
+ end
+
state_machine :status do
after_transition any => [:installed] do |application|
application.run_after_commit do
@@ -34,6 +41,8 @@ module Clusters
scope :for_cluster, -> (cluster) { where(cluster: cluster) }
+ after_save :clear_reactive_cache!
+
def chart
'knative/knative'
end
@@ -49,7 +58,8 @@ module Clusters
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files,
- repository: REPOSITORY
+ repository: REPOSITORY,
+ postinstall: install_knative_metrics
)
end
@@ -71,7 +81,7 @@ module Clusters
end
def calculate_reactive_cache
- { services: read_services }
+ { services: read_services, pods: read_pods }
end
def ingress_service
@@ -79,7 +89,7 @@ module Clusters
end
def services_for(ns: namespace)
- return unless services
+ return [] unless services
return [] unless ns
services.select do |service|
@@ -87,13 +97,35 @@ module Clusters
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
+
+ def verify_cluster?
+ cluster&.application_helm_available? && cluster&.platform_kubernetes_rbac?
+ end
end
end
end
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 46d0388a464..26bf73f4dd8 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -5,7 +5,8 @@ module Clusters
class Prometheus < ActiveRecord::Base
include PrometheusAdapter
- VERSION = '6.7.3'.freeze
+ VERSION = '6.7.3'
+ READY_STATUS = [:installed, :updating, :updated, :update_errored].freeze
self.table_name = 'clusters_applications_prometheus'
@@ -24,12 +25,8 @@ module Clusters
end
end
- def ready_status
- [:installed]
- end
-
def ready?
- ready_status.include?(status_name)
+ READY_STATUS.include?(status_name)
end
def chart
@@ -50,10 +47,29 @@ module Clusters
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
- files: files
+ files: files,
+ postinstall: install_knative_metrics
+ )
+ end
+
+ def upgrade_command(values)
+ ::Gitlab::Kubernetes::Helm::UpgradeCommand.new(
+ name,
+ version: VERSION,
+ chart: chart,
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files_with_replaced_values(values)
)
end
+ # Returns a copy of files where the values of 'values.yaml'
+ # are replaced by the argument.
+ #
+ # See #values for the data format required
+ def files_with_replaced_values(replaced_values)
+ files.merge('values.yaml': replaced_values)
+ end
+
def prometheus_client
return unless kube_client
@@ -74,6 +90,10 @@ module Clusters
def kube_client
cluster&.kubeclient&.core_client
end
+
+ def install_knative_metrics
+ ["kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}"] if cluster.application_knative_available?
+ end
end
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 0c0247da1fb..f17da0bb7b1 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 < ActiveRecord::Base
- VERSION = '0.1.43'.freeze
+ VERSION = '0.1.45'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 7fe43cd2de0..a2c48973fa5 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -49,8 +49,9 @@ module Clusters
validates :name, cluster_name: true
validates :cluster_type, presence: true
- validate :restrict_modification, on: :update
+ validates :domain, allow_nil: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }
+ validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
@@ -63,6 +64,7 @@ module Clusters
delegate :available?, to: :application_helm, prefix: true, allow_nil: true
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
+ delegate :available?, to: :application_knative, prefix: true, allow_nil: true
enum cluster_type: {
instance_type: 1,
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index 0e74cce29b7..a556dd5ad8b 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -77,6 +77,10 @@ module Clusters
def available?
installed? || updated?
end
+
+ def update_in_progress?
+ updating?
+ end
end
end
end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 0dc0c4f80d6..8f3424db295 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -65,6 +65,8 @@ module Clusters
abac: 2
}
+ default_value_for :authorization_type, :rbac
+
def actual_namespace
if namespace.present?
namespace
@@ -152,7 +154,7 @@ module Clusters
def build_kube_client!
raise "Incomplete settings" unless api_url
- raise "No namespace" if cluster.project_type? && actual_namespace.empty? # can probably remove this line once we remove #actual_namespace
+ raise "No namespace" if cluster.project_type? && actual_namespace.empty? # can probably remove this line once we remove #actual_namespace
unless (username && password) || token
raise "Either username/password or token is required to access API"
diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb
index e349f0fe971..885f61beb05 100644
--- a/app/models/commit_collection.rb
+++ b/app/models/commit_collection.rb
@@ -19,6 +19,12 @@ class CommitCollection
commits.each(&block)
end
+ def committers
+ emails = commits.reject(&:merge_commit?).map(&:committer_email).uniq
+
+ User.by_any_email(emails)
+ end
+
# Sets the pipeline status for every commit.
#
# Setting this status ahead of time removes the need for running a query for
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 0f50bd39131..7f6562b63e5 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -41,6 +41,7 @@ class CommitStatus < ActiveRecord::Base
scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
scope :after_stage, -> (index) { where('stage_idx > ?', index) }
+ scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) }
# We use `CommitStatusEnums.failure_reasons` here so that EE can more easily
# extend this `Hash` with new values.
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index a8c9e54f00c..73a27326f6c 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -15,7 +15,7 @@ module CacheMarkdownField
# Increment this number every time the renderer changes its output
CACHE_REDCARPET_VERSION = 3
CACHE_COMMONMARK_VERSION_START = 10
- CACHE_COMMONMARK_VERSION = 12
+ CACHE_COMMONMARK_VERSION = 13
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
diff --git a/app/models/concerns/ci/processable.rb b/app/models/concerns/ci/processable.rb
new file mode 100644
index 00000000000..1c78b1413a8
--- /dev/null
+++ b/app/models/concerns/ci/processable.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Ci
+ ##
+ # This module implements methods that need to be implemented by CI/CD
+ # entities that are supposed to go through pipeline processing
+ # services.
+ #
+ #
+ module Processable
+ def schedulable?
+ raise NotImplementedError
+ end
+
+ def action?
+ raise NotImplementedError
+ end
+
+ def when
+ read_attribute(:when) || 'on_success'
+ end
+
+ def expanded_environment_name
+ raise NotImplementedError
+ end
+ end
+end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index b92643f87f8..0d2be4c61ab 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -85,11 +85,11 @@ module HasStatus
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
scope :success, -> { where(status: 'success') }
- scope :failed, -> { where(status: 'failed') }
- scope :canceled, -> { where(status: 'canceled') }
- scope :skipped, -> { where(status: 'skipped') }
- scope :manual, -> { where(status: 'manual') }
- scope :scheduled, -> { where(status: 'scheduled') }
+ scope :failed, -> { where(status: 'failed') }
+ scope :canceled, -> { where(status: 'canceled') }
+ scope :skipped, -> { where(status: 'skipped') }
+ scope :manual, -> { where(status: 'manual') }
+ scope :scheduled, -> { where(status: 'scheduled') }
scope :alive, -> { where(status: [:created, :pending, :running]) }
scope :created_or_pending, -> { where(status: [:created, :pending]) }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
diff --git a/app/models/concerns/manual_inverse_association.rb b/app/models/concerns/manual_inverse_association.rb
index e18edd33ba7..ff61412767e 100644
--- a/app/models/concerns/manual_inverse_association.rb
+++ b/app/models/concerns/manual_inverse_association.rb
@@ -5,8 +5,8 @@ module ManualInverseAssociation
class_methods do
def manual_inverse_association(association, inverse)
- define_method(association) do |*args|
- super(*args).tap do |value|
+ define_method(association) do
+ super().tap do |value|
next unless value
child_association = value.association(inverse)
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 2c08a8e1acf..cf057d774cf 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class ContainerRepository < ActiveRecord::Base
+ include Gitlab::Utils::StrongMemoize
+
belongs_to :project
validates :name, length: { minimum: 0, allow_nil: false }
@@ -8,6 +10,8 @@ class ContainerRepository < ActiveRecord::Base
delegate :client, to: :registry
+ scope :ordered, -> { order(:name) }
+
# rubocop: disable CodeReuse/ServiceClass
def registry
@registry ||= begin
@@ -39,11 +43,12 @@ class ContainerRepository < ActiveRecord::Base
end
def tags
- return @tags if defined?(@tags)
return [] unless manifest && manifest['tags']
- @tags = manifest['tags'].map do |tag|
- ContainerRegistry::Tag.new(self, tag)
+ strong_memoize(:tags) do
+ manifest['tags'].sort.map do |tag|
+ ContainerRegistry::Tag.new(self, tag)
+ end
end
end
diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb
index 9bcc95e35a5..74aa04ab7d0 100644
--- a/app/models/dashboard_group_milestone.rb
+++ b/app/models/dashboard_group_milestone.rb
@@ -11,11 +11,12 @@ class DashboardGroupMilestone < GlobalMilestone
@group_name = milestone.group.full_name
end
- def self.build_collection(groups)
- Milestone.of_groups(groups.select(:id))
+ def self.build_collection(groups, params)
+ milestones = Milestone.of_groups(groups.select(:id))
.reorder_by_due_date_asc
.order_by_name_asc
.active
- .map { |m| new(m) }
+ milestones = milestones.search_title(params[:search_title]) if params[:search_title].present?
+ milestones.map { |m| new(m) }
end
end
diff --git a/app/models/email.rb b/app/models/email.rb
index b6a977dfa22..3ce6e792fa8 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -15,7 +15,7 @@ class Email < ActiveRecord::Base
after_commit :update_invalid_gpg_signatures, if: -> { previous_changes.key?('confirmed_at') }
devise :confirmable
- self.reconfirmable = false # currently email can't be changed, no need to reconfirm
+ self.reconfirmable = false # currently email can't be changed, no need to reconfirm
delegate :username, to: :user
diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
new file mode 100644
index 00000000000..7f4947ba27a
--- /dev/null
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class ProjectErrorTrackingSetting < ActiveRecord::Base
+ include ReactiveCaching
+
+ self.reactive_cache_key = ->(setting) { [setting.class.model_name.singular, setting.project_id] }
+
+ belongs_to :project
+
+ validates :api_url, length: { maximum: 255 }, public_url: true, url: { enforce_sanitization: true }
+
+ validate :validate_api_url_path
+
+ attr_encrypted :token,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm'
+
+ after_save :clear_reactive_cache!
+
+ def sentry_client
+ Sentry::Client.new(api_url, token)
+ end
+
+ def sentry_external_url
+ self.class.extract_sentry_external_url(api_url)
+ end
+
+ def list_sentry_issues(opts = {})
+ with_reactive_cache('list_issues', opts.stringify_keys) do |result|
+ { issues: result }
+ end
+ end
+
+ def calculate_reactive_cache(request, opts)
+ case request
+ when 'list_issues'
+ sentry_client.list_issues(**opts.symbolize_keys)
+ end
+ end
+
+ # http://HOST/api/0/projects/ORG/PROJECT
+ # ->
+ # http://HOST/ORG/PROJECT
+ def self.extract_sentry_external_url(url)
+ url.sub('api/0/projects/', '')
+ end
+
+ private
+
+ def validate_api_url_path
+ unless URI(api_url).path.starts_with?('/api/0/projects')
+ errors.add(:api_url, 'path needs to start with /api/0/projects')
+ end
+ rescue URI::InvalidURIError
+ end
+ end
+end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 4f73beaafc5..68b2353556e 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -3,6 +3,8 @@
class ExternalIssue
include Referable
+ attr_reader :project
+
def initialize(issue_identifier, project)
@issue_identifier, @project = issue_identifier, project
end
@@ -32,12 +34,8 @@ class ExternalIssue
[self.class, to_s].hash
end
- def project
- @project
- end
-
def project_id
- @project.id
+ project.id
end
def to_reference(_from = nil, full: nil)
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 4e82f3fed27..fd17745b035 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -27,6 +27,7 @@ class GlobalMilestone
items = Milestone.of_projects(projects)
.reorder_by_due_date_asc
.order_by_name_asc
+ items = items.search_title(params[:search_title]) if params[:search_title].present?
Milestone.filter_by_state(items, params[:state]).map { |m| new(m) }
end
diff --git a/app/models/group.rb b/app/models/group.rb
index edac2444c4d..52f503404af 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -98,7 +98,7 @@ class Group < Namespace
def select_for_project_authorization
if current_scope.joins_values.include?(:shared_projects)
joins('INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id')
- .where('project_namespace.share_with_group_lock = ?', false)
+ .where('project_namespace.share_with_group_lock = ?', false)
.select("projects.id AS project_id, LEAST(project_group_links.group_access, members.access_level) AS access_level")
else
super
@@ -382,6 +382,10 @@ class Group < Namespace
end
end
+ def highest_group_member(user)
+ GroupMember.where(source_id: self_and_ancestors_ids, user_id: user.id).order(:access_level).last
+ end
+
def hashed_storage?(_feature)
false
end
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index a58537de319..97cb26c6ea9 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -5,9 +5,10 @@ class GroupMilestone < GlobalMilestone
def self.build_collection(group, projects, params)
params =
- { state: params[:state] }
+ { state: params[:state], search_title: params[:search_title] }
project_milestones = Milestone.of_projects(projects)
+ project_milestones = project_milestones.search_title(params[:search_title]) if params[:search_title].present?
child_milestones = Milestone.filter_by_state(project_milestones, params[:state])
grouped_milestones = child_milestones.group_by(&:title)
diff --git a/app/models/issue.rb b/app/models/issue.rb
index b7e13bcbccf..5c4ecbfdf4e 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -230,7 +230,8 @@ class Issue < ActiveRecord::Base
end
def check_for_spam?
- project.public? && (title_changed? || description_changed?)
+ publicly_visible? &&
+ (title_changed? || description_changed? || confidential_changed?)
end
def as_json(options = {})
diff --git a/app/models/label.rb b/app/models/label.rb
index 5d2d1afd1d9..1c3db3eb35d 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -214,6 +214,7 @@ class Label < ActiveRecord::Base
super(options).tap do |json|
json[:type] = self.try(:type)
json[:priority] = priority(options[:project]) if options.key?(:project)
+ json[:textColor] = text_color
end
end
diff --git a/app/models/list.rb b/app/models/list.rb
index 029685be927..682af761ba0 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -54,6 +54,6 @@ class List < ActiveRecord::Base
private
def can_be_destroyed
- destroyable?
+ throw(:abort) unless destroyable?
end
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 9fc95ea00c3..b0f049438eb 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -76,7 +76,7 @@ class Member < ActiveRecord::Base
scope :maintainers, -> { active.where(access_level: MAINTAINER) }
scope :masters, -> { maintainers } # @deprecated
scope :owners, -> { active.where(access_level: OWNER) }
- scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) }
+ scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) }
scope :owners_and_masters, -> { owners_and_maintainers } # @deprecated
scope :order_name_asc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'ASC')) }
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 6092c56b925..7206d858dae 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -284,6 +284,14 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
+ def committers
+ @committers ||= commits.committers
+ end
+
+ def authors
+ User.from_union([committers, User.where(id: self.author_id)])
+ end
+
# Verifies if title has changed not taking into account WIP prefix
# for merge requests.
def wipless_title_changed(old_title)
@@ -327,13 +335,15 @@ class MergeRequest < ActiveRecord::Base
end
def commits
- if persisted?
- merge_request_diff.commits
- elsif compare_commits
- compare_commits.reverse
- else
- []
- end
+ return merge_request_diff.commits if persisted?
+
+ commits_arr = if compare_commits
+ compare_commits.reverse
+ else
+ []
+ end
+
+ CommitCollection.new(source_project, commits_arr, source_branch)
end
def commits_count
@@ -550,15 +560,19 @@ class MergeRequest < ActiveRecord::Base
end
def diff_refs
- if persisted?
- merge_request_diff.diff_refs
- else
- Gitlab::Diff::DiffRefs.new(
- base_sha: diff_base_sha,
- start_sha: diff_start_sha,
- head_sha: diff_head_sha
- )
- end
+ persisted? ? merge_request_diff.diff_refs : repository_diff_refs
+ end
+
+ # Instead trying to fetch the
+ # persisted diff_refs, this method goes
+ # straight to the repository to get the
+ # most recent data possible.
+ def repository_diff_refs
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: branch_merge_base_sha,
+ start_sha: target_branch_sha,
+ head_sha: source_branch_sha
+ )
end
def branch_merge_base_sha
@@ -1092,10 +1106,16 @@ class MergeRequest < ActiveRecord::Base
def all_pipelines(shas: all_commit_shas)
return Ci::Pipeline.none unless source_project
- @all_pipelines ||= source_project.ci_pipelines
- .where(sha: shas, ref: source_branch)
- .where(merge_request: [nil, self])
- .sort_by_merge_request_pipelines
+ @all_pipelines ||=
+ source_project.ci_pipelines
+ .for_merge_request(self, source_branch, all_commit_shas)
+ end
+
+ def update_head_pipeline
+ find_actual_head_pipeline.try do |pipeline|
+ self.head_pipeline = pipeline
+ update_column(:head_pipeline_id, head_pipeline.id) if head_pipeline_id_changed?
+ end
end
def merge_request_pipeline_exists?
@@ -1338,4 +1358,11 @@ class MergeRequest < ActiveRecord::Base
source_project.repository.squash_in_progress?(id)
end
+
+ private
+
+ def find_actual_head_pipeline
+ source_project&.ci_pipelines
+ &.latest_for_merge_request(self, source_branch, diff_head_sha)
+ end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index f55c39d9912..26cfdc5ef30 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -28,7 +28,7 @@ class Milestone < ActiveRecord::Base
has_internal_id :iid, scope: :group, init: ->(s) { s&.group&.milestones&.maximum(:iid) }
has_many :issues
- has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
+ has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -38,12 +38,14 @@ class Milestone < ActiveRecord::Base
scope :closed, -> { with_state(:closed) }
scope :for_projects, -> { where(group: nil).includes(:project) }
- scope :for_projects_and_groups, -> (project_ids, group_ids) do
- conditions = []
- conditions << arel_table[:project_id].in(project_ids) if project_ids&.compact&.any?
- conditions << arel_table[:group_id].in(group_ids) if group_ids&.compact&.any?
+ scope :for_projects_and_groups, -> (projects, groups) do
+ projects = projects.compact if projects.is_a? Array
+ projects = [] if projects.nil?
- where(conditions.reduce(:or))
+ groups = groups.compact if groups.is_a? Array
+ groups = [] if groups.nil?
+
+ where(project_id: projects).or(where(group_id: groups))
end
scope :order_by_name_asc, -> { order(Arel::Nodes::Ascending.new(arel_table[:title].lower)) }
@@ -75,7 +77,7 @@ class Milestone < ActiveRecord::Base
alias_attribute :name, :title
class << self
- # Searches for milestones matching the given query.
+ # Searches for milestones with a matching title or description.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
#
@@ -86,6 +88,17 @@ class Milestone < ActiveRecord::Base
fuzzy_search(query, [:title, :description])
end
+ # Searches for milestones with a matching title.
+ #
+ # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
+ #
+ # query - The search query as a String
+ #
+ # Returns an ActiveRecord::Relation.
+ def search_title(query)
+ fuzzy_search(query, [:title])
+ end
+
def filter_by_state(milestones, state)
case state
when 'closed' then milestones.closed
@@ -133,18 +146,29 @@ class Milestone < ActiveRecord::Base
@link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end
- def self.upcoming_ids_by_projects(projects)
- rel = unscoped.of_projects(projects).active.where('due_date > ?', Time.now)
+ def self.upcoming_ids(projects, groups)
+ rel = unscoped
+ .for_projects_and_groups(projects, groups)
+ .active.where('milestones.due_date > NOW()')
if Gitlab::Database.postgresql?
- rel.order(:project_id, :due_date).select('DISTINCT ON (project_id) id')
+ rel.order(:project_id, :group_id, :due_date).select('DISTINCT ON (project_id, group_id) id')
else
+ # We need to use MySQL's NULL-safe comparison operator `<=>` here
+ # because one of `project_id` or `group_id` is always NULL
+ join_clause = <<~HEREDOC
+ LEFT OUTER JOIN milestones earlier_milestones
+ ON milestones.project_id <=> earlier_milestones.project_id
+ AND milestones.group_id <=> earlier_milestones.group_id
+ AND milestones.due_date > earlier_milestones.due_date
+ AND earlier_milestones.due_date > NOW()
+ AND earlier_milestones.state = 'active'
+ HEREDOC
+
rel
- .group(:project_id, :due_date, :id)
- .having('due_date = MIN(due_date)')
- .pluck(:id, :project_id, :due_date)
- .uniq(&:second)
- .map(&:first)
+ .joins(join_clause)
+ .where('earlier_milestones.id IS NULL')
+ .select(:id)
end
end
@@ -178,7 +202,7 @@ class Milestone < ActiveRecord::Base
return STATE_COUNT_HASH unless projects || groups
counts = Milestone
- .for_projects_and_groups(projects&.map(&:id), groups&.map(&:id))
+ .for_projects_and_groups(projects, groups)
.reorder(nil)
.group(:state)
.count
@@ -262,8 +286,7 @@ class Milestone < ActiveRecord::Base
if project
relation = Milestone.for_projects_and_groups([project_id], [project.group&.id])
elsif group
- project_ids = group.projects.map(&:id)
- relation = Milestone.for_projects_and_groups(project_ids, [group.id])
+ relation = Milestone.for_projects_and_groups(group.projects.select(:id), [group.id])
end
title_exists = relation.find_by_title(title)
diff --git a/app/models/note.rb b/app/models/note.rb
index becf14e9785..1578ae9c4cc 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -456,6 +456,10 @@ class Note < ActiveRecord::Base
Upload.find_by(model: self, path: paths)
end
+ def parent
+ project
+ end
+
private
def keep_around_commit
diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb
index ad6a008dee8..34220c1b450 100644
--- a/app/models/pool_repository.rb
+++ b/app/models/pool_repository.rb
@@ -85,7 +85,11 @@ class PoolRepository < ActiveRecord::Base
def unlink_repository(repository)
object_pool.unlink_repository(repository.raw)
- mark_obsolete unless member_projects.where.not(id: repository.project.id).exists?
+ if member_projects.where.not(id: repository.project.id).exists?
+ true
+ else
+ mark_obsolete
+ end
end
def object_pool
diff --git a/app/models/project.rb b/app/models/project.rb
index cd558752080..15465d9b356 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -73,7 +73,7 @@ class Project < ActiveRecord::Base
delegate :no_import?, to: :import_state, allow_nil: true
default_value_for :archived, false
- default_value_for :visibility_level, gitlab_config_features.visibility_level
+ default_value_for(:visibility_level) { Gitlab::CurrentSettings.default_project_visibility }
default_value_for :resolve_outdated_diff_discussions, false
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { Gitlab::CurrentSettings.pick_repository_storage }
@@ -187,6 +187,7 @@ class Project < ActiveRecord::Base
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :project_repository, inverse_of: :project
+ has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
@@ -295,6 +296,8 @@ class Project < ActiveRecord::Base
allow_destroy: true,
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
+ accepts_nested_attributes_for :error_tracking_setting, update_only: true
+
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team
@@ -328,10 +331,10 @@ class Project < ActiveRecord::Base
ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
enforce_user: true }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
- validate :check_limit, on: :create
+ validate :check_personal_projects_limit, on: :create
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
- validate :visibility_level_allowed_by_group
- validate :visibility_level_allowed_as_fork
+ validate :visibility_level_allowed_by_group, if: -> { changes.has_key?(:visibility_level) }
+ validate :visibility_level_allowed_as_fork, if: -> { changes.has_key?(:visibility_level) }
validate :check_wiki_path_conflict
validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) }
validates :repository_storage,
@@ -644,19 +647,15 @@ class Project < ActiveRecord::Base
end
# ref can't be HEAD, can only be branch/tag name or SHA
- def latest_successful_builds_for(ref = default_branch)
+ def latest_successful_build_for(job_name, ref = default_branch)
latest_pipeline = ci_pipelines.latest_successful_for(ref)
+ return unless latest_pipeline
- if latest_pipeline
- latest_pipeline.builds.latest.with_artifacts_archive
- else
- builds.none
- end
+ latest_pipeline.builds.latest.with_artifacts_archive.find_by(name: job_name)
end
- def latest_successful_build_for(job_name, ref = default_branch)
- builds = latest_successful_builds_for(ref)
- builds.find_by!(name: job_name)
+ def latest_successful_build_for!(job_name, ref = default_branch)
+ latest_successful_build_for(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}"))
end
def merge_base_commit(first_commit_id, second_commit_id)
@@ -810,18 +809,22 @@ class Project < ActiveRecord::Base
::Gitlab::CurrentSettings.mirror_available
end
- def check_limit
- unless creator.can_create_project? || namespace.kind == 'group'
- projects_limit = creator.projects_limit
+ def check_personal_projects_limit
+ # Since this method is called as validation hook, `creator` might not be
+ # present. Since the validation for that will fail, we can just return
+ # early.
+ return if !creator || creator.can_create_project? ||
+ namespace.kind == 'group'
- if projects_limit == 0
- self.errors.add(:limit_reached, "Personal project creation is not allowed. Please contact your administrator with questions")
+ limit = creator.projects_limit
+ error =
+ if limit.zero?
+ _('Personal project creation is not allowed. Please contact your administrator with questions')
else
- self.errors.add(:limit_reached, "Your project limit is #{projects_limit} projects! Please contact your administrator to increase it")
+ _('Your project limit is %{limit} projects! Please contact your administrator to increase it')
end
- end
- rescue
- self.errors.add(:base, "Can't check your ability to create project")
+
+ self.errors.add(:limit_reached, error % { limit: limit })
end
def visibility_level_allowed_by_group
@@ -912,11 +915,16 @@ class Project < ActiveRecord::Base
def new_issuable_address(author, address_type)
return unless Gitlab::IncomingEmail.supports_issue_creation? && author
+ # check since this can come from a request parameter
+ return unless %w(issue merge_request).include?(address_type)
+
author.ensure_incoming_email_token!
- suffix = address_type == 'merge_request' ? '+merge-request' : ''
- Gitlab::IncomingEmail.reply_address(
- "#{full_path}#{suffix}+#{author.incoming_email_token}")
+ suffix = address_type.dasherize
+
+ # example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-issue@localhost.com
+ # example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-merge-request@localhost.com
+ Gitlab::IncomingEmail.reply_address("#{full_path_slug}-#{project_id}-#{author.incoming_email_token}-#{suffix}")
end
def build_commit_note(commit)
@@ -1527,7 +1535,7 @@ class Project < ActiveRecord::Base
end
def pages_available?
- Gitlab.config.pages.enabled && !namespace.subgroup?
+ Gitlab.config.pages.enabled
end
def remove_private_deploy_keys
@@ -1598,24 +1606,7 @@ class Project < ActiveRecord::Base
# rubocop: disable CodeReuse/ServiceClass
def after_create_default_branch
- return unless default_branch
-
- # Ensure HEAD points to the default branch in case it is not master
- change_head(default_branch)
-
- if Gitlab::CurrentSettings.default_branch_protection != Gitlab::Access::PROTECTION_NONE && !ProtectedBranch.protected?(self, default_branch)
- params = {
- name: default_branch,
- push_access_levels_attributes: [{
- access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER
- }],
- merge_access_levels_attributes: [{
- access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER
- }]
- }
-
- ProtectedBranches::CreateService.new(self, creator, params).execute(skip_authorization: true)
- end
+ Projects::ProtectDefaultBranchService.new(self).execute
end
# rubocop: enable CodeReuse/ServiceClass
@@ -1700,6 +1691,13 @@ class Project < ActiveRecord::Base
.append(key: 'CI_PROJECT_VISIBILITY', value: visibility)
.concat(container_registry_variables)
.concat(auto_devops_variables)
+ .concat(api_variables)
+ end
+
+ def api_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'CI_API_V4_URL', value: API::Helpers::Version.new('v4').root_url)
+ end
end
def container_registry_variables
@@ -1774,6 +1772,24 @@ class Project < ActiveRecord::Base
handle_update_attribute_error(e, value)
end
+ # Tries to set repository as read_only, checking for existing Git transfers in progress beforehand
+ #
+ # @return [Boolean] true when set to read_only or false when an existing git transfer is in progress
+ def set_repository_read_only!
+ with_lock do
+ break false if git_transfer_in_progress?
+
+ update_column(:repository_read_only, true)
+ end
+ end
+
+ # Set repository as writable again
+ def set_repository_writable!
+ with_lock do
+ update_column(repository_read_only, false)
+ end
+ end
+
def pushes_since_gc
Gitlab::Redis::SharedState.with { |redis| redis.get(pushes_since_gc_redis_shared_state_key).to_i }
end
@@ -1888,15 +1904,17 @@ class Project < ActiveRecord::Base
def migrate_to_hashed_storage!
return unless storage_upgradable?
- update!(repository_read_only: true)
-
- if repo_reference_count > 0 || wiki_reference_count > 0
+ if git_transfer_in_progress?
ProjectMigrateHashedStorageWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
else
ProjectMigrateHashedStorageWorker.perform_async(id)
end
end
+ def git_transfer_in_progress?
+ repo_reference_count > 0 || wiki_reference_count > 0
+ end
+
def storage_version=(value)
super
@@ -1928,23 +1946,15 @@ class Project < ActiveRecord::Base
.where('project_authorizations.project_id = merge_requests.target_project_id')
.limit(1)
.select(1)
- source_of_merge_requests.opened
- .where(allow_collaboration: true)
- .where('EXISTS (?)', developer_access_exists)
+ merge_requests_allowing_collaboration.where('EXISTS (?)', developer_access_exists)
end
- def branch_allows_collaboration?(user, branch_name)
- return false unless user
-
- cache_key = "user:#{user.id}:#{branch_name}:branch_allows_push"
-
- memoized_results = strong_memoize(:branch_allows_collaboration) do
- Hash.new do |result, cache_key|
- result[cache_key] = fetch_branch_allows_collaboration?(user, branch_name)
- end
- end
+ def any_branch_allows_collaboration?(user)
+ fetch_branch_allows_collaboration(user)
+ end
- memoized_results[cache_key]
+ def branch_allows_collaboration?(user, branch_name)
+ fetch_branch_allows_collaboration(user, branch_name)
end
def licensed_features
@@ -2013,11 +2023,21 @@ class Project < ActiveRecord::Base
end
def leave_pool_repository
- pool_repository&.unlink_repository(repository)
+ pool_repository&.unlink_repository(repository) && update_column(:pool_repository_id, nil)
+ end
+
+ def link_pool_repository
+ pool_repository&.link_repository(repository)
end
private
+ def merge_requests_allowing_collaboration(source_branch = nil)
+ relation = source_of_merge_requests.opened.where(allow_collaboration: true)
+ relation = relation.where(source_branch: source_branch) if source_branch
+ relation
+ end
+
def create_new_pool_repository
pool = begin
create_pool_repository!(shard: Shard.by_name(repository_storage), source_project: self)
@@ -2142,26 +2162,19 @@ class Project < ActiveRecord::Base
raise ex
end
- def fetch_branch_allows_collaboration?(user, branch_name)
- check_access = -> do
- next false if empty_repo?
+ def fetch_branch_allows_collaboration(user, branch_name = nil)
+ return false unless user
- merge_requests = source_of_merge_requests.opened
- .where(allow_collaboration: true)
+ Gitlab::SafeRequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do
+ next false if empty_repo?
# Issue for N+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/49322
Gitlab::GitalyClient.allow_n_plus_1_calls do
- if branch_name
- merge_requests.find_by(source_branch: branch_name)&.can_be_merged_by?(user)
- else
- merge_requests.any? { |merge_request| merge_request.can_be_merged_by?(user) }
+ merge_requests_allowing_collaboration(branch_name).any? do |merge_request|
+ merge_request.can_be_merged_by?(user)
end
end
end
-
- Gitlab::SafeRequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do
- check_access.call
- end
end
def services_templates
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index 525725034a5..aa0c121fe99 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -30,4 +30,8 @@ class ProjectImportData < ActiveRecord::Base
def merge_credentials(hash)
self.credentials = credentials.to_h.merge(hash) unless hash.empty?
end
+
+ def clear_credentials
+ self.credentials = {}
+ end
end
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index a15780c14f9..83fd9a34438 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -59,7 +59,7 @@ class IrkerService < Service
' append "?key=secretpassword" to the URI (Note that due to a bug, if you ' \
' want to use a password, you have to omit the "#" on the channel). If you ' \
' specify a default IRC URI to prepend before each recipient, you can just ' \
- ' give a channel name.' },
+ ' give a channel name.' },
{ type: 'checkbox', name: 'colorize_messages' }
]
end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index b8e17087db5..3245cd22e73 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -39,9 +39,7 @@ class TeamcityService < CiService
end
def help
- 'The build configuration in Teamcity must use the build format '\
- 'number %build.vcs.number% '\
- 'you will also want to configure monitoring of all branches so merge '\
+ 'You will want to configure monitoring of all branches so merge '\
'requests build, that setting is in the vsc root advanced settings.'
end
@@ -70,7 +68,7 @@ class TeamcityService < CiService
end
def calculate_reactive_cache(sha, ref)
- response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
+ response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,revision:#{sha}")
{ build_page: read_build_page(response), commit_status: read_commit_status(response) }
end
diff --git a/app/models/release.rb b/app/models/release.rb
index df3dfe1cf2f..0dae5c90394 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -10,6 +10,10 @@ class Release < ActiveRecord::Base
# releases prior to 11.7 have no author
belongs_to :author, class_name: 'User'
+ has_many :links, class_name: 'Releases::Link'
+
+ accepts_nested_attributes_for :links, allow_destroy: true
+
validates :description, :project, :tag, presence: true
scope :sorted, -> { order(created_at: :desc) }
@@ -26,6 +30,16 @@ class Release < ActiveRecord::Base
actual_tag.nil?
end
+ def assets_count
+ links.count + sources.count
+ end
+
+ def sources
+ strong_memoize(:sources) do
+ Releases::Source.all(project, tag)
+ end
+ end
+
private
def actual_sha
diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb
new file mode 100644
index 00000000000..6f639e5a7b2
--- /dev/null
+++ b/app/models/releases/link.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Releases
+ class Link < ActiveRecord::Base
+ self.table_name = 'release_links'
+
+ belongs_to :release
+
+ validates :url, presence: true, url: true, uniqueness: { scope: :release }
+ validates :name, presence: true, uniqueness: { scope: :release }
+
+ scope :sorted, -> { order(created_at: :desc) }
+
+ def internal?
+ url.start_with?(release.project.web_url)
+ end
+
+ def external?
+ !internal?
+ end
+ end
+end
diff --git a/app/models/releases/source.rb b/app/models/releases/source.rb
new file mode 100644
index 00000000000..4d3d54457af
--- /dev/null
+++ b/app/models/releases/source.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Releases
+ class Source
+ include ActiveModel::Model
+
+ attr_accessor :project, :tag_name, :format
+
+ FORMATS = %w(zip tar.gz tar.bz2 tar).freeze
+
+ class << self
+ def all(project, tag_name)
+ Releases::Source::FORMATS.map do |format|
+ Releases::Source.new(project: project,
+ tag_name: tag_name,
+ format: format)
+ end
+ end
+ end
+
+ def url
+ Gitlab::Routing
+ .url_helpers
+ .project_archive_url(project,
+ id: File.join(tag_name, archive_prefix),
+ format: format)
+ end
+
+ private
+
+ def archive_prefix
+ "#{project.path}-#{tag_name.tr('/', '-')}"
+ end
+ end
+end
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index a3fa67c72bf..5eba7ddd75c 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -61,7 +61,10 @@ class RemoteMirror < ActiveRecord::Base
timestamp = Time.now
remote_mirror.update!(
- last_update_at: timestamp, last_successful_update_at: timestamp, last_error: nil
+ last_update_at: timestamp,
+ last_successful_update_at: timestamp,
+ last_error: nil,
+ error_notification_sent: false
)
end
@@ -179,6 +182,10 @@ class RemoteMirror < ActiveRecord::Base
project.repository.add_remote(remote_name, remote_url)
end
+ def after_sent_notification
+ update_column(:error_notification_sent, true)
+ end
+
private
def store_credentials
@@ -221,7 +228,8 @@ class RemoteMirror < ActiveRecord::Base
last_error: nil,
last_update_at: nil,
last_successful_update_at: nil,
- update_status: 'finished'
+ update_status: 'finished',
+ error_notification_sent: false
)
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index b19ae2e0e6a..b47238b52f1 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1072,19 +1072,11 @@ class Repository
end
def cache
- @cache ||= if is_wiki
- Gitlab::RepositoryCache.new(self, extra_namespace: 'wiki')
- else
- Gitlab::RepositoryCache.new(self)
- end
+ @cache ||= Gitlab::RepositoryCache.new(self)
end
def request_store_cache
- @request_store_cache ||= if is_wiki
- Gitlab::RepositoryCache.new(self, extra_namespace: 'wiki', backend: Gitlab::SafeRequestStore)
- else
- Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
- end
+ @request_store_cache ||= Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
end
def tags_sorted_by_committed_date
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index f9b23bbbf6c..f23ddd64fe3 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -50,11 +50,11 @@ class Snippet < ActiveRecord::Base
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
# Scopes
- scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
+ scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) }
scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
- scope :fresh, -> { order("created_at DESC") }
+ scope :fresh, -> { order("created_at DESC") }
scope :inc_relations_for_view, -> { includes(author: :status) }
participant :author
diff --git a/app/models/ssh_host_key.rb b/app/models/ssh_host_key.rb
index b6844dbe870..99a0c54a26a 100644
--- a/app/models/ssh_host_key.rb
+++ b/app/models/ssh_host_key.rb
@@ -52,6 +52,11 @@ class SshHostKey
@compare_host_keys = compare_host_keys
end
+ # Needed for reactive caching
+ def self.primary_key
+ 'id'
+ end
+
def id
[project.id, url].join(':')
end
diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed_project.rb
index 911fb7e9ce9..f5d0d6fab3b 100644
--- a/app/models/storage/hashed_project.rb
+++ b/app/models/storage/hashed_project.rb
@@ -31,7 +31,7 @@ module Storage
gitlab_shell.add_namespace(repository_storage, base_dir)
end
- def rename_repo
+ def rename_repo(old_full_path: nil, new_full_path: nil)
true
end
diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb
index 9f6f19acb41..76ac5c13c18 100644
--- a/app/models/storage/legacy_project.rb
+++ b/app/models/storage/legacy_project.rb
@@ -29,18 +29,19 @@ module Storage
gitlab_shell.add_namespace(repository_storage, base_dir)
end
- def rename_repo
- new_full_path = project.build_full_path
+ def rename_repo(old_full_path: nil, new_full_path: nil)
+ old_full_path ||= project.full_path_was
+ new_full_path ||= project.build_full_path
- if gitlab_shell.mv_repository(repository_storage, project.full_path_was, new_full_path)
+ if gitlab_shell.mv_repository(repository_storage, old_full_path, new_full_path)
# If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions
begin
- gitlab_shell.mv_repository(repository_storage, "#{project.full_path_was}.wiki", "#{new_full_path}.wiki")
+ gitlab_shell.mv_repository(repository_storage, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
return true
rescue => e
- Rails.logger.error "Exception renaming #{project.full_path_was} -> #{new_full_path}: #{e}"
+ Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}"
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
return false
diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb
index c76b8e71507..7eee4fbbe5f 100644
--- a/app/models/suggestion.rb
+++ b/app/models/suggestion.rb
@@ -5,8 +5,7 @@ class Suggestion < ApplicationRecord
validates :note, presence: true
validates :commit_id, presence: true, if: :applied?
- delegate :original_position, :position, :diff_file,
- :noteable, to: :note
+ delegate :original_position, :position, :noteable, to: :note
def project
noteable.source_project
@@ -16,6 +15,15 @@ class Suggestion < ApplicationRecord
noteable.source_branch
end
+ def file_path
+ position.file_path
+ end
+
+ def diff_file
+ repository = project.repository
+ position.diff_file(repository)
+ end
+
# For now, suggestions only serve as a way to send patches that
# will change a single line (being able to apply multiple in the same place),
# which explains `from_line` and `to_line` being the same line.
diff --git a/app/models/user.rb b/app/models/user.rb
index 26fd2d903a1..4ef5bdc2d12 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -145,7 +145,7 @@ class User < ActiveRecord::Base
has_many :issue_assignees
has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
- has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
+ has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout'
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 0d0f1c28bad..72de04203a6 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -7,6 +7,10 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
+ desc "User has access to all private groups & projects"
+ with_options scope: :user, score: 0
+ condition(:full_private_access) { @user&.full_private_access? }
+
with_options scope: :user, score: 0
condition(:external_user) { @user.nil? || @user.external? }
diff --git a/app/policies/board_policy.rb b/app/policies/board_policy.rb
new file mode 100644
index 00000000000..46db008421f
--- /dev/null
+++ b/app/policies/board_policy.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class BoardPolicy < BasePolicy
+ delegate { @subject.parent }
+
+ condition(:is_group_board) { @subject.group_board? }
+
+ rule { is_group_board ? can?(:read_group) : can?(:read_project) }.enable :read_parent
+
+ rule { is_group_board & can?(:read_group) }.policy do
+ enable :read_milestone
+ enable :read_issue
+ end
+end
diff --git a/app/policies/container_repository_policy.rb b/app/policies/container_repository_policy.rb
new file mode 100644
index 00000000000..6781c845142
--- /dev/null
+++ b/app/policies/container_repository_policy.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class ContainerRepositoryPolicy < BasePolicy
+ delegate { @subject.project }
+end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
index 777f933cdcd..040b5a73415 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -29,4 +29,6 @@ class PersonalSnippetPolicy < BasePolicy
rule { anonymous }.prevent :comment_personal_snippet
rule { can?(:comment_personal_snippet) }.enable :award_emoji
+
+ rule { full_private_access }.enable :read_personal_snippet
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 3146f26bed5..12f9f29dcc1 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -200,6 +200,7 @@ class ProjectPolicy < BasePolicy
enable :read_environment
enable :read_deployment
enable :read_merge_request
+ enable :read_sentry_issue
end
# We define `:public_user_access` separately because there are cases in gitlab-ee
@@ -222,6 +223,8 @@ class ProjectPolicy < BasePolicy
rule { owner | admin | guest | group_member }.prevent :request_access
rule { ~request_access_enabled }.prevent :request_access
+ rule { can?(:developer_access) & can?(:create_issue) }.enable :import_issues
+
rule { can?(:developer_access) }.policy do
enable :admin_merge_request
enable :admin_milestone
diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb
index 288bf070cfc..7dafa33bb99 100644
--- a/app/policies/project_snippet_policy.rb
+++ b/app/policies/project_snippet_policy.rb
@@ -5,13 +5,12 @@ class ProjectSnippetPolicy < BasePolicy
desc "Snippet is public"
condition(:public_snippet, scope: :subject) { @subject.public? }
+ condition(:internal_snippet, scope: :subject) { @subject.internal? }
condition(:private_snippet, scope: :subject) { @subject.private? }
condition(:public_project, scope: :subject) { @subject.project.public? }
condition(:is_author) { @user && @subject.author == @user }
- condition(:internal, scope: :subject) { @subject.internal? }
-
# We have to check both project feature visibility and a snippet visibility and take the stricter one
# This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
rule { ~can?(:read_project) }.policy do
@@ -26,13 +25,13 @@ class ProjectSnippetPolicy < BasePolicy
# is used to hide/show various snippet-related controls, so we can't just move
# all of the handling here.
rule do
- all?(private_snippet | (internal & external_user),
+ all?(private_snippet | (internal_snippet & external_user),
~project.guest,
- ~admin,
- ~is_author)
+ ~is_author,
+ ~full_private_access)
end.prevent :read_project_snippet
- rule { internal & ~is_author & ~admin }.policy do
+ rule { internal_snippet & ~is_author & ~admin }.policy do
prevent :update_project_snippet
prevent :admin_project_snippet
end
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 1db6c9eff36..44b6ca299ae 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -189,6 +189,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
merge_request.subscribed?(current_user, merge_request.target_project)
end
+ def conflicts_docs_path
+ help_page_path('user/project/merge_requests/resolve_conflicts.md')
+ end
+
private
def cached_can_be_reverted?
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 9bd64ea217e..ea1d941cf83 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -13,7 +13,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
presents :project
AnchorData = Struct.new(:is_link, :label, :link, :class_modifier, :icon)
- MAX_TAGS_TO_SHOW = 3
+ MAX_TOPICS_TO_SHOW = 3
def statistic_icon(icon_name = 'plus-square-o')
sprite_icon(icon_name, size: 16, css_class: 'icon append-right-4')
@@ -310,20 +310,20 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
end
- def tags_to_show
- project.tag_list.take(MAX_TAGS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
+ def topics_to_show
+ project.tag_list.take(MAX_TOPICS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
end
- def count_of_extra_tags_not_shown
- if project.tag_list.count > MAX_TAGS_TO_SHOW
- project.tag_list.count - MAX_TAGS_TO_SHOW
+ def count_of_extra_topics_not_shown
+ if project.tag_list.count > MAX_TOPICS_TO_SHOW
+ project.tag_list.count - MAX_TOPICS_TO_SHOW
else
0
end
end
- def has_extra_tags?
- count_of_extra_tags_not_shown > 0
+ def has_extra_topics?
+ count_of_extra_topics_not_shown > 0
end
private
diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb
index 7b65bd22f54..4744a7c1cc8 100644
--- a/app/serializers/base_serializer.rb
+++ b/app/serializers/base_serializer.rb
@@ -16,11 +16,11 @@ class BaseSerializer
.as_json
end
- def self.entity(entity_class)
- @entity_class ||= entity_class
- end
+ class << self
+ attr_reader :entity_class
- def self.entity_class
- @entity_class
+ def entity(entity_class)
+ @entity_class ||= entity_class
+ end
end
end
diff --git a/app/serializers/container_repository_entity.rb b/app/serializers/container_repository_entity.rb
index 59bf35f5aff..cc746698a05 100644
--- a/app/serializers/container_repository_entity.rb
+++ b/app/serializers/container_repository_entity.rb
@@ -3,7 +3,7 @@
class ContainerRepositoryEntity < Grape::Entity
include RequestAwareEntity
- expose :id, :path, :location
+ expose :id, :name, :path, :location, :created_at
expose :tags_path do |repository|
project_registry_repository_tags_path(project, repository, format: :json)
diff --git a/app/serializers/container_tag_entity.rb b/app/serializers/container_tag_entity.rb
index 637294877f8..361c073e22e 100644
--- a/app/serializers/container_tag_entity.rb
+++ b/app/serializers/container_tag_entity.rb
@@ -3,7 +3,7 @@
class ContainerTagEntity < Grape::Entity
include RequestAwareEntity
- expose :name, :location, :revision, :short_revision, :total_size, :created_at
+ expose :name, :path, :location, :digest, :revision, :short_revision, :total_size, :created_at
expose :destroy_path, if: -> (*) { can_destroy? } do |tag|
project_registry_repository_tag_path(project, tag.repository, tag.name)
diff --git a/app/serializers/error_tracking/error_entity.rb b/app/serializers/error_tracking/error_entity.rb
new file mode 100644
index 00000000000..91388e7c3ad
--- /dev/null
+++ b/app/serializers/error_tracking/error_entity.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class ErrorEntity < Grape::Entity
+ expose :id, :title, :type, :user_count, :count,
+ :first_seen, :last_seen, :message, :culprit,
+ :external_url, :project_id, :project_name, :project_slug,
+ :short_id, :status, :frequency
+ end
+end
diff --git a/app/serializers/error_tracking/error_serializer.rb b/app/serializers/error_tracking/error_serializer.rb
new file mode 100644
index 00000000000..ff9a645eb16
--- /dev/null
+++ b/app/serializers/error_tracking/error_serializer.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class ErrorSerializer < BaseSerializer
+ entity ErrorEntity
+ end
+end
diff --git a/app/serializers/merge_request_diff_entity.rb b/app/serializers/merge_request_diff_entity.rb
index 433bfe60474..7e3053e5881 100644
--- a/app/serializers/merge_request_diff_entity.rb
+++ b/app/serializers/merge_request_diff_entity.rb
@@ -24,6 +24,14 @@ class MergeRequestDiffEntity < Grape::Entity
short_sha(merge_request_diff.head_commit_sha)
end
+ expose :base_version_path do |merge_request_diff|
+ project = merge_request.target_project
+
+ next unless project
+
+ merge_request_version_path(project, merge_request, merge_request_diff)
+ end
+
expose :version_path do |merge_request_diff|
start_sha = options[:start_sha]
project = merge_request.target_project
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 9731b52f1ad..9361c9f987b 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -11,6 +11,9 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_user_id
expose :merge_when_pipeline_succeeds
expose :source_branch
+ expose :source_branch_protected do |merge_request|
+ merge_request.source_project.present? && ProtectedBranch.protected?(merge_request.source_project, merge_request.source_branch)
+ end
expose :source_project_id
expose :source_project_full_path do |merge_request|
merge_request.source_project&.full_path
@@ -240,6 +243,10 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :supports_suggestion?, as: :can_receive_suggestion
+ expose :conflicts_docs_path do |merge_request|
+ presenter(merge_request).conflicts_docs_path
+ end
+
private
delegate :current_user, to: :request
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index c9669e59199..29b1a6c244b 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -59,7 +59,7 @@ class PipelineEntity < Grape::Entity
pipeline.present.failure_reason
end
- expose :retry_path, if: -> (*) { can_retry? } do |pipeline|
+ expose :retry_path, if: -> (*) { can_retry? } do |pipeline|
retry_project_pipeline_path(pipeline.project, pipeline)
end
diff --git a/app/serializers/projects/serverless/service_entity.rb b/app/serializers/projects/serverless/service_entity.rb
index 4f1f62d145b..c98dc1a1c4a 100644
--- a/app/serializers/projects/serverless/service_entity.rb
+++ b/app/serializers/projects/serverless/service_entity.rb
@@ -13,6 +13,25 @@ module Projects
service.dig('metadata', 'namespace')
end
+ expose :environment_scope do |service|
+ service.dig('environment_scope')
+ end
+
+ expose :cluster_id do |service|
+ service.dig('cluster_id')
+ end
+
+ expose :detail_url do |service|
+ project_serverless_path(
+ request.project,
+ service.dig('environment_scope'),
+ service.dig('metadata', 'name'))
+ end
+
+ expose :podcount do |service|
+ service.dig('podcount')
+ end
+
expose :created_at do |service|
service.dig('metadata', 'creationTimestamp')
end
@@ -22,11 +41,24 @@ module Projects
end
expose :description do |service|
- service.dig('spec', 'runLatest', 'configuration', 'revisionTemplate', 'metadata', 'annotations', 'Description')
+ service.dig(
+ 'spec',
+ 'runLatest',
+ 'configuration',
+ 'revisionTemplate',
+ 'metadata',
+ 'annotations',
+ 'Description')
end
expose :image do |service|
- service.dig('spec', 'runLatest', 'configuration', 'build', 'template', 'name')
+ service.dig(
+ 'spec',
+ 'runLatest',
+ 'configuration',
+ 'build',
+ 'template',
+ 'name')
end
end
end
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 43a26f4264e..834baeb9643 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -50,7 +50,7 @@ module Boards
if move_between_ids
attrs[:move_between_ids] = move_between_ids
- attrs[:board_group_id] = board.group&.id
+ attrs[:board_group_id] = board.group&.id
end
attrs
diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb
index 609c430caed..e20805d0405 100644
--- a/app/services/boards/lists/destroy_service.rb
+++ b/app/services/boards/lists/destroy_service.rb
@@ -20,7 +20,7 @@ module Boards
# rubocop: disable CodeReuse/ActiveRecord
def decrement_higher_lists(list)
- board.lists.movable.where('position > ?', list.position)
+ board.lists.movable.where('position > ?', list.position)
.update_all('position = position - 1')
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/ci/destroy_expired_job_artifacts_service.rb b/app/services/ci/destroy_expired_job_artifacts_service.rb
new file mode 100644
index 00000000000..7d2f5d33fed
--- /dev/null
+++ b/app/services/ci/destroy_expired_job_artifacts_service.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Ci
+ class DestroyExpiredJobArtifactsService
+ include ::Gitlab::ExclusiveLeaseHelpers
+ include ::Gitlab::LoopHelpers
+
+ BATCH_SIZE = 100
+ LOOP_TIMEOUT = 45.minutes
+ LOOP_LIMIT = 1000
+ EXCLUSIVE_LOCK_KEY = 'expired_job_artifacts:destroy:lock'
+ LOCK_TIMEOUT = 50.minutes
+
+ ##
+ # Destroy expired job artifacts on GitLab instance
+ #
+ # This destroy process cannot run for more than 45 minutes. This is for
+ # preventing multiple `ExpireBuildArtifactsWorker` CRON jobs run concurrently,
+ # which is scheduled at every hour.
+ def execute
+ in_lock(EXCLUSIVE_LOCK_KEY, ttl: LOCK_TIMEOUT, retries: 1) do
+ loop_until(timeout: LOOP_TIMEOUT, limit: LOOP_LIMIT) do
+ destroy_batch
+ end
+ end
+ end
+
+ private
+
+ def destroy_batch
+ artifacts = Ci::JobArtifact.expired(BATCH_SIZE).to_a
+
+ return false if artifacts.empty?
+
+ artifacts.each(&:destroy!)
+ end
+ end
+end
diff --git a/app/services/ci/destroy_pipeline_service.rb b/app/services/ci/destroy_pipeline_service.rb
index 13f892aabb8..5c4a34043c1 100644
--- a/app/services/ci/destroy_pipeline_service.rb
+++ b/app/services/ci/destroy_pipeline_service.rb
@@ -5,8 +5,6 @@ module Ci
def execute(pipeline)
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_pipeline, pipeline)
- AuditEventService.new(current_user, pipeline).security_event
-
pipeline.destroy!
end
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 446188347df..4a7ce00b8e2 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -10,7 +10,7 @@ module Ci
update_retried
new_builds =
- stage_indexes_of_created_builds.map do |index|
+ stage_indexes_of_created_processables.map do |index|
process_stage(index)
end
@@ -27,7 +27,7 @@ module Ci
return if HasStatus::BLOCKED_STATUS.include?(current_status)
if HasStatus::COMPLETED_STATUSES.include?(current_status)
- created_builds_in_stage(index).select do |build|
+ created_processables_in_stage(index).select do |build|
Gitlab::OptimisticLocking.retry_lock(build) do |subject|
Ci::ProcessBuildService.new(project, @user)
.execute(build, current_status)
@@ -43,19 +43,19 @@ module Ci
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
- def stage_indexes_of_created_builds
- created_builds.order(:stage_idx).pluck('distinct stage_idx')
+ def stage_indexes_of_created_processables
+ created_processables.order(:stage_idx).pluck('distinct stage_idx')
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
- def created_builds_in_stage(index)
- created_builds.where(stage_idx: index)
+ def created_processables_in_stage(index)
+ created_processables.where(stage_idx: index)
end
# rubocop: enable CodeReuse/ActiveRecord
- def created_builds
- pipeline.builds.created
+ def created_processables
+ pipeline.processables.created
end
# This method is for compatibility and data consistency and should be removed with 9.3 version of GitLab
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index 218f1e63d08..fab8a179843 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -2,7 +2,7 @@
module Ci
class RetryBuildService < ::BaseService
- CLONE_ACCESSORS = %i[pipeline project ref tag options commands name
+ CLONE_ACCESSORS = %i[pipeline project ref tag options name
allow_failure stage stage_id stage_idx trigger_request
yaml_variables when environment coverage_regex
description tag_list protected].freeze
diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb
index e86ca8cf1d0..8a71730d5ec 100644
--- a/app/services/clusters/applications/base_helm_service.rb
+++ b/app/services/clusters/applications/base_helm_service.rb
@@ -45,6 +45,10 @@ module Clusters
def install_command
@install_command ||= app.install_command
end
+
+ def upgrade_command(new_values = "")
+ app.upgrade_command(new_values)
+ end
end
end
end
diff --git a/app/services/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb
index f102e00d150..28879d2d67f 100644
--- a/app/services/concerns/exclusive_lease_guard.rb
+++ b/app/services/concerns/exclusive_lease_guard.rb
@@ -6,9 +6,14 @@
#
# `#try_obtain_lease` takes a block which will be run if it was able to
# obtain the lease. Implement `#lease_timeout` to configure the timeout
-# for the exclusive lease. Optionally override `#lease_key` to set the
+# for the exclusive lease.
+#
+# Optionally override `#lease_key` to set the
# lease key, it defaults to the class name with underscores.
#
+# Optionally override `#lease_release?` to prevent the job to
+# be re-executed more often than LEASE_TIMEOUT.
+#
module ExclusiveLeaseGuard
extend ActiveSupport::Concern
@@ -23,7 +28,7 @@ module ExclusiveLeaseGuard
begin
yield lease
ensure
- release_lease(lease)
+ release_lease(lease) if lease_release?
end
end
@@ -40,6 +45,10 @@ module ExclusiveLeaseGuard
"#{self.class.name} does not implement #{__method__}"
end
+ def lease_release?
+ true
+ end
+
def release_lease(uuid)
Gitlab::ExclusiveLease.cancel(lease_key, uuid)
end
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index 44252f7b0a6..8322a3d74f4 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -14,7 +14,7 @@ class DeleteBranchService < BaseService
end
if repository.rm_branch(current_user, branch_name)
- success('Branch was removed')
+ success('Branch was deleted')
else
error('Failed to remove branch')
end
diff --git a/app/services/error_tracking/list_issues_service.rb b/app/services/error_tracking/list_issues_service.rb
new file mode 100644
index 00000000000..4cc35cfa4a8
--- /dev/null
+++ b/app/services/error_tracking/list_issues_service.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module ErrorTracking
+ class ListIssuesService < ::BaseService
+ DEFAULT_ISSUE_STATUS = 'unresolved'
+ DEFAULT_LIMIT = 20
+
+ def execute
+ return error('not enabled') unless enabled?
+ return error('access denied') unless can_read?
+
+ result = project_error_tracking_setting
+ .list_sentry_issues(issue_status: issue_status, limit: limit)
+
+ # our results are not yet ready
+ unless result
+ return error('not ready', :no_content)
+ end
+
+ success(issues: result[:issues])
+ end
+
+ def external_url
+ project_error_tracking_setting&.sentry_external_url
+ end
+
+ private
+
+ def project_error_tracking_setting
+ project.error_tracking_setting
+ end
+
+ def issue_status
+ params[:issue_status] || DEFAULT_ISSUE_STATUS
+ end
+
+ def limit
+ params[:limit] || DEFAULT_LIMIT
+ end
+
+ def enabled?
+ project_error_tracking_setting&.enabled?
+ end
+
+ def can_read?
+ can?(current_user, :read_sentry_issue, project)
+ end
+ end
+end
diff --git a/app/services/import/base_service.rb b/app/services/import/base_service.rb
new file mode 100644
index 00000000000..2683c75e41f
--- /dev/null
+++ b/app/services/import/base_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Import
+ class BaseService < ::BaseService
+ def initialize(client, user, params)
+ @client = client
+ @current_user = user
+ @params = params
+ end
+
+ private
+
+ def find_or_create_namespace(namespace, owner)
+ namespace = params[:target_namespace].presence || namespace
+
+ return current_user.namespace if namespace == owner
+
+ group = Groups::NestedCreateService.new(current_user, group_path: namespace).execute
+
+ group.errors.any? ? current_user.namespace : group
+ rescue => e
+ Gitlab::AppLogger.error(e)
+
+ current_user.namespace
+ end
+
+ def project_save_error(project)
+ project.errors.full_messages.join(', ')
+ end
+
+ def success(project)
+ super().merge(project: project, status: :success)
+ end
+ end
+end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
new file mode 100644
index 00000000000..a2533683da9
--- /dev/null
+++ b/app/services/import/github_service.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Import
+ class GithubService < Import::BaseService
+ attr_accessor :client
+ attr_reader :params, :current_user
+
+ def execute(access_params, provider)
+ unless authorized?
+ return error('This namespace has already been taken! Please choose another one.', :unprocessable_entity)
+ end
+
+ project = Gitlab::LegacyGithubImport::ProjectCreator
+ .new(repo, project_name, target_namespace, current_user, access_params, type: provider)
+ .execute(extra_project_attrs)
+
+ if project.persisted?
+ success(project)
+ else
+ error(project_save_error(project), :unprocessable_entity)
+ end
+ end
+
+ def repo
+ @repo ||= client.repo(params[:repo_id].to_i)
+ end
+
+ def project_name
+ @project_name ||= params[:new_name].presence || repo.name
+ end
+
+ def namespace_path
+ @namespace_path ||= params[:target_namespace].presence || current_user.namespace_path
+ end
+
+ def target_namespace
+ @target_namespace ||= find_or_create_namespace(namespace_path, current_user.namespace_path)
+ end
+
+ def extra_project_attrs
+ {}
+ end
+
+ def authorized?
+ can?(current_user, :create_projects, target_namespace)
+ end
+ end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index c7e7bb55e4b..805bb5b510d 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -61,10 +61,10 @@ class IssuableBaseService < BaseService
return unless milestone_id
params[:milestone_id] = '' if milestone_id == IssuableFinder::NONE
- group_ids = project.group&.self_and_ancestors&.pluck(:id)
+ groups = project.group&.self_and_ancestors&.select(:id)
milestone =
- Milestone.for_projects_and_groups([project.id], group_ids).find_by_id(milestone_id)
+ Milestone.for_projects_and_groups([project.id], groups).find_by_id(milestone_id)
params[:milestone_id] = '' unless milestone
end
diff --git a/app/services/issues/import_csv_service.rb b/app/services/issues/import_csv_service.rb
new file mode 100644
index 00000000000..ef08fafa7cc
--- /dev/null
+++ b/app/services/issues/import_csv_service.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Issues
+ class ImportCsvService
+ def initialize(user, project, csv_io)
+ @user = user
+ @project = project
+ @csv_io = csv_io
+ @results = { success: 0, error_lines: [], parse_error: false }
+ end
+
+ def execute
+ process_csv
+ email_results_to_user
+
+ @results
+ end
+
+ private
+
+ def process_csv
+ csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8)
+
+ CSV.new(csv_data, col_sep: detect_col_sep(csv_data.lines.first), headers: true).each.with_index(2) do |row, line_no|
+ issue = Issues::CreateService.new(@project, @user, title: row[0], description: row[1]).execute
+
+ if issue.persisted?
+ @results[:success] += 1
+ else
+ @results[:error_lines].push(line_no)
+ end
+ end
+ rescue ArgumentError, CSV::MalformedCSVError
+ @results[:parse_error] = true
+ end
+
+ def email_results_to_user
+ Notify.import_issues_csv_email(@user.id, @project.id, @results).deliver_now
+ end
+
+ def detect_col_sep(header)
+ if header.include?(",")
+ ","
+ elsif header.include?(";")
+ ";"
+ elsif header.include?("\t")
+ "\t"
+ else
+ raise CSV::MalformedCSVError
+ end
+ end
+ end
+end
diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb
index fe34be41ac1..db710bac900 100644
--- a/app/services/labels/create_service.rb
+++ b/app/services/labels/create_service.rb
@@ -3,7 +3,7 @@
module Labels
class CreateService < Labels::BaseService
def initialize(params = {})
- @params = params.dup.with_indifferent_access
+ @params = params.to_h.dup.with_indifferent_access
end
# returns the created label
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index 3c0e6196d4f..e73e6476c12 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -49,7 +49,7 @@ module Labels
.new(current_user, title: new_label.title, group_id: project.group.id)
.execute(skip_authorization: true)
.where.not(id: new_label)
- .select(:id) # Can't use pluck() to avoid object-creation because of the batching
+ .select(:id) # Can't use pluck() to avoid object-creation because of the batching
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/labels/update_service.rb b/app/services/labels/update_service.rb
index c3a720a1c66..e563447c64c 100644
--- a/app/services/labels/update_service.rb
+++ b/app/services/labels/update_service.rb
@@ -3,7 +3,7 @@
module Labels
class UpdateService < Labels::BaseService
def initialize(params = {})
- @params = params.dup.with_indifferent_access
+ @params = params.to_h.dup.with_indifferent_access
end
# returns the updated label
diff --git a/app/services/lfs/locks_finder_service.rb b/app/services/lfs/locks_finder_service.rb
index 4a5b2a52921..192ce3d3c2a 100644
--- a/app/services/lfs/locks_finder_service.rb
+++ b/app/services/lfs/locks_finder_service.rb
@@ -12,7 +12,7 @@ module Lfs
# rubocop: disable CodeReuse/ActiveRecord
def find_locks
- options = params.slice(:id, :path).compact.symbolize_keys
+ options = params.slice(:id, :path).to_h.compact.symbolize_keys
project.lfs_file_locks.where(options)
end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 7bb9fa60515..02c2388c05c 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -26,7 +26,7 @@ module MergeRequests
todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user)
create_merge_request_pipeline(issuable, current_user)
- update_merge_requests_head_pipeline(issuable)
+ issuable.update_head_pipeline
super
end
@@ -45,20 +45,6 @@ module MergeRequests
private
- def update_merge_requests_head_pipeline(merge_request)
- pipeline = head_pipeline_for(merge_request)
- merge_request.update(head_pipeline_id: pipeline.id) if pipeline
- end
-
- def head_pipeline_for(merge_request)
- return unless merge_request.source_project
-
- sha = merge_request.source_branch_sha
- return unless sha
-
- merge_request.all_pipelines(shas: sha).first
- end
-
def set_projects!
# @project is used to determine whether the user can set the merge request's
# assignee, milestone and labels. Whether they can depends on their
diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb
index 39071b5dc14..cbe5996e8ca 100644
--- a/app/services/milestones/promote_service.rb
+++ b/app/services/milestones/promote_service.rb
@@ -82,11 +82,9 @@ module Milestones
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def group_project_ids
- @group_project_ids ||= group.projects.pluck(:id)
+ group.projects.select(:id)
end
- # rubocop: enable CodeReuse/ActiveRecord
def raise_error(message)
raise PromoteMilestoneError, "Promotion failed - #{message}"
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index 4c14d834949..7ee9732040d 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -31,7 +31,7 @@ module Notes
return if command_params.empty?
return unless supported?(note)
- self.class.noteable_update_service(note).new(project, current_user, command_params).execute(note.noteable)
+ self.class.noteable_update_service(note).new(note.parent, current_user, command_params).execute(note.noteable)
end
end
end
diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb
index aa9b253eb20..c3cd9d1ea4a 100644
--- a/app/services/projects/after_rename_service.rb
+++ b/app/services/projects/after_rename_service.rb
@@ -12,22 +12,27 @@ module Projects
#
# Projects::AfterRenameService.new(project).execute
class AfterRenameService
- attr_reader :project, :full_path_before, :full_path_after, :path_before
+ # @return [String] The Project being renamed.
+ attr_reader :project
- RenameFailedError = Class.new(StandardError)
+ # @return [String] The path slug the project was using, before the rename took place.
+ attr_reader :path_before
- # @param [Project] project The Project of the repository to rename.
- def initialize(project)
- @project = project
+ # @return [String] The full path of the namespace + project, before the rename took place.
+ attr_reader :full_path_before
- # The full path of the namespace + project, before the rename took place.
- @full_path_before = project.full_path_was
+ # @return [String] The full path of the namespace + project, after the rename took place.
+ attr_reader :full_path_after
- # The full path of the namespace + project, after the rename took place.
- @full_path_after = project.build_full_path
+ RenameFailedError = Class.new(StandardError)
- # The path of just the project, before the rename took place.
- @path_before = project.path_was
+ # @param [Project] project The Project being renamed.
+ # @param [String] path_before The path slug the project was using, before the rename took place.
+ def initialize(project, path_before:, full_path_before:)
+ @project = project
+ @path_before = path_before
+ @full_path_before = full_path_before
+ @full_path_after = project.full_path
end
def execute
@@ -61,7 +66,7 @@ module Projects
.new(project, full_path_before)
.execute
else
- project.storage.rename_repo
+ project.storage.rename_repo(old_full_path: full_path_before, new_full_path: full_path_after)
end
rename_failed! unless success
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index 61f6402a810..3dad90188cf 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -14,7 +14,7 @@ module Projects
order: { due_date: :asc, title: :asc }
}
- finder_params[:group_ids] = @project.group.self_and_ancestors_ids if @project.group
+ finder_params[:group_ids] = @project.group.self_and_ancestors.select(:id) if @project.group
MilestonesFinder.new(finder_params).execute.select([:iid, :title])
end
diff --git a/app/services/projects/container_repository/cleanup_tags_service.rb b/app/services/projects/container_repository/cleanup_tags_service.rb
new file mode 100644
index 00000000000..488290db824
--- /dev/null
+++ b/app/services/projects/container_repository/cleanup_tags_service.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+module Projects
+ module ContainerRepository
+ class CleanupTagsService < BaseService
+ def execute(container_repository)
+ return error('feature disabled') unless can_use?
+ return error('access denied') unless can_admin?
+
+ tags = container_repository.tags
+ tags_by_digest = group_by_digest(tags)
+
+ tags = without_latest(tags)
+ tags = filter_by_name(tags)
+ tags = with_manifest(tags)
+ tags = order_by_date(tags)
+ tags = filter_keep_n(tags)
+ tags = filter_by_older_than(tags)
+
+ deleted_tags = delete_tags(tags, tags_by_digest)
+
+ success(deleted: deleted_tags.map(&:name))
+ end
+
+ private
+
+ def delete_tags(tags_to_delete, tags_by_digest)
+ deleted_digests = group_by_digest(tags_to_delete).select do |digest, tags|
+ delete_tag_digest(digest, tags, tags_by_digest[digest])
+ end
+
+ deleted_digests.values.flatten
+ end
+
+ def delete_tag_digest(digest, tags, other_tags)
+ # Issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/21405
+ # we have to remove all tags due
+ # to Docker Distribution bug unable
+ # to delete single tag
+ return unless tags.count == other_tags.count
+
+ # delete all tags
+ tags.map(&:delete)
+ end
+
+ def group_by_digest(tags)
+ tags.group_by(&:digest)
+ end
+
+ def without_latest(tags)
+ tags.reject(&:latest?)
+ end
+
+ def with_manifest(tags)
+ tags.select(&:valid?)
+ end
+
+ def order_by_date(tags)
+ now = DateTime.now
+ tags.sort_by { |tag| tag.created_at || now }.reverse
+ end
+
+ def filter_by_name(tags)
+ regex = Gitlab::UntrustedRegexp.new("\\A#{params['name_regex']}\\z")
+
+ tags.select do |tag|
+ regex.scan(tag.name).any?
+ end
+ end
+
+ def filter_keep_n(tags)
+ tags.drop(params['keep_n'].to_i)
+ end
+
+ def filter_by_older_than(tags)
+ return tags unless params['older_than']
+
+ older_than = ChronicDuration.parse(params['older_than']).seconds.ago
+
+ tags.select do |tag|
+ tag.created_at && tag.created_at < older_than
+ end
+ end
+
+ def can_admin?
+ can?(current_user, :admin_container_image, project)
+ end
+
+ def can_use?
+ Feature.enabled?(:container_registry_cleanup, project, default_enabled: true)
+ end
+ end
+ end
+end
diff --git a/app/services/projects/create_from_template_service.rb b/app/services/projects/create_from_template_service.rb
index 8306d43ca7c..678bc0d24c3 100644
--- a/app/services/projects/create_from_template_service.rb
+++ b/app/services/projects/create_from_template_service.rb
@@ -5,7 +5,7 @@ module Projects
include Gitlab::Utils::StrongMemoize
def initialize(user, params)
- @current_user, @params = user, params.dup
+ @current_user, @params = user, params.to_h.dup
end
def execute
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 336d029d330..b14b31302f5 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -7,9 +7,16 @@ module Projects
DestroyError = Class.new(StandardError)
DELETED_FLAG = '+deleted'.freeze
+ REPO_REMOVAL_DELAY = 5.minutes.to_i
def async_execute
project.update_attribute(:pending_delete, true)
+
+ # Ensure no repository +deleted paths are kept,
+ # regardless of any issue with the ProjectDestroyWorker
+ # job process.
+ schedule_stale_repos_removal
+
job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params)
Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}")
end
@@ -92,14 +99,23 @@ module Projects
log_info(%Q{Repository "#{path}" moved to "#{new_path}" for project "#{project.full_path}"})
project.run_after_commit do
- # self is now project
- GitlabShellWorker.perform_in(5.minutes, :remove_repository, self.repository_storage, new_path)
+ GitlabShellWorker.perform_in(REPO_REMOVAL_DELAY, :remove_repository, self.repository_storage, new_path)
end
else
false
end
end
+ def schedule_stale_repos_removal
+ repo_paths = [removal_path(repo_path), removal_path(wiki_path)]
+
+ # Ideally it should wait until the regular removal phase finishes,
+ # so let's delay it a bit further.
+ repo_paths.each do |path|
+ GitlabShellWorker.perform_in(REPO_REMOVAL_DELAY * 2, :remove_repository, project.repository_storage, path)
+ end
+ end
+
def rollback_repository(old_path, new_path)
# There is a possibility project does not have repository or wiki
return true unless repo_exists?(old_path)
@@ -113,13 +129,11 @@ module Projects
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def mv_repository(from_path, to_path)
- return true unless gitlab_shell.exists?(project.repository_storage, from_path + '.git')
+ return true unless repo_exists?(from_path)
gitlab_shell.mv_repository(project.repository_storage, from_path, to_path)
end
- # rubocop: enable CodeReuse/ActiveRecord
def attempt_rollback(project, message)
return unless project
diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb
index f3e026ba38c..2d851866a18 100644
--- a/app/services/projects/hashed_storage/migrate_repository_service.rb
+++ b/app/services/projects/hashed_storage/migrate_repository_service.rb
@@ -2,6 +2,8 @@
module Projects
module HashedStorage
+ RepositoryMigrationError = Class.new(StandardError)
+
class MigrateRepositoryService < BaseService
include Gitlab::ShellAdapter
@@ -16,6 +18,8 @@ module Projects
end
def execute
+ try_to_set_repository_read_only!
+
@old_storage_version = project.storage_version
project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
project.ensure_storage_path_exists
@@ -48,6 +52,16 @@ module Projects
private
+ def try_to_set_repository_read_only!
+ # Mitigate any push operation to start during migration
+ unless project.set_repository_read_only!
+ migration_error = "Target repository '#{old_disk_path}' cannot be made read-only as there is a git transfer in progress"
+ logger.error migration_error
+
+ raise RepositoryMigrationError, migration_error
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def has_wiki?
gitlab_shell.exists?(project.repository_storage, "#{old_wiki_disk_path}.git")
diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb
new file mode 100644
index 00000000000..abd6d8de750
--- /dev/null
+++ b/app/services/projects/operations/update_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Projects
+ module Operations
+ class UpdateService < BaseService
+ def execute
+ Projects::UpdateService
+ .new(project, current_user, project_update_params)
+ .execute
+ end
+
+ private
+
+ def project_update_params
+ params.slice(:error_tracking_setting_attributes)
+ end
+ end
+ end
+end
diff --git a/app/services/projects/protect_default_branch_service.rb b/app/services/projects/protect_default_branch_service.rb
new file mode 100644
index 00000000000..245490791bf
--- /dev/null
+++ b/app/services/projects/protect_default_branch_service.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Projects
+ # Service class that can be used to execute actions necessary after creating a
+ # default branch.
+ class ProtectDefaultBranchService
+ attr_reader :project, :default_branch_protection
+
+ # @param [Project] project
+ def initialize(project)
+ @project = project
+
+ @default_branch_protection = Gitlab::Access::BranchProtection
+ .new(Gitlab::CurrentSettings.default_branch_protection)
+ end
+
+ def execute
+ protect_default_branch if default_branch
+ end
+
+ def protect_default_branch
+ # Ensure HEAD points to the default branch in case it is not master
+ project.change_head(default_branch)
+
+ create_protected_branch if protect_branch?
+ end
+
+ def create_protected_branch
+ params = {
+ name: default_branch,
+ push_access_levels_attributes: [{ access_level: push_access_level }],
+ merge_access_levels_attributes: [{ access_level: merge_access_level }]
+ }
+
+ # The creator of the project is always allowed to create protected
+ # branches, so we skip the authorization check in this service class.
+ ProtectedBranches::CreateService
+ .new(project, project.creator, params)
+ .execute(skip_authorization: true)
+ end
+
+ def protect_branch?
+ default_branch_protection.any? &&
+ !ProtectedBranch.protected?(project, default_branch)
+ end
+
+ def default_branch
+ project.default_branch
+ end
+
+ def push_access_level
+ if default_branch_protection.developer_can_push?
+ Gitlab::Access::DEVELOPER
+ else
+ Gitlab::Access::MAINTAINER
+ end
+ end
+
+ def merge_access_level
+ if default_branch_protection.developer_can_merge?
+ Gitlab::Access::DEVELOPER
+ else
+ Gitlab::Access::MAINTAINER
+ end
+ end
+ end
+end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index dd1b9680ece..6856009b395 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -67,7 +67,7 @@ module Projects
end
if project.previous_changes.include?('path')
- AfterRenameService.new(project).execute
+ after_rename_service(project).execute
else
system_hook_service.execute_hooks_for(project, :update)
end
@@ -75,6 +75,13 @@ module Projects
update_pages_config if changing_pages_related_config?
end
+ def after_rename_service(project)
+ # The path slug the project was using, before the rename took place.
+ path_before = project.previous_changes['path'].first
+
+ AfterRenameService.new(project, path_before: path_before, full_path_before: project.full_path_was)
+ end
+
def changing_pages_related_config?
changing_pages_https_only? || changing_pages_access_level?
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index d248b10f41e..5c58caee8cd 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -2,6 +2,7 @@
module QuickActions
class InterpretService < BaseService
+ include Gitlab::Utils::StrongMemoize
include Gitlab::QuickActions::Dsl
attr_reader :issuable
@@ -210,15 +211,9 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
- if project
- available_labels = LabelsFinder
- .new(current_user, project_id: project.id, include_ancestor_groups: true)
- .execute
- end
-
- project &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
- available_labels.any?
+ parent &&
+ current_user.can?(:"admin_#{issuable.to_ability_name}", parent) &&
+ find_labels.any?
end
command :label do |labels_param|
label_ids = find_label_ids(labels_param)
@@ -245,7 +240,7 @@ module QuickActions
issuable.is_a?(Issuable) &&
issuable.persisted? &&
issuable.labels.any? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
+ current_user.can?(:"admin_#{issuable.to_ability_name}", parent)
end
command :unlabel do |labels_param = nil|
if labels_param.present?
@@ -674,9 +669,25 @@ module QuickActions
MilestonesFinder.new(params.merge(project_ids: [project.id], group_ids: [project.group&.id])).execute
end
- def find_labels(labels_param)
- extract_references(labels_param, :label) |
- LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split, include_ancestor_groups: true).execute
+ def parent
+ project || group
+ end
+
+ def group
+ strong_memoize(:group) do
+ issuable.group if issuable.respond_to?(:group)
+ end
+ end
+
+ def find_labels(labels_params = nil)
+ finder_params = { include_ancestor_groups: true }
+ finder_params[:project_id] = project.id if project
+ finder_params[:group_id] = group.id if group
+ finder_params[:name] = labels_params.split if labels_params
+
+ result = LabelsFinder.new(current_user, finder_params).execute
+
+ extract_references(labels_params, :label) | result
end
def find_label_references(labels_param)
@@ -707,9 +718,11 @@ module QuickActions
# rubocop: disable CodeReuse/ActiveRecord
def extract_references(arg, type)
+ return [] unless arg
+
ext = Gitlab::ReferenceExtractor.new(project, current_user)
- ext.analyze(arg, author: current_user)
+ ext.analyze(arg, author: current_user, group: group)
ext.references(type)
end
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index 73fcebf79af..c6e143d440d 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -43,11 +43,12 @@ module Releases
description: description,
author: current_user,
tag: tag.name,
- sha: tag.dereferenced_target.sha
+ sha: tag.dereferenced_target.sha,
+ links_attributes: params.dig(:assets, 'links') || []
)
success(tag: tag, release: release)
- rescue ActiveRecord::RecordInvalid => e
+ rescue => e
error(e.message, 400)
end
end
diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb
index d931d528c86..cc47b46b527 100644
--- a/app/services/suggestions/apply_service.rb
+++ b/app/services/suggestions/apply_service.rb
@@ -11,6 +11,10 @@ module Suggestions
return error('Suggestion is not appliable')
end
+ unless latest_diff_refs?(suggestion)
+ return error('The file has been changed')
+ end
+
params = file_update_params(suggestion)
result = ::Files::UpdateService.new(suggestion.project, @current_user, params).execute
@@ -19,30 +23,44 @@ module Suggestions
end
result
+ rescue Files::UpdateService::FileChangedError
+ error('The file has been changed')
end
private
- def file_update_params(suggestion)
- diff_file = suggestion.diff_file
+ # Checks whether the latest diff refs for the branch matches with
+ # the position refs we're using to update the file content. Since
+ # the persisted refs are updated async (for MergeRequest),
+ # it's more consistent to fetch this data directly from the repository.
+ def latest_diff_refs?(suggestion)
+ suggestion.position.diff_refs == suggestion.noteable.repository_diff_refs
+ end
- file_path = diff_file.file_path
- branch_name = suggestion.noteable.source_branch
- file_content = new_file_content(suggestion)
+ def file_update_params(suggestion)
+ blob = suggestion.diff_file.new_blob
+ file_path = suggestion.file_path
+ branch_name = suggestion.branch
+ file_content = new_file_content(suggestion, blob)
commit_message = "Apply suggestion to #{file_path}"
+ file_last_commit =
+ Gitlab::Git::Commit.last_for_path(suggestion.project.repository,
+ blob.commit_id,
+ blob.path)
+
{
file_path: file_path,
branch_name: branch_name,
start_branch: branch_name,
commit_message: commit_message,
- file_content: file_content
+ file_content: file_content,
+ last_commit_sha: file_last_commit&.id
}
end
- def new_file_content(suggestion)
+ def new_file_content(suggestion, blob)
range = suggestion.from_line_index..suggestion.to_line_index
- blob = suggestion.diff_file.new_blob
blob.load_all_data!
content = blob.data.lines
diff --git a/app/services/upload_service.rb b/app/services/upload_service.rb
index 39909ee4f82..41ca95b3b6f 100644
--- a/app/services/upload_service.rb
+++ b/app/services/upload_service.rb
@@ -11,7 +11,7 @@ class UploadService
uploader = @uploader_class.new(@model, nil, @uploader_context)
uploader.store!(@file)
- uploader.to_h
+ uploader
end
private
diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb
index af4fe1aebb9..0b00bd135eb 100644
--- a/app/services/users/update_service.rb
+++ b/app/services/users/update_service.rb
@@ -55,7 +55,7 @@ module Users
params.reject! { |key, _| read_only.include?(key.to_sym) }
end
- @user.assign_attributes(params) if params.any?
+ @user.assign_attributes(params) unless params.empty?
end
end
end
diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb
index 25474b494ff..272837aa6ce 100644
--- a/app/uploaders/personal_file_uploader.rb
+++ b/app/uploaders/personal_file_uploader.rb
@@ -6,8 +6,15 @@ class PersonalFileUploader < FileUploader
options.storage_path
end
- def self.base_dir(model, _store = nil)
- File.join(options.base_dir, model_path_segment(model))
+ def self.base_dir(model, store = nil)
+ base_dirs(model)[store || Store::LOCAL]
+ end
+
+ def self.base_dirs(model)
+ {
+ Store::LOCAL => File.join(options.base_dir, model_path_segment(model)),
+ Store::REMOTE => model_path_segment(model)
+ }
end
def self.model_path_segment(model)
@@ -33,13 +40,6 @@ class PersonalFileUploader < FileUploader
store_dirs[object_store]
end
- def store_dirs
- {
- Store::LOCAL => File.join(base_dir, dynamic_segment),
- Store::REMOTE => File.join(self.class.model_path_segment(model), dynamic_segment)
- }
- end
-
private
def secure_url
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index 0efca895a50..9a243e07936 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -23,13 +23,23 @@ module RecordsUploads
return unless model
return unless file && file.exists?
- Upload.transaction do
- uploads.where(path: upload_path).delete_all
- upload.delete if upload
-
- self.upload = build_upload.tap(&:save!)
+ # MySQL InnoDB may encounter a deadlock if a deletion and an
+ # insert is in the same transaction due to its next-key locking
+ # algorithm, so we need to skip the transaction.
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/55161#note_131556351
+ if Gitlab::Database.mysql?
+ readd_upload
+ else
+ Upload.transaction { readd_upload }
end
end
+
+ def readd_upload
+ uploads.where(path: upload_path).delete_all
+ upload.delete if upload
+
+ self.upload = build_upload.tap(&:save!)
+ end
# rubocop: enable CodeReuse/ActiveRecord
def upload_path
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
index 5feb0b0f05b..d3e32818dc7 100644
--- a/app/validators/url_validator.rb
+++ b/app/validators/url_validator.rb
@@ -26,6 +26,7 @@
# - allow_local_network: Allow urls pointing to private network addresses. Default: true
# - ports: Allowed ports. Default: all.
# - enforce_user: Validate user format. Default: false
+# - enforce_sanitization: Validate that there are no html/css/js tags. Default: false
#
# Example:
# class User < ActiveRecord::Base
@@ -70,7 +71,8 @@ class UrlValidator < ActiveModel::EachValidator
allow_localhost: true,
allow_local_network: true,
ascii_only: false,
- enforce_user: false
+ enforce_user: false,
+ enforce_sanitization: false
}
end
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index fdaad1cf181..c99d7e9b8e9 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -13,7 +13,7 @@
= s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
= link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
.form-group
- = f.label :auto_devops_domain, class: 'label-bold'
+ = f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold'
= f.text_field :auto_devops_domain, class: 'form-control', placeholder: 'domain.com'
.form-text.text-muted
= s_("AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages.")
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index da2ebb08405..93da87538bc 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -104,7 +104,7 @@
- link_to_help = link_to(_("here"), help_page_path("user/permissions"))
= _('Read more about project permissions <strong>%{link_to_help}</strong>').html_safe % { link_to_help: link_to_help }
- = form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
+ = form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div
= users_select_tag(:user_ids, multiple: true, email_user: true, skip_ldap: @group.ldap_synced?, scope: :all)
.prepend-top-10
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index e4fc2985087..b75454b33d7 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -30,7 +30,7 @@
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('IP Address')
- .table-mobile-content
+ .table-mobile-content.str-truncated.has-tooltip{ title: runner.ip_address }
= runner.ip_address
.table-section.section-5
@@ -56,8 +56,9 @@
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('Last contact')
.table-mobile-content
- - if runner.contacted_at
- = time_ago_with_tooltip runner.contacted_at
+ - contacted_at = runner_contacted_at(runner)
+ - if contacted_at
+ = time_ago_with_tooltip contacted_at
- else
= _('Never')
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
index b2163ee85fa..a4e2c3252af 100644
--- a/app/views/admin/users/_user.html.haml
+++ b/app/views/admin/users/_user.html.haml
@@ -3,7 +3,7 @@
= image_tag avatar_icon_for_user(user), class: "avatar", alt: ''
.row-main-content
.user-name.row-title.str-truncated-100
- = link_to user.name, [:admin, user]
+ = link_to user.name, [:admin, user], class: "js-user-link", data: { user_id: user.id }
- if user.blocked?
%span.badge.badge-danger blocked
- if user.admin?
diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml
index 9de9143e8b1..c787d7420b7 100644
--- a/app/views/ci/status/_dropdown_graph_badge.html.haml
+++ b/app/views/ci/status/_dropdown_graph_badge.html.haml
@@ -6,7 +6,7 @@
- tooltip = "#{subject.name} - #{status.status_tooltip}"
- if status.has_details?
- = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
+ = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
%span{ class: klass }= sprite_icon(status.icon)
%span.ci-build-text= subject.name
@@ -16,5 +16,5 @@
%span.ci-build-text= subject.name
- if status.has_action?
- = link_to status.action_path, class: "ci-action-icon-container ci-action-icon-wrapper js-ci-action-icon", method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
+ = link_to status.action_path, class: "ci-action-icon-container ci-action-icon-wrapper js-ci-action-icon", method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
= sprite_icon(status.action_icon, css_class: "icon-action-#{status.action_icon}")
diff --git a/app/views/ci/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml
index fa82611d9c1..90c59bec975 100644
--- a/app/views/ci/variables/_content.html.haml
+++ b/app/views/ci/variables/_content.html.haml
@@ -1 +1,3 @@
= _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. 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/clusters/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
index 73b11d509d3..85d1002243b 100644
--- a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
@@ -1,6 +1,6 @@
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
-.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert' }
- %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } &times;
+.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
+ %button.close.js-close{ type: "button" } &times;
.gcp-signup-offer--content
.gcp-signup-offer--icon.append-right-8
= sprite_icon("information", size: 16)
diff --git a/app/views/clusters/clusters/_integration_form.html.haml b/app/views/clusters/clusters/_integration_form.html.haml
index 5e451f60c9d..4c47e11927e 100644
--- a/app/views/clusters/clusters/_integration_form.html.haml
+++ b/app/views/clusters/clusters/_integration_form.html.haml
@@ -13,19 +13,19 @@
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
.form-text.text-muted= s_('ClusterIntegration|Enable or disable GitLab\'s connection to your Kubernetes cluster.')
- - if has_multiple_clusters?
- .form-group
- %h5= s_('ClusterIntegration|Environment scope')
+ .form-group
+ %h5= s_('ClusterIntegration|Environment scope')
+ - if has_multiple_clusters?
= field.text_field :environment_scope, class: 'col-md-6 form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
.form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
+ - else
+ = text_field_tag :environment_scope, '*', class: 'col-md-6 form-control disabled', placeholder: s_('ClusterIntegration|Environment scope'), disabled: true
+ - environment_scope_url = 'https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope-premium'
+ - environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url }
+ .form-text.text-muted
+ %code *
+ = s_("ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}").html_safe % { environment_scope_start: environment_scope_start, environment_scope_end: '</a>'.html_safe }
- if can?(current_user, :update_cluster, @cluster)
.form-group
= field.submit _('Save changes'), class: 'btn btn-success'
-
- - unless has_multiple_clusters?
- %h5= s_('ClusterIntegration|Environment scope')
- %p
- %code *
- is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster.
- = link_to 'More information', ('https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope')
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index b1aa8e5d477..89a2dfdd69f 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -16,6 +16,7 @@
install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
toggle_status: @cluster.enabled? ? 'true': 'false',
+ has_rbac: @cluster.platform_kubernetes_rbac? ? 'true': 'false',
cluster_type: @cluster.cluster_type,
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 1050945b15a..ae67192cbcd 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -15,9 +15,11 @@
= nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
Your projects
+ %span.badge.badge-pill= limited_counter_with_delimiter(@total_user_projects_count)
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, data: {placement: 'right'} do
Starred projects
+ %span.badge.badge-pill= limited_counter_with_delimiter(@total_starred_projects_count)
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, data: {placement: 'right'} do
Explore projects
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index 31d4b3da4f1..4dbda5c754b 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -4,6 +4,9 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
+
+= render_if_exists "shared/gold_trial_callout"
+
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 50f39f93283..19b06ba5cdd 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -1,6 +1,8 @@
- @hide_top_links = true
- page_title "Groups"
-- header_title "Groups", dashboard_groups_path
+- header_title "Groups", dashboard_groups_path
+
+= render_if_exists "shared/gold_trial_callout"
= render 'dashboard/groups_head'
- if params[:filter].blank? && @groups.empty?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index fdd5c19d562..afd46412fab 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,6 +4,8 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
+= render_if_exists "shared/gold_trial_callout"
+
.page-title-holder
%h1.page-title= _('Issues')
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 77cfa1271df..3e5f13b92e3 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -2,6 +2,8 @@
- page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username)
+= render_if_exists "shared/gold_trial_callout"
+
.page-title-holder
%h1.page-title= _('Merge Requests')
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index ae0e38bf0ee..13822d36f15 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -13,6 +13,8 @@
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
+ .nav-controls
+ = render 'shared/milestones/search_form'
.milestones
%ul.content-list
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index deed774a4a5..446b4715b2d 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -4,6 +4,8 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
+= render_if_exists "shared/gold_trial_callout"
+
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 8933d9e31ff..ad08409c8fe 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -4,6 +4,8 @@
- page_title "Starred Projects"
- header_title "Projects", dashboard_projects_path
+= render_if_exists "shared/gold_trial_callout"
+
%div{ class: container_class }
= render "projects/last_push"
= render 'dashboard/projects_head'
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index d2593179f17..47729321961 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -2,6 +2,8 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
+= render_if_exists "shared/gold_trial_callout"
+
.page-title-holder
%h1.page-title= _('Todos')
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 4b8ad5acd5b..dd1edb5fdc9 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -7,7 +7,7 @@
= f.hidden_field :reset_password_token
.form-group
= f.label 'New password', for: "user_password"
- = f.password_field :password, class: "form-control top qa-password-field", required: true, title: 'This field is required'
+ = f.password_field :password, class: "form-control top qa-password-field", required: true, title: 'This field is required'
.form-group
= f.label 'Confirm new password', for: "user_password_confirmation"
= f.password_field :password_confirmation, class: "form-control bottom qa-password-confirmation", title: 'This field is required', required: true
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index 34d4293bd45..30ed7ed6b29 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,6 +1,6 @@
- page_title "Sign in"
-%div
+#signin-container
- if form_based_providers.any?
= render 'devise/shared/tabs_ldap'
- else
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index fefdf5f9531..f49cdfbf8da 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -7,7 +7,7 @@
- resource_params = params[resource_name].presence || params
= f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0)
%div
- = f.label 'Two-Factor Authentication code', name: :otp_attempt
+ = f.label 'Two-Factor Authentication code', name: :otp_attempt
= f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.'
%p.form-text.text-muted.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.
.prepend-top-20
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index 74791b81ccd..dae9a7acf6b 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -31,7 +31,7 @@
%strong= t scope, scope: [:doorkeeper, :scopes]
.text-secondary= t scope, scope: [:doorkeeper, :scope_desc]
.form-actions.text-right
- = form_tag oauth_authorization_path, method: :delete, class: 'inline' do
+ = form_tag oauth_authorization_path, method: :delete, class: 'inline' do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index fb0d2c3b8b0..90ed8e41d32 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -21,6 +21,6 @@
= link_to note.attachment.url, target: '_blank' do
= image_tag note.attachment.url, class: 'note-image-attach'
- else
- = link_to note.attachment.url, target: '_blank', class: 'note-file-attach' do
+ = link_to note.attachment.url, target: '_blank', class: 'note-file-attach' do
%i.fa.fa-paperclip
= note.attachment_identifier
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index a3eafc61d0a..869be4e8581 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -2,6 +2,8 @@
- page_title _("Groups")
- header_title _("Groups"), dashboard_groups_path
+= render_if_exists "shared/gold_trial_callout"
+
- if current_user
= render 'dashboard/groups_head'
- else
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index 452f390695c..d18dec7bd8e 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -2,6 +2,8 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
+= render_if_exists "shared/gold_trial_callout"
+
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index 452f390695c..d18dec7bd8e 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -2,6 +2,8 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
+= render_if_exists "shared/gold_trial_callout"
+
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 452f390695c..d18dec7bd8e 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -2,6 +2,8 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
+= render_if_exists "shared/gold_trial_callout"
+
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 13d584f5f1d..2af3e861587 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -23,7 +23,7 @@
Members with access to
%strong= @group.name
%span.badge= @members.total_count
- = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
+ = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
.position-relative.append-right-8
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 4df3d831942..5cf3193bc62 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -6,15 +6,10 @@
- subscribed = params[:subscribed]
- labels_or_filters = @labels.exists? || search.present? || subscribed.present?
-- if @labels.present? && can_admin_label
- - content_for(:header_content) do
- .nav-controls
- = link_to _('New label'), new_group_label_path(@group), class: "btn btn-success"
-
- if labels_or_filters
#promote-label-modal
%div{ class: container_class }
- = render 'shared/labels/nav'
+ = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
.labels-container.prepend-top-5
- if @labels.any?
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index a9ce2fe5ab0..808bb1309b1 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -7,7 +7,7 @@
= render 'shared/issuable/nav', type: :merge_requests
- if current_user
.nav-controls
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests, with_feature_enabled: 'merge_requests', with_shared: false, include_projects_in_subgroups: true
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests, with_feature_enabled: 'merge_requests', with_shared: false, include_projects_in_subgroups: true
= render 'shared/issuable/search_bar', type: :merge_requests
diff --git a/app/views/groups/milestones/_form.html.haml b/app/views/groups/milestones/_form.html.haml
index 39e3af5f6d2..b3d13a2dc43 100644
--- a/app/views/groups/milestones/_form.html.haml
+++ b/app/views/groups/milestones/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@group, @milestone], html: { class: 'milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
+= form_for [@group, @milestone], html: { class: 'milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
.row
= form_errors(@milestone)
@@ -24,4 +24,3 @@
- else
= f.submit 'Update milestone', class: "btn-success btn"
= link_to "Cancel", group_milestone_path(@group, @milestone), class: "btn btn-cancel"
-
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index af4fe8f2ef8..b6fb908c8f6 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -4,6 +4,7 @@
= render 'shared/milestones_filter', counts: @milestone_states
.nav-controls
+ = render 'shared/milestones/search_form'
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @group)
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-success"
diff --git a/app/views/groups/runners/_group_runners.html.haml b/app/views/groups/runners/_group_runners.html.haml
index bcfb6d99716..806a24e2bbc 100644
--- a/app/views/groups/runners/_group_runners.html.haml
+++ b/app/views/groups/runners/_group_runners.html.haml
@@ -21,6 +21,6 @@
- else
%h4.underlined-title
- = _('Available group Runners : %{runners}.').html_safe % { runners: @group.runners.count }
+ = _('Available group Runners: %{runners}').html_safe % { runners: @group.runners.count }
%ul.bordered-list
= render partial: 'groups/runners/runner', collection: @group.runners, as: :runner
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 08a6359f777..0bb2363f65a 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -11,20 +11,20 @@
%meta{ 'http-equiv' => 'X-UA-Compatible', content: 'IE=edge' }
-# Open Graph - http://ogp.me/
- %meta{ property: 'og:type', content: "object" }
- %meta{ property: 'og:site_name', content: site_name }
- %meta{ property: 'og:title', content: page_title }
+ %meta{ property: 'og:type', content: "object" }
+ %meta{ property: 'og:site_name', content: site_name }
+ %meta{ property: 'og:title', content: page_title }
%meta{ property: 'og:description', content: page_description }
- %meta{ property: 'og:image', content: page_image }
+ %meta{ property: 'og:image', content: page_image }
%meta{ property: 'og:image:width', content: '64' }
%meta{ property: 'og:image:height', content: '64' }
- %meta{ property: 'og:url', content: request.base_url + request.fullpath }
+ %meta{ property: 'og:url', content: request.base_url + request.fullpath }
-# Twitter Card - https://dev.twitter.com/cards/types/summary
- %meta{ property: 'twitter:card', content: "summary" }
- %meta{ property: 'twitter:title', content: page_title }
- %meta{ property: 'twitter:description', content: page_description }
- %meta{ property: 'twitter:image', content: page_image }
+ %meta{ property: 'twitter:card', content: "summary" }
+ %meta{ property: 'twitter:title', content: page_title }
+ %meta{ property: 'twitter:description', content: page_description }
+ %meta{ property: 'twitter:image', content: page_image }
= page_card_meta_tags
%title= page_title(site_name)
@@ -33,8 +33,8 @@
= favicon_link_tag favicon, id: 'favicon', data: { original_href: favicon }, type: 'image/png'
= stylesheet_link_tag "application", media: "all"
- = stylesheet_link_tag "print", media: "print"
- = stylesheet_link_tag "test", media: "all" if Rails.env.test?
+ = stylesheet_link_tag "print", media: "print"
+ = stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
= stylesheet_link_tag 'csslab' if Feature.enabled?(:csslab)
@@ -61,10 +61,10 @@
%meta{ name: 'theme-color', content: '#474D57' }
-# Apple Safari/iOS home screen icons
- = favicon_link_tag 'touch-icon-iphone.png', rel: 'apple-touch-icon'
- = favicon_link_tag 'touch-icon-ipad.png', rel: 'apple-touch-icon', sizes: '76x76'
+ = favicon_link_tag 'touch-icon-iphone.png', rel: 'apple-touch-icon'
+ = favicon_link_tag 'touch-icon-ipad.png', rel: 'apple-touch-icon', sizes: '76x76'
= favicon_link_tag 'touch-icon-iphone-retina.png', rel: 'apple-touch-icon', sizes: '120x120'
- = favicon_link_tag 'touch-icon-ipad-retina.png', rel: 'apple-touch-icon', sizes: '152x152'
+ = favicon_link_tag 'touch-icon-ipad-retina.png', rel: 'apple-touch-icon', sizes: '152x152'
%link{ rel: 'mask-icon', href: image_path('logo.svg'), color: 'rgb(226, 67, 41)' }
-# Windows 8 pinned site tile
diff --git a/app/views/layouts/_init_client_detection_flags.html.haml b/app/views/layouts/_init_client_detection_flags.html.haml
new file mode 100644
index 00000000000..c729f8aa696
--- /dev/null
+++ b/app/views/layouts/_init_client_detection_flags.html.haml
@@ -0,0 +1,7 @@
+- client = client_js_flags
+
+- if client
+ -# haml-lint:disable InlineJavaScript
+ :javascript
+ gl = window.gl || {};
+ gl.client = #{client.to_json};
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 1f4d24d996c..4373240001e 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,8 +1,9 @@
!!! 5
%html{ lang: I18n.locale, class: page_class }
= render "layouts/head"
- %body{ class: "#{user_application_theme} #{@body_class}", data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
+ %body{ class: "#{user_application_theme} #{@body_class} #{client_class_list}", data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
= render "layouts/init_auto_complete" if @gfm_form
+ = render "layouts/init_client_detection_flags"
= render 'peek/bar'
= render partial: "layouts/header/default", locals: { project: @project, group: @group }
= render 'layouts/page', sidebar: sidebar, nav: nav
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index bfbfeee7c4b..1d40b78fa83 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,7 +1,7 @@
- page_title @group.name
- page_description @group.description unless page_description
- header_title group_title(@group) unless header_title
-- nav "group"
+- nav "group"
- @left_sidebar = true
- content_for :page_specific_javascripts do
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index 953c0e7f46c..513902890af 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -1,6 +1,13 @@
+- show_blog_link = current_user_menu?(:help) && blog_post_url.present?
%ul
- if current_user_menu?(:help)
%li
= link_to _("Help"), help_path
+ %li.divider
+ - if show_blog_link
+ %li
+ = link_to _("What's new?"), blog_post_url
+ %li
+ = link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
= render 'shared/user_dropdown_contributing_link'
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index e42251f9ec8..5a66b02c048 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -1,4 +1,4 @@
-%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown" } }
+%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown" } }
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do
= sprite_icon('plus-square', size: 16)
= sprite_icon('angle-down', css_class: 'caret-down')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index ddd30efe062..f659c89dd30 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -10,7 +10,7 @@
= render "layouts/nav/projects_dropdown/show"
- if dashboard_nav_link?(:groups)
- = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do
+ = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do
%button{ type: 'button', data: { toggle: "dropdown" } }
= _('Groups')
= sprite_icon('angle-down', css_class: 'caret-down')
diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index 69167edb1df..1e3bb8f1224 100644
--- a/app/views/layouts/nav/sidebar/_profile.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
@@ -3,7 +3,7 @@
.context-header
= link_to profile_path, title: _('Profile Settings') do
.avatar-container.s40.settings-avatar
- = sprite_icon('user', size: 24)
+ = image_tag avatar_icon_for_user(current_user, 40), class: "avatar s40 avatar-tile", alt: current_user.name
.sidebar-context-title User Settings
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index d8017742c90..207c08ee5bb 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -26,10 +26,10 @@
%span= _('Details')
= nav_link(path: 'projects#activity') do
- = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
+ = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity qa-activity-link' do
%span= _('Activity')
- - if project_nav_tab?(:releases) && Feature.enabled?(:releases_page, @project)
+ - if project_nav_tab?(:releases)
= nav_link(controller: :releases) do
= link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do
%span= _('Releases')
@@ -43,7 +43,7 @@
- if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths) do
- = link_to project_tree_path(@project), class: 'shortcuts-tree' do
+ = link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
.nav-icon-container
= sprite_icon('doc-text')
%span.nav-item-name
@@ -64,7 +64,7 @@
= _('Commits')
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project) do
+ = link_to project_branches_path(@project), class: 'qa-branches-link' do
= _('Branches')
= nav_link(controller: [:tags]) do
@@ -146,7 +146,7 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
- = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests' do
+ = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests qa-merge-requests-link' do
.nav-icon-container
= sprite_icon('git-merge')
%span.nav-item-name
@@ -170,7 +170,7 @@
= _('CI / CD')
%ul.sidebar-sub-level-items
- = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts], html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts], html_options: { class: "fly-out-top-item" }) do
= link_to project_pipelines_path(@project) do
%strong.fly-out-top-item-name
= _('CI / CD')
@@ -201,7 +201,7 @@
- if project_nav_tab? :operations
= nav_link(controller: sidebar_operations_paths) do
- = link_to sidebar_operations_link_path, class: 'shortcuts-operations' do
+ = link_to sidebar_operations_link_path, class: 'shortcuts-operations qa-link-operations' do
.nav-icon-container
= sprite_icon('cloud-gear')
%span.nav-item-name
@@ -227,6 +227,12 @@
%span
= _('Environments')
+ - if project_nav_tab?(:error_tracking)
+ = nav_link(controller: :error_tracking) do
+ = link_to project_error_tracking_index_path(@project), title: _('Error Tracking'), class: 'shortcuts-tracking qa-operations-tracking-link' do
+ %span
+ = _('Error Tracking')
+
- if project_nav_tab? :serverless
= nav_link(controller: :functions) do
= link_to project_serverless_functions_path(@project), title: _('Serverless') do
@@ -277,7 +283,7 @@
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
- = link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do
+ = link_to get_project_wiki_path(@project), class: 'shortcuts-wiki qa-wiki-link' do
.nav-icon-container
= sprite_icon('book')
%span.nav-item-name
@@ -339,7 +345,10 @@
= link_to project_settings_ci_cd_path(@project), title: _('CI / CD') do
%span
= _('CI / CD')
- = render_if_exists 'projects/sidebar/settings_operations'
+ - if settings_operations_available?
+ = nav_link(controller: [:operations]) do
+ = link_to project_settings_operations_path(@project), title: _('Operations') do
+ = _('Operations')
- if @project.pages_available?
= nav_link(controller: :pages) do
= link_to project_pages_path(@project), title: _('Pages') do
diff --git a/app/views/layouts/snippets.html.haml b/app/views/layouts/snippets.html.haml
index 6418500d5d1..5f986c81ff4 100644
--- a/app/views/layouts/snippets.html.haml
+++ b/app/views/layouts/snippets.html.haml
@@ -1,4 +1,4 @@
-- header_title _("Snippets"), snippets_path
+- header_title _("Snippets"), snippets_path
- content_for :page_specific_javascripts do
- if @snippet && current_user
diff --git a/app/views/notify/import_issues_csv_email.html.haml b/app/views/notify/import_issues_csv_email.html.haml
new file mode 100644
index 00000000000..f30d2b5f078
--- /dev/null
+++ b/app/views/notify/import_issues_csv_email.html.haml
@@ -0,0 +1,18 @@
+- text_style = 'font-size:16px; text-align:center; line-height:30px;'
+
+%p{ style: text_style }
+ Your CSV import for project
+ %a{ href: project_url(@project), style: "color:#3777b0; text-decoration:none;" }
+ = @project.full_name
+ has been completed.
+
+%p{ style: text_style }
+ #{pluralize(@results[:success], 'issue')} imported.
+
+- if @results[:error_lines].present?
+ %p{ style: text_style }
+ Errors found on line #{'number'.pluralize(@results[:error_lines].size)}: #{@results[:error_lines].join(', ')}. Please check if these lines have an issue title.
+
+- if @results[:parse_error]
+ %p{ style: text_style }
+ Error parsing CSV file. Please make sure it has the correct format: a delimited text file that uses a comma to separate values.
diff --git a/app/views/notify/import_issues_csv_email.text.erb b/app/views/notify/import_issues_csv_email.text.erb
new file mode 100644
index 00000000000..1117f90714d
--- /dev/null
+++ b/app/views/notify/import_issues_csv_email.text.erb
@@ -0,0 +1,11 @@
+Your CSV import for project <%= @project.full_name %> (<%= project_url(@project) %>) has been completed.
+
+<%= pluralize(@results[:success], 'issue') %> imported.
+
+<% if @results[:error_lines].present? %>
+Errors found on line <%= 'number'.pluralize(@results[:error_lines].size) %>: <%= @results[:error_lines].join(', ') %>. Please check if these lines have an issue title.
+<% end %>
+
+<% if @results[:parse_error] %>
+Error parsing CSV file. Please make sure it has the correct format: a delimited text file that uses a comma to separate values.
+<% end %>
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index e167e094240..ee2c5a13b8a 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,37 +1,37 @@
-- page_title "Account"
+- page_title _('Account')
- @content_class = "limit-container-width" unless fluid_layout
- if current_user.ldap_user?
.alert.alert-info
- Some options are unavailable for LDAP accounts
+ = s_('Profiles|Some options are unavailable for LDAP accounts')
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Two-Factor Authentication
+ = s_('Profiles|Two-Factor Authentication')
%p
- Increase your account's security by enabling Two-Factor Authentication (2FA).
+ = s_("Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)")
.col-lg-8
%p
- Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
+ #{_('Status')}: #{current_user.two_factor_enabled? ? _('Enabled') : _('Disabled')}
- if current_user.two_factor_enabled?
- = link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info'
+ = link_to _('Manage two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-info'
- else
.append-bottom-10
- = link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success'
+ = link_to _('Enable two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-success'
%hr
- if display_providers_on_profile?
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Social sign-in
+ = s_('Profiles|Social sign-in')
%p
- Activate signin with one of the following services
+ = s_('Profiles|Activate signin with one of the following services')
.col-lg-8
%label.label-bold
- Connected Accounts
- %p Click on icon to activate signin with one of the following services
+ = s_('Profiles|Connected Accounts')
+ %p= s_('Profiles|Click on icon to activate signin with one of the following services')
- button_based_providers.each do |provider|
.provider-btn-group
.provider-btn-image
@@ -39,24 +39,24 @@
- if auth_active?(provider)
- if unlink_allowed?(provider)
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
- Disconnect
+ = s_('Profiles|Disconnect')
- else
%a.provider-btn
- Active
+ = s_('Profiles|Active')
- else
= link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
- Connect
+ = s_('Profiles|Connect')
= render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: local_assigns[:group_saml_identities]
%hr
- if current_user.can_change_username?
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0.warning-title
- Change username
+ = s_('Profiles|Change username')
%p
- Changing your username can have unintended side effects.
+ = s_('Profiles|Changing your username can have unintended side effects.')
= succeed '.' do
- = link_to 'Learn more', help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
+ = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
.col-lg-8
- data = { initial_username: current_user.username, root_url: root_url, action_url: update_username_profile_path(format: :json) }
#update-username{ data: data }
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 31f1cf560e2..12da62f4c64 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,5 +1,5 @@
%div{ class: container_class }
- .nav-block.d-none.d-sm-block.activities
+ .nav-block.d-none.d-sm-flex.activities
= render 'shared/event_filter'
.controls
= link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn d-none d-sm-inline-block has-tooltip' do
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index aa980da7e95..409b6dba9ca 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -19,7 +19,7 @@
%ul
%li Project and wiki repositories
%li Project uploads
- %li Project configuration including web hooks and services
+ %li Project configuration, including services
%li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
%li LFS objects
%p
@@ -28,13 +28,14 @@
%li Job traces and artifacts
%li Container registry images
%li CI variables
+ %li Webhooks
%li Any encrypted tokens
%p
Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- if project.export_status == :finished
- = link_to 'Download export', download_export_project_path(project),
+ = link_to 'Download export', download_export_project_path(project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
- = link_to 'Generate new export', generate_new_export_project_path(project),
+ = link_to 'Generate new export', generate_new_export_project_path(project),
method: :post, class: "btn btn-default"
- else
= link_to 'Export project', export_project_path(project),
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 82b2ab64a5d..65537cf56de 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -19,11 +19,12 @@
%span.access-request-links.prepend-left-8
= render 'shared/members/access_request_links', source: @project
- if @project.tag_list.present?
- %span.project-tag-list.d-inline-flex.prepend-left-8.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_tags? ? @project.tag_list.join(', ') : nil }
+ %span.project-topic-list.d-inline-flex.prepend-left-8.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_topics? ? @project.tag_list.join(', ') : nil }
= sprite_icon('tag', size: 16, css_class: 'icon append-right-4')
- = @project.tags_to_show
- - if @project.has_extra_tags?
- = _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown }
+ = @project.topics_to_show
+ - if @project.has_extra_topics?
+ = _("+ %{count} more") % { count: @project.count_of_extra_topics_not_shown }
+
.project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
- if current_user
diff --git a/app/views/projects/_issuable_by_email.html.haml b/app/views/projects/_issuable_by_email.html.haml
index d59191a6f87..0b2d179456d 100644
--- a/app/views/projects/_issuable_by_email.html.haml
+++ b/app/views/projects/_issuable_by_email.html.haml
@@ -21,7 +21,7 @@
= clipboard_button(target: '#issuable_email', class: 'btn btn-clipboard input-group-text btn-transparent d-none d-sm-block')
- if issuable_type == 'issue'
- - enter_title_text = _('Enter the issue title')
+ - enter_title_text = _('Enter the issue title')
- enter_description_text = _('Enter the issue description')
- else
- enter_title_text = _('Enter the merge request title')
@@ -36,7 +36,12 @@
%p
= render 'by_email_description'
%p
- This is a private email address, generated just for you.
+ This is a private email address
+
+ %a{ href: 'https://docs.gitlab.com/ee/development/emails.html#email-namespace', target: "_blank", rel: "noopener" }
+ %i.fa.fa-question-circle{ 'aria-label': "Learn more about incoming email addresses" }
+
+ generated just for you.
Anyone who gets ahold of it can create issues or merge requests as if they were you.
You should
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 0f709c65d0e..03ba1104507 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -18,17 +18,7 @@
Preview
%li.md-header-toolbar.active
- = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") })
- = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") })
- = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") })
- = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") })
- = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") })
- = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") })
- = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") })
- = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") })
- = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") })
- %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } }
- = sprite_icon("screen-full")
+ = render 'projects/blob/markdown_buttons', show_fullscreen_button: true
.md-write-holder
= yield
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index ba7d3154326..276363df7da 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -43,7 +43,7 @@
= f.label :description, class: 'label-bold' do
Project description
%span (optional)
- = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_description", track_value: "" }
+ = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_description", track_value: "" }
= f.label :visibility_level, class: 'label-bold' do
Visibility Level
diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml
index cfb91568061..f42d5128715 100644
--- a/app/views/projects/artifacts/_tree_file.html.haml
+++ b/app/views/projects/artifacts/_tree_file.html.haml
@@ -14,4 +14,4 @@
= link_to path_to_file, class: 'str-truncated' do
%span= blob.name
%td
- = number_to_human_size(blob.size, precision: 2)
+ = number_to_human_size(blob.size)
diff --git a/app/views/projects/badges/badge_flat-square.svg.erb b/app/views/projects/badges/badge_flat-square.svg.erb
new file mode 100644
index 00000000000..5b90da15ef5
--- /dev/null
+++ b/app/views/projects/badges/badge_flat-square.svg.erb
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="<%= badge.width %>" height="20">
+ <g shape-rendering="crispEdges">
+ <path fill="<%= badge.key_color %>" d="M0 0 h<%= badge.key_width %> v20 H0 z"/>
+ <path fill="<%= badge.value_color %>" d="M<%= badge.key_width %> 0 h<%= badge.value_width %> v20 H<%= badge.key_width %> z"/>
+ </g>
+
+ <g fill="#fff" text-anchor="middle">
+ <g font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
+ <text x="<%= badge.key_text_anchor %>" y="14">
+ <%= badge.key_text %>
+ </text>
+ <text x="<%= badge.value_text_anchor %>" y="14">
+ <%= badge.value_text %>
+ </text>
+ </g>
+ </g>
+</svg>
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 3c1f33ea95e..a54460f1196 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -1,4 +1,6 @@
- action = current_action?(:edit) || current_action?(:update) ? 'edit' : 'create'
+- file_name = params[:id].split("/").last ||= ""
+- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name)
.file-holder-bottom-radius.file-holder.file.append-bottom-default
.js-file-title.file-title.clearfix{ data: { current_action: action } }
@@ -17,6 +19,8 @@
required: true, class: 'form-control new-file-name js-file-path-name-input'
.file-buttons
+ - if is_markdown
+ = render 'projects/blob/markdown_buttons', show_fullscreen_button: false
= button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do
%span.no-wrap
= custom_icon('icon_no_wrap')
diff --git a/app/views/projects/blob/_markdown_buttons.html.haml b/app/views/projects/blob/_markdown_buttons.html.haml
new file mode 100644
index 00000000000..1d6acd86108
--- /dev/null
+++ b/app/views/projects/blob/_markdown_buttons.html.haml
@@ -0,0 +1,13 @@
+.md-header-toolbar.active
+ = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") })
+ = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") })
+ = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") })
+ = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") })
+ = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") })
+ = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") })
+ = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") })
+ = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") })
+ = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") })
+ - if show_fullscreen_button
+ %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } }
+ = sprite_icon("screen-full")
diff --git a/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml b/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml
index 5be7cc7f25a..61d67a88a5a 100644
--- a/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml
+++ b/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml
@@ -1,9 +1,9 @@
-- if viewer.valid?(@project, @commit.sha)
+- if viewer.valid?(project: @project, sha: @commit.sha, user: @current_user)
= icon('check fw')
This GitLab CI configuration is valid.
- else
= icon('warning fw')
This GitLab CI configuration is invalid:
- = viewer.validation_message(@project, @commit.sha)
+ = viewer.validation_message(project: @project, sha: @commit.sha, user: @current_user)
= link_to 'Learn more', help_page_path('ci/yaml/README')
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 88f9b7dfc9f..4b0ea15335e 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -76,7 +76,7 @@
= icon("trash-o")
- else
= link_to project_branch_path(@project, branch.name),
- class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
+ class: "btn btn-remove remove-row qa-remove-btn js-ajax-loading-spinner has-tooltip",
title: s_('Branches|Delete branch'),
method: :delete,
data: { confirm: s_("Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?") % { branch_name: branch.name } },
diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml
index 0e4b119bb54..93061452e12 100644
--- a/app/views/projects/branches/_panel.html.haml
+++ b/app/views/projects/branches/_panel.html.haml
@@ -10,7 +10,7 @@
.card.prepend-top-10
.card-header
= panel_title
- %ul.content-list.all-branches
+ %ul.content-list.all-branches.qa-all-branches
- branches.first(overview_max_branches).each do |branch|
= render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch)
- if branches.size > overview_max_branches
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index ca867961f6b..43f1cd01b67 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -35,7 +35,7 @@
- if can? current_user, :push_code, @project
= link_to project_merged_branches_path(@project),
- class: 'btn btn-inverted btn-remove has-tooltip',
+ class: 'btn btn-inverted btn-remove has-tooltip qa-delete-merged-branches',
title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref },
method: :delete,
data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'),
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 45515fb492f..bbe0a2c97fd 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -35,8 +35,8 @@
- elsif can_collaborate_with_project?(@project)
%li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- elsif create_mr_from_new_fork
- - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
- notice: edit_in_new_fork_notice,
+ - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
+ 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)
%li= link_to _('New file'), fork_path, method: :post
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
index 745983ace7e..a8b728527c8 100644
--- a/app/views/projects/buttons/_notifications.html.haml
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -12,8 +12,8 @@
%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", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= sprite_icon("notifications", css_class: "icon notifications-icon js-notifications-icon")
%span.js-notification-loading.fa.hidden
- %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
- = sprite_icon("arrow-down", css_class: "icon")
+ %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" }, class: "#{btn_class}" }
+ = sprite_icon("arrow-down", css_class: "icon mr-0")
.sr-only Toggle dropdown
- else
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting - #{notification_title(notification_setting.level)}", 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" } }
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 44e9cb84341..9d069c025ba 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -25,7 +25,7 @@
= job.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite')
= link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name"
- else
- .light none
+ .light= _('none')
.icon-container.commit-icon
= custom_icon("icon_commit")
@@ -33,10 +33,10 @@
= link_to job.short_sha, project_commit_path(job.project, job.sha), class: "commit-sha"
- if job.stuck?
- = icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
+ = icon('warning', class: 'text-warning has-tooltip', title: _('Job is stuck. Check runners.'))
- if retried
- = icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried')
+ = icon('refresh', class: 'text-warning has-tooltip', title: _('Job was retried'))
.label-container
- if job.tags.any?
@@ -44,13 +44,13 @@
%span.badge.badge-primary
= tag
- if job.try(:trigger_request)
- %span.badge.badge-info triggered
+ %span.badge.badge-info= _('triggered')
- if job.try(:allow_failure)
- %span.badge.badge-danger allowed to fail
+ %span.badge.badge-danger= _('allowed to fail')
- if job.schedulable?
%span.badge.badge-info= s_('DelayedJobs|delayed')
- elsif job.action?
- %span.badge.badge-info manual
+ %span.badge.badge-info= _('manual')
- if pipeline_link
%td
@@ -70,7 +70,7 @@
- if job.try(:runner)
= runner_link(job.runner)
- else
- .light none
+ .light= _('none')
- if stage
%td
@@ -97,11 +97,11 @@
%td
.float-right
- if can?(current_user, :read_build, job) && job.artifacts?
- = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
+ = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: _('Download artifacts'), class: 'btn btn-build' do
= sprite_icon('download')
- if can?(current_user, :update_build, job)
- if job.active?
- = link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: 'Cancel', class: 'btn btn-build' do
+ = link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: _('Cancel'), class: 'btn btn-build' do
= icon('remove', class: 'cred')
- elsif job.scheduled?
.btn-group
@@ -123,8 +123,8 @@
= sprite_icon('time-out')
- elsif allow_retry
- if job.playable? && !admin && can?(current_user, :update_build, job)
- = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
+ = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Play'), class: 'btn btn-build' do
= custom_icon('icon_play')
- elsif job.retryable?
- = link_to retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ = link_to retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Retry'), class: 'btn btn-build' do
= icon('repeat')
diff --git a/app/views/projects/ci/lints/_create.html.haml b/app/views/projects/ci/lints/_create.html.haml
index 30bf1384b22..59b5b9f8a30 100644
--- a/app/views/projects/ci/lints/_create.html.haml
+++ b/app/views/projects/ci/lints/_create.html.haml
@@ -1,45 +1,48 @@
- if @status
%p
- %b Status:
- syntax is correct
+ %b= _("Status:")
+ = _("syntax is correct")
%i.fa.fa-ok.correct-syntax
.table-holder
%table.table.table-bordered
%thead
%tr
- %th Parameter
- %th Value
+ %th= _("Parameter")
+ %th= _("Value")
%tbody
- @stages.each do |stage|
- @builds.select { |build| build[:stage] == stage }.each do |build|
+ - job = @jobs[build[:name].to_sym]
%tr
%td #{stage.capitalize} Job - #{build[:name]}
%td
- %pre= build[:commands]
+ %pre= job[:before_script].to_a.join('\n')
+ %pre= job[:script].to_a.join('\n')
+ %pre= job[:after_script].to_a.join('\n')
%br
- %b Tag list:
+ %b= _("Tag list:")
= build[:tag_list].to_a.join(", ")
%br
- %b Only policy:
- = @jobs[build[:name].to_sym][:only].to_a.join(", ")
+ %b= _("Only policy:")
+ = job[:only].to_a.join(", ")
%br
- %b Except policy:
- = @jobs[build[:name].to_sym][:except].to_a.join(", ")
+ %b= _("Except policy:")
+ = job[:except].to_a.join(", ")
%br
- %b Environment:
+ %b= _("Environment:")
= build[:environment]
%br
- %b When:
+ %b= _("When:")
= build[:when]
- if build[:allow_failure]
- %b Allowed to fail
+ %b= _("Allowed to fail")
- else
%p
- %b Status:
- syntax is incorrect
+ %b= _("Status:")
+ = _("syntax is incorrect")
%i.fa.fa-remove.incorrect-syntax
- %b Error:
+ %b= _("Error:")
= @error
diff --git a/app/views/projects/ci/lints/show.html.haml b/app/views/projects/ci/lints/show.html.haml
index cbda6bf2107..7b87664961e 100644
--- a/app/views/projects/ci/lints/show.html.haml
+++ b/app/views/projects/ci/lints/show.html.haml
@@ -1,9 +1,9 @@
-- page_title "CI Lint"
-- page_description "Validate your GitLab CI configuration file"
+- page_title _("CI Lint")
+- page_description _("Validate your GitLab CI configuration file")
- content_for :library_javascripts do
= page_specific_javascript_tag('lib/ace.js')
-%h2.pt-3.pb-3 Check your .gitlab-ci.yml
+%h2.pt-3.pb-3= _("Check your .gitlab-ci.yml")
.project-ci-linter
= form_tag project_ci_lint_path(@project), method: :post do
@@ -11,14 +11,14 @@
.col-sm-12
.file-holder
.js-file-title.file-title.clearfix
- Content of .gitlab-ci.yml
+ = _("Contents of .gitlab-ci.yml")
#ci-editor.ci-editor= @content
= text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true)
.col-sm-12
.float-left.prepend-top-10
- = submit_tag('Validate', class: 'btn btn-success submit-yml')
+ = submit_tag(_('Validate'), class: 'btn btn-success submit-yml')
.float-right.prepend-top-10
- = button_tag('Clear', type: 'button', class: 'btn btn-default clear-yml')
+ = button_tag(_('Clear'), type: 'button', class: 'btn btn-default clear-yml')
.row.prepend-top-20
.col-sm-12
diff --git a/app/views/projects/cleanup/_show.html.haml b/app/views/projects/cleanup/_show.html.haml
index cecc139b183..888be4ee282 100644
--- a/app/views/projects/cleanup/_show.html.haml
+++ b/app/views/projects/cleanup/_show.html.haml
@@ -24,6 +24,6 @@
= _("No file selected")
= f.file_field :bfg_object_map, accept: 'text/plain', class: "hidden js-object-map-input", required: true
.form-text.text-muted
- = _("The maximum file size allowed is %{max_attachment_size}mb") % { max_attachment_size: Gitlab::CurrentSettings.max_attachment_size }
+ = _("The maximum file size allowed is %{size}.") % { size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) }
= f.submit _('Start cleanup'), class: 'btn btn-success'
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 2a919a767c0..a389261136a 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -49,7 +49,7 @@
#{ _('Download') }
- unless @commit.parents.length > 1
%li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch), class: "qa-email-patches"
- %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff), class: "qa-plain-diff"
+ %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff), class: "qa-plain-diff"
.commit-box{ data: { project_path: project_path(@project) } }
%h3.commit-title
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 2eef599cf84..2cc3d921abc 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -9,4 +9,4 @@
= link_to _("Plain diff"), merge_request_path(@merge_request, format: :diff), class: "btn btn-sm"
= link_to _("Email patch"), merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
%p
- = _("To preserve performance only <strong>%{display_size} of ${real_size}</strong> files are displayed.").html_safe % { display_size: diff_files.size, real_size: diff_files.real_size }
+ = _("To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed.").html_safe % { display_size: diff_files.size, real_size: diff_files.real_size }
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 1b52821af15..a58f736b5b4 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -39,9 +39,9 @@
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
.form-group
- = f.label :tag_list, "Tags", class: 'label-bold'
+ = f.label :tag_list, "Topics", class: 'label-bold'
= f.text_field :tag_list, value: @project.tag_list.join(', '), maxlength: 2000, class: "form-control"
- %p.form-text.text-muted Separate tags with commas.
+ %p.form-text.text-muted Separate topics with commas.
%fieldset.features
%h5.prepend-top-0= _("Project avatar")
.form-group
diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml
index b7e1cf85cb7..aebd176af9b 100644
--- a/app/views/projects/environments/folder.html.haml
+++ b/app/views/projects/environments/folder.html.haml
@@ -1,8 +1,5 @@
- @no_container = true
- page_title _("Environments")
-#environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json),
- "folder-name" => @folder,
- "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
- "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
+#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data,
"css-class" => container_class } }
diff --git a/app/views/projects/error_tracking/index.html.haml b/app/views/projects/error_tracking/index.html.haml
new file mode 100644
index 00000000000..bc02c5f0e5a
--- /dev/null
+++ b/app/views/projects/error_tracking/index.html.haml
@@ -0,0 +1,3 @@
+- page_title _('Errors')
+
+#js-error_tracking{ data: error_tracking_data(@project) }
diff --git a/app/views/projects/issues/_import_export.svg b/app/views/projects/issues/_import_export.svg
new file mode 100644
index 00000000000..53c35d12f57
--- /dev/null
+++ b/app/views/projects/issues/_import_export.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 238 111" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="4" width="82" rx="3" height="28" fill="#fff"/><path id="5" d="m68.926 12.09v-2.41c0-.665-.437-.888-.975-.507l-6.552 4.631c-.542.383-.539.998 0 1.379l6.552 4.631c.542.383.975.154.975-.507v-2.41h4.874c.668 0 1.2-.538 1.2-1.201v-2.406c0-.668-.537-1.201-1.2-1.201h-4.874" fill="#fc8a51"/><path id="6" d="m4 24h74v-20h-74v20m-4-21c0-1.655 1.338-2.996 2.991-2.996h76.02c1.652 0 2.991 1.35 2.991 2.996v22.01c0 1.655-1.338 2.996-2.991 2.996h-76.02c-1.652 0-2.991-1.35-2.991-2.996v-22.01"/><circle id="2" cx="16" cy="14" r="7"/><circle id="0" cx="16" cy="14" r="7"/><mask id="3" width="14" height="14" x="0" y="0" fill="#fff"><use xlink:href="#2"/></mask><mask id="1" width="14" height="14" x="0" y="0" fill="#fff"><use xlink:href="#0"/></mask></defs><g fill="none" fill-rule="evenodd"><rect width="98" height="111" fill="#fff" rx="6"/><path fill="#e5e5e5" fill-rule="nonzero" d="m4 6.01v98.99c0 1.11.897 2.01 2 2.01h85.998c1.105 0 2-.897 2-2.01v-98.99c0-1.11-.897-2.01-2-2.01h-85.998c-1.105 0-2 .897-2 2.01m-4 0c0-3.318 2.685-6.01 6-6.01h85.998c3.314 0 6 2.689 6 6.01v98.99c0 3.318-2.685 6.01-6 6.01h-85.998c-3.314 0-6-2.689-6-6.01v-98.99"/><rect width="76" height="85" x="11" y="12" fill="#f9f9f9" rx="3"/><g transform="translate(37 59)"><use xlink:href="#4"/><path fill="#e5e5e5" fill-rule="nonzero" d="m4 24h74v-20h-74v20m-4-21c0-1.655 1.338-2.996 2.991-2.996h76.02c1.652 0 2.991 1.35 2.991 2.996v22.01c0 1.655-1.338 2.996-2.991 2.996h-76.02c-1.652 0-2.991-1.35-2.991-2.996v-22.01"/><use fill="#fff" stroke="#6b4fbb" stroke-width="8" mask="url(#1)" xlink:href="#0"/><use xlink:href="#5"/></g><g transform="translate(140)"><path fill="#fff" d="m0 4h94v103h-94z"/><path fill="#e5e5e5" fill-rule="nonzero" d="m0 74v30.993c0 3.318 2.687 6.01 6 6.01h85.998c3.316 0 6-2.69 6-6.01v-98.99c0-3.318-2.687-6.01-6-6.01h-85.998c-3.316 0-6 2.69-6 6.01v.993h4v-.993c0-1.11.896-2.01 2-2.01h85.998c1.105 0 2 .897 2 2.01v98.99c0 1.11-.896 2.01-2 2.01h-85.998c-1.105 0-2-.897-2-2.01v-30.993h-4"/><g fill="#f9f9f9"><rect width="82" height="28" x="8" y="12" rx="3"/><rect width="82" height="28" x="8" y="43" rx="3"/></g></g><g fill-rule="nonzero" transform="translate(148 73)"><use fill="#e5e5e5" xlink:href="#6"/><path fill="#6b4fbb" d="m17 17c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3m0 4c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7"/></g><g transform="translate(25 24)"><use xlink:href="#4"/><use fill="#e5e5e5" fill-rule="nonzero" xlink:href="#6"/><use fill="#fff" stroke="#6b4fbb" stroke-width="8" mask="url(#3)" xlink:href="#2"/><use xlink:href="#5"/></g><g transform="translate(107 10)"><use xlink:href="#4"/><use fill="#fc8a51" fill-opacity=".3" fill-rule="nonzero" xlink:href="#6"/><path fill="#6b4fbb" fill-rule="nonzero" d="m16 17c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3m0 4c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7" id="7"/><use xlink:href="#5"/></g><g transform="translate(128 41)"><use xlink:href="#4"/><use fill="#fc8a51" fill-opacity=".3" fill-rule="nonzero" xlink:href="#6"/><use xlink:href="#7"/><path fill="#fc8a51" d="m66.926 12.09v-2.41c0-.665-.437-.888-.975-.507l-6.552 4.631c-.542.383-.539.998 0 1.379l6.552 4.631c.542.383.975.154.975-.507v-2.41h4.874c.668 0 1.2-.538 1.2-1.201v-2.406c0-.668-.537-1.201-1.2-1.201h-4.874"/></g></g></svg> \ No newline at end of file
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index 5c36d2202a6..c73d167303f 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -1,35 +1,35 @@
- if @merge_requests.any?
- %h2.merge-requests-title
- = pluralize(@merge_requests.count, 'Related Merge Request')
- %ul.unstyled-list.related-merge-requests
- - has_any_head_pipeline = @merge_requests.any?(&:head_pipeline_id)
- - @merge_requests.each do |merge_request|
- %li
- %span.merge-request-ci-status
- - if merge_request.head_pipeline
- = render_pipeline_status(merge_request.head_pipeline)
- - elsif has_any_head_pipeline
- = icon('blank fw')
- %span.merge-request-id
- = merge_request.to_reference
- %span.merge-request-info
- %strong
- = link_to merge_request.title, merge_request_path(merge_request), class: "row_title"
- - unless @issue.project.id == merge_request.target_project.id
- in
- - project = merge_request.target_project
- = link_to project.full_name, project_path(project)
+ .card-slim.mt-3
+ .card-header
+ %h2.card-title.mt-0.mb-0.h5.merge-requests-title
+ %span.mr-1.bold
+ = _('Related merge requests')
+ .d-inline-flex.lh-100.align-middle
+ .mr-count-badge
+ .mr-count-badge-count
+ = sprite_icon('merge-request', size: 16, css_class: 'mr-1 text-secondary')
+ = @merge_requests.count
+ %ul.content-list.related-items-list
+ - has_any_head_pipeline = @merge_requests.any?(&:head_pipeline_id)
+ - @merge_requests.each do |merge_request|
+ %li.list-item.py-0.px-0
+ .item-body.issuable-info-container.py-lg-3.px-lg-3.pl-md-3
+ .item-contents
+ .item-title.d-flex.align-items-center.mr-title
+ = render partial: 'projects/issues/merge_requests_status', locals: { merge_request: merge_request, css_class: 'd-none d-xl-block append-right-8' }
+ = link_to merge_request.title, merge_request_path(merge_request), { class: 'mr-title-link'}
+ .item-meta
+ = render partial: 'projects/issues/merge_requests_status', locals: { merge_request: merge_request, css_class: 'd-xl-none d-lg-block append-right-5' }
+ %span.d-flex.align-items-center.append-right-8.mr-item-path.item-path-id.mt-0
+ %span.path-id-text.bold.text-truncate{ data: { toggle: 'tooltip'}, title: merge_request.target_project.full_path }
+ = merge_request.target_project.full_path
+ = merge_request.to_reference
+ %span.mr-ci-status.flex-md-grow-1.justify-content-end.d-flex.ml-md-2
+ - if merge_request.head_pipeline
+ = render_pipeline_status(merge_request.head_pipeline, tooltip_placement: 'bottom')
+ - elsif has_any_head_pipeline
+ = icon('blank fw')
- - if merge_request.merged?
- %span.merge-request-status.prepend-left-10.merged
- Merged
- - elsif merge_request.closed?
- %span.merge-request-status.prepend-left-10.closed
- Closed
- - else
- %span.merge-request-status.prepend-left-10.open
- Open
-
- - if @closed_by_merge_requests.present?
- %li
- = render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
+ - if @closed_by_merge_requests.present?
+ %p
+ = render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
diff --git a/app/views/projects/issues/_merge_requests_status.html.haml b/app/views/projects/issues/_merge_requests_status.html.haml
new file mode 100644
index 00000000000..43e4c8db93f
--- /dev/null
+++ b/app/views/projects/issues/_merge_requests_status.html.haml
@@ -0,0 +1,22 @@
+- time_format = '%b %e, %Y %l:%M%P %Z%z'
+
+- if merge_request.merged?
+ - mr_status_date = merge_request.merged_at
+ - mr_status_title = _('Merged')
+ - mr_status_icon = 'merge'
+ - mr_status_class = 'merged'
+- elsif merge_request.closed?
+ - mr_status_date = merge_request.closed_event&.created_at
+ - mr_status_title = _('Closed')
+ - mr_status_icon = 'issue-close'
+ - mr_status_class = 'closed'
+- else
+ - mr_status_date = merge_request.created_at
+ - mr_status_title = _('Opened')
+ - mr_status_icon = 'issue-open-m'
+ - mr_status_class = 'open'
+
+- mr_status_tooltip = "<div><span class=\"bold\">#{mr_status_title}</span> #{time_ago_in_words(mr_status_date)} ago</div><span class=\"text-tertiary\">#{l(mr_status_date.to_time, format: time_format)}</span>"
+
+%span.mr-status-wrapper.suggestion-help-hover{ class: css_class, data: { toggle: 'tooltip', placement: 'bottom', html: 'true', title: mr_status_tooltip } }
+ = sprite_icon(mr_status_icon, size: 16, css_class: "merge-request-status #{mr_status_class}")
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index e4a0d4b8479..329efa0cdbf 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -1,11 +1,30 @@
-= render 'shared/issuable/feed_buttons'
-
-- if @can_bulk_update
- = button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
-- if show_new_issue_link?(@project)
- = link_to "New issue", new_project_issue_path(@project,
- issue: { assignee_id: finder.assignee.try(:id),
- milestone_id: finder.milestones.first.try(:id) }),
- class: "btn btn-success",
- title: "New issue",
- id: "new_issue_link"
+- show_feed_buttons = local_assigns.fetch(:show_feed_buttons, true)
+- show_import_button = local_assigns.fetch(:show_import_button, true) && can?(current_user, :import_issues, @project)
+- show_export_button = local_assigns.fetch(:show_export_button, true)
+
+.nav-controls.issues-nav-controls
+ - if show_feed_buttons
+ = render 'shared/issuable/feed_buttons'
+
+ .btn-group.append-right-10<
+ - if show_export_button
+ = render_if_exists 'projects/issues/export_csv/button'
+
+ - if show_import_button
+ = render 'projects/issues/import_csv/button'
+
+ - if @can_bulk_update
+ = button_tag _("Edit issues"), class: "btn btn-default append-right-10 js-bulk-update-toggle"
+ - if show_new_issue_link?(@project)
+ = link_to _("New issue"), new_project_issue_path(@project,
+ issue: { assignee_id: finder.assignee.try(:id),
+ milestone_id: finder.milestones.first.try(:id) }),
+ class: "btn btn-success",
+ title: _("New issue"),
+ id: "new_issue_link"
+
+- if show_export_button
+ = render_if_exists 'projects/issues/export_csv/modal'
+
+- if show_import_button
+ = render 'projects/issues/import_csv/modal'
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 5374f4a1de0..fbd70cd1906 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -30,7 +30,7 @@
= icon('check', class: 'icon')
= _('Create merge request and branch')
- %li{ class: [!can_create_merge_request && 'droplab-item-selected'], role: 'button', data: { value: 'create-branch', text: _('Create branch') } }
+ %li{ class: [!can_create_merge_request && 'droplab-item-selected'], role: 'button', data: { value: 'create-branch', text: _('Create branch') } }
.menu-item
= icon('check', class: 'icon')
= _('Create branch')
diff --git a/app/views/projects/issues/import_csv/_button.html.haml b/app/views/projects/issues/import_csv/_button.html.haml
new file mode 100644
index 00000000000..acc2c50294f
--- /dev/null
+++ b/app/views/projects/issues/import_csv/_button.html.haml
@@ -0,0 +1,9 @@
+- type = local_assigns.fetch(:type, :icon)
+
+%button.csv-import-button.btn{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon),
+ data: { toggle: 'modal', target: '.issues-import-modal' } }
+ - if type == :icon
+ = sprite_icon('upload')
+ - else
+ = _('Import CSV')
+
diff --git a/app/views/projects/issues/import_csv/_modal.html.haml b/app/views/projects/issues/import_csv/_modal.html.haml
new file mode 100644
index 00000000000..5339c4325b9
--- /dev/null
+++ b/app/views/projects/issues/import_csv/_modal.html.haml
@@ -0,0 +1,24 @@
+.issues-import-modal.modal
+ .modal-dialog
+ .modal-content
+ = form_tag import_csv_namespace_project_issues_path, multipart: true do
+ .modal-header
+ %h3
+ = _('Import issues')
+ .import-export-svg-container
+ = render 'projects/issues/import_export.svg'
+ %a.close{ href: '#', 'data-dismiss' => 'modal' } ×
+ .modal-body
+ .modal-text
+ %p
+ = _("Your issues will be imported in the background. Once finished, you'll get a confirmation email.")
+ .form-group
+ = label_tag :file, _('Upload CSV file'), class: 'label-bold'
+ %div
+ = file_field_tag :file, accept: '.csv,text/csv', required: true
+ %p.text-secondary
+ = _('It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected.')
+ = _('The maximum file size allowed is %{size}.') % { size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) }
+ .modal-footer
+ %button{ type: 'submit', class: 'btn btn-success', title: _('Import issues'), data: { track_label: "export_issues_csv", track_event: "click_button"} }
+ = _('Import issues')
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 1e7737aeb97..39e9e9171cf 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -11,8 +11,7 @@
%div{ class: (container_class) }
.top-area
= render 'shared/issuable/nav', type: :issues
- .nav-controls
- = render "projects/issues/nav_btns"
+ = render "projects/issues/nav_btns"
= render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
@@ -23,4 +22,4 @@
- if new_issue_email
= render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue'
- else
- = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project)
+ = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project), show_import_button: true
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index 59592abcf6a..afea5268006 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -8,10 +8,6 @@
.nav-controls
- if can?(current_user, :update_build, @project)
- - if @all_builds.running_or_pending.limit(1).any? # rubocop: disable CodeReuse/ActiveRecord
- = link_to 'Cancel running', cancel_all_project_jobs_path(@project),
- data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
-
- unless @repository.gitlab_ci_yml
= link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 56b06374d6d..bb7c297ba1f 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -5,15 +5,10 @@
- subscribed = params[:subscribed]
- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
-- if labels_or_filters && can_admin_label
- - content_for(:header_content) do
- .nav-controls
- = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new"
-
- if labels_or_filters
#promote-label-modal
%div{ class: container_class }
- = render 'shared/labels/nav'
+ = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
.labels-container.prepend-top-10
- if can_admin_label
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index faa070d0389..02d2dbf0d61 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -56,6 +56,7 @@
- if merge_request.assignee
%li
= link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: _('Assigned to :name'))
+ = render_if_exists 'projects/merge_requests/approvals_count', merge_request: merge_request
= render 'shared/issuable_meta_data', issuable: merge_request
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index ebd3229e42b..56c043b2b3d 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,6 +1,6 @@
= form_for [@project.namespace.becomes(Namespace), @project, @milestone],
- html: {class: 'milestone-form common-note-form js-quick-submit js-requires-input'},
- data: { markdown_version: @milestone.cached_markdown_version } do |f|
+ html: { class: 'milestone-form common-note-form js-quick-submit js-requires-input' },
+ data: { markdown_version: @milestone.cached_markdown_version } do |f|
= form_errors(@milestone)
.row
.col-md-6
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 57f3c640696..ba853ef0708 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -6,6 +6,7 @@
= render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
.nav-controls
+ = render 'shared/milestones/search_form'
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
= link_to new_project_milestone_path(@project), class: "btn btn-success qa-new-project-milestone", title: 'New milestone' do
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index a760d02c4c3..8275996b522 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -33,7 +33,7 @@
%span.d-block.d-sm-none Blank
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab', track_label: 'create_from_template', track_event: "click_tab" }, role: 'tab' }
- %span.d-none.d-sm-block Create from template
+ %span.d-none.d-sm-block.qa-project-create-from-template-tab Create from template
%span.d-block.d-sm-none Template
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' }
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index 259979417e0..1121cf06b5c 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -34,7 +34,7 @@
= n_('Reveal value', 'Reveal values', @schedule.variables.size)
.form-group.row
.col-md-9
- = f.label :active, s_('PipelineSchedules|Activated'), class: 'label-bold'
+ = f.label :active, s_('PipelineSchedules|Activated'), class: 'label-bold'
%div
= f.check_box :active, required: false, value: @schedule.active?
= _('Active')
diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml
index 3f05e06b0c6..b5d397e3065 100644
--- a/app/views/projects/project_members/_groups.html.haml
+++ b/app/views/projects/project_members/_groups.html.haml
@@ -1,7 +1,6 @@
.card.project-members-groups
.card-header
- Groups with access to
- %strong= @project.name
+ = _("Groups with access to <strong>%{project_name}</strong>").html_safe % { project_name: sanitize_project_name(@project.name) }
%span.badge.badge-pill= group_links.size
%ul.content-list.members-list
= render partial: 'shared/members/group', collection: group_links, as: :group_link
diff --git a/app/views/projects/project_members/_new_project_group.html.haml b/app/views/projects/project_members/_new_project_group.html.haml
index 88e68f89024..079811e4e79 100644
--- a/app/views/projects/project_members/_new_project_group.html.haml
+++ b/app/views/projects/project_members/_new_project_group.html.haml
@@ -10,8 +10,9 @@
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
= icon('chevron-down')
.form-text.text-muted.append-bottom-10
- = link_to _("Read more"), help_page_path("user/permissions")
- about role permissions
+ - permissions_docs_path = help_page_path('user/permissions')
+ - link_start = %q{<a href="%{url}">}.html_safe % { url: permissions_docs_path }
+ = _("%{link_start}Read more%{link_end} about role permissions").html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.form-group
= label_tag :expires_at, _('Access expiration date'), class: 'label-bold'
.clearable-input
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 1de7d9c6957..0590578c3fe 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -2,20 +2,21 @@
.col-sm-12
= form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f|
.form-group
- = label_tag :user_ids, "Select members to invite", class: "label-bold"
+ = label_tag :user_ids, _("Select members to invite"), class: "label-bold"
= users_select_tag(:user_ids, multiple: true, class: "input-clamp qa-member-select-input", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
.form-group
- = label_tag :access_level, "Choose a role permission", class: "label-bold"
+ = label_tag :access_level, _("Choose a role permission"), class: "label-bold"
.select-wrapper
= select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select select-control"
= icon('chevron-down')
.form-text.text-muted.append-bottom-10
- = link_to "Read more", help_page_path("user/permissions")
- about role permissions
+ - permissions_docs_path = help_page_path('user/permissions')
+ - link_start = %q{<a href="%{url}">}.html_safe % { url: permissions_docs_path }
+ = _("%{link_start}Read more%{link_end} about role permissions").html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.form-group
.clearable-input
- = label_tag :expires_at, 'Access expiration date', class: 'label-bold'
+ = label_tag :expires_at, _('Access expiration date'), class: 'label-bold'
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
- = f.submit "Add to project", class: "btn btn-success qa-add-member-button"
- = link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
+ = f.submit _("Add to project"), class: "btn btn-success qa-add-member-button"
+ = link_to _("Import"), import_project_project_members_path(@project), class: "btn btn-default", title: _("Import members from another project")
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 9682f8ac922..e0dd386fc5d 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -4,14 +4,13 @@
.card
.card-header.flex-project-members-panel
%span.flex-project-title
- Members of
- %strong= project.name
+ = _("Members of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize_project_name(project.name) }
%span.badge.badge-pill= members.total_count
- = form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
+ = form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
.position-relative
- = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
- %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
+ = search_field_tag :search, params[:search], { placeholder: _('Find existing members by name'), class: 'form-control', spellcheck: false }
+ %button.member-search-btn{ type: "submit", "aria-label" => _("Submit search") }
= icon("search")
= render 'shared/members/sort_dropdown'
%ul.content-list.members-list.qa-members-list
diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml
index 8b93e81cd31..bcca943de6a 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -1,15 +1,15 @@
-- page_title "Import members"
+- page_title _("Import members")
%h3.page-title
- Import members from another project
+ = _("Import members from another project")
%p.light
- Only project members will be imported. Group members will be skipped.
+ = _("Only project members will be imported. Group members will be skipped.")
%hr
= form_tag apply_import_project_project_members_path(@project), method: 'post' do
.form-group.row
- = label_tag :source_project_id, "Project", class: 'col-form-label col-sm-2'
+ = label_tag :source_project_id, _("Project"), class: 'col-form-label col-sm-2'
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(@projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
- = button_tag 'Import project members', class: "btn btn-success"
- = link_to "Cancel", project_project_members_path(@project), class: "btn btn-cancel"
+ = button_tag _('Import project members'), class: "btn btn-success"
+ = link_to _("Cancel"), project_project_members_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 14ed3345765..242ff91f539 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,39 +1,34 @@
-- page_title "Members"
+- page_title _("Members")
.row.prepend-top-default
.col-lg-12
%h4
- Project members
+ = _("Project members")
- if can?(current_user, :admin_project_member, @project)
%p
- You can invite a new member to
- %strong= @project.name
- or invite another group.
+ = _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.").html_safe % { project_name: sanitize_project_name(@project.name) }
- else
%p
- Members can be added by project
- %i Maintainers
- or
- %i Owners
+ = _("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
+ %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?
%li.nav-tab{ role: 'presentation' }
- %a.nav-link{ href: '#invite-group-pane', id: 'invite-group-tab', data: { toggle: 'tab' }, role: 'tab' } Invite group
+ %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'
+ = 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'
+ = render 'projects/project_members/new_project_group', tab_title: _('Invite group')
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
.clearfix
%h5.member.existing-title
- Existing members and groups
+ = _("Existing members and groups")
- if @group_links.any?
= render 'projects/project_members/groups', group_links: @group_links
diff --git a/app/views/projects/project_templates/_built_in_templates.html.haml b/app/views/projects/project_templates/_built_in_templates.html.haml
index 5b4d8927045..2a0ce4bd16b 100644
--- a/app/views/projects/project_templates/_built_in_templates.html.haml
+++ b/app/views/projects/project_templates/_built_in_templates.html.haml
@@ -12,6 +12,6 @@
%a.btn.btn-default.append-right-10{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: template.name } }
= _("Preview")
%label.btn.btn-success.template-button.choose-template.append-bottom-0{ for: template.name }
- %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "create_from_template", track_property: "template_use", track_event: "click_button" } }
+ %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "create_from_template", track_property: "template_use", track_event: "click_button" } }
%span
= _("Use template")
diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
index 81b07af22ad..bb7998f739d 100644
--- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
@@ -15,7 +15,7 @@
= link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
- else
- (branch was removed from repository)
+ (branch was deleted from repository)
= yield
diff --git a/app/views/projects/releases/index.html.haml b/app/views/projects/releases/index.html.haml
index f01d4e826b9..28bb4e032eb 100644
--- a/app/views/projects/releases/index.html.haml
+++ b/app/views/projects/releases/index.html.haml
@@ -2,4 +2,4 @@
- page_title _('Releases')
%div{ class: container_class }
- #js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases') } }
+ #js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases/index') } }
diff --git a/app/views/projects/runners/_group_runners.html.haml b/app/views/projects/runners/_group_runners.html.haml
index a6c16c70313..a24ada53bac 100644
--- a/app/views/projects/runners/_group_runners.html.haml
+++ b/app/views/projects/runners/_group_runners.html.haml
@@ -32,6 +32,6 @@
- else
%h4.underlined-title
- = _('Available group Runners : %{runners}').html_safe % { runners: @group_runners.count }
+ = _('Available group Runners: %{runners}').html_safe % { runners: @group_runners.count }
%ul.bordered-list
= render partial: 'projects/runners/runner', collection: @group_runners, as: :runner
diff --git a/app/views/projects/serverless/functions/index.html.haml b/app/views/projects/serverless/functions/index.html.haml
index f650fa0f38f..635580eac5c 100644
--- a/app/views/projects/serverless/functions/index.html.haml
+++ b/app/views/projects/serverless/functions/index.html.haml
@@ -5,7 +5,7 @@
- status_path = project_serverless_functions_path(@project, format: :json)
- clusters_path = project_clusters_path(@project)
-.serverless-functions-page.js-serverless-functions-page{ data: { status_path: status_path, installed: @installed, clusters_path: clusters_path, help_path: help_page_path('user/project/clusters/serverless/index') } }
+.serverless-functions-page.js-serverless-functions-page{ data: { status_path: status_path, installed: @installed, clusters_path: clusters_path, help_path: help_page_path('user/project/clusters/serverless/index') } }
%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] }
.js-serverless-functions-notice
diff --git a/app/views/projects/serverless/functions/show.html.haml b/app/views/projects/serverless/functions/show.html.haml
new file mode 100644
index 00000000000..29737b7014a
--- /dev/null
+++ b/app/views/projects/serverless/functions/show.html.haml
@@ -0,0 +1,16 @@
+- @no_container = true
+- @content_class = "limit-container-width" unless fluid_layout
+
+- add_to_breadcrumbs('Serverless', project_serverless_functions_path(@project))
+
+- page_title @service[:name]
+
+.serverless-function-details-page.js-serverless-function-details-page{ data: { service: @service.as_json } }
+%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] }
+ .top-area.adjust
+ .serverless-function-details#js-serverless-function-details
+
+ .js-serverless-function-notice
+ .flash-container
+
+ .function-holder.js-function-holder.input-group
diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml
new file mode 100644
index 00000000000..4911e8d3770
--- /dev/null
+++ b/app/views/projects/settings/operations/_error_tracking.html.haml
@@ -0,0 +1,30 @@
+- return unless can?(current_user, :read_environment, @project)
+
+- setting = error_tracking_setting
+
+%section.settings.expanded.border-0.no-animate
+ .settings-header
+ %h4
+ = _('Error Tracking')
+ %p
+ = _('To link Sentry to GitLab, enter your Sentry URL and Auth Token.')
+ .settings-content
+ = form_for @project, url: project_settings_operations_path(@project), method: :patch do |f|
+ = form_errors(@project)
+ .form-group
+ = f.fields_for :error_tracking_setting_attributes, setting do |form|
+ .form-check.form-group
+ = form.check_box :enabled, class: 'form-check-input'
+ = form.label :enabled, _('Active'), class: 'form-check-label'
+ .form-group
+ = form.label :api_url, _('Sentry API URL'), class: 'label-bold'
+ = form.url_field :api_url, class: 'form-control', placeholder: _('http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/')
+ %p.form-text.text-muted
+ = _('Enter your Sentry API URL')
+ .form-group
+ = form.label :token, _('Auth Token'), class: 'label-bold'
+ = form.text_field :token, class: 'form-control'
+ %p.form-text.text-muted
+ = _('Find and manage Auth Tokens in your Sentry account settings page.')
+
+ = f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
new file mode 100644
index 00000000000..b36fa9a5f51
--- /dev/null
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -0,0 +1,5 @@
+- @content_class = 'limit-container-width' unless fluid_layout
+- page_title _('Operations')
+
+= render 'projects/settings/operations/error_tracking', expanded: true
+= render_if_exists 'projects/settings/operations/tracing'
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index f55202c2c5f..cc203cfad86 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -28,7 +28,7 @@
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
- if can?(current_user, :push_code, @project)
- = link_to edit_project_tag_release_path(@project, tag.name), class: 'btn has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do
+ = link_to edit_project_tag_release_path(@project, tag.name), class: 'btn btn-edit has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do
= icon("pencil")
- if can?(current_user, :admin_project, @project)
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 15a960f81b8..feeaf799f51 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -20,7 +20,7 @@
.nav-controls.controls-flex
- if can?(current_user, :push_code, @project)
- = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do
+ = 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")
= link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do
= icon('files-o')
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 4e9a119ac66..ec8e5234bd4 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -40,27 +40,24 @@
#{ _('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,
+ - 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)
+ - 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.",
+ - 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)
+ - 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.",
+ - 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)
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
= link_to fork_path, method: :post do
#{ _('New directory') }
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index ff9a7b53a86..aaf9b973cda 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -6,75 +6,75 @@
- if project_search_tabs?(:blobs)
%li{ class: active_when(@scope == 'blobs') }
= link_to search_filter_path(scope: 'blobs') do
- Code
+ = _("Code")
%span.badge.badge-pill
= @search_results.blobs_count
- if project_search_tabs?(:issues)
%li{ class: active_when(@scope == 'issues') }
= link_to search_filter_path(scope: 'issues') do
- Issues
+ = _("Issues")
%span.badge.badge-pill
= limited_count(@search_results.limited_issues_count)
- if project_search_tabs?(:merge_requests)
%li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do
- Merge requests
+ = _("Merge requests")
%span.badge.badge-pill
= limited_count(@search_results.limited_merge_requests_count)
- if project_search_tabs?(:milestones)
%li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do
- Milestones
+ = _("Milestones")
%span.badge.badge-pill
= limited_count(@search_results.limited_milestones_count)
- if project_search_tabs?(:notes)
%li{ class: active_when(@scope == 'notes') }
= link_to search_filter_path(scope: 'notes') do
- Comments
+ = _("Comments")
%span.badge.badge-pill
= limited_count(@search_results.limited_notes_count)
- if project_search_tabs?(:wiki)
%li{ class: active_when(@scope == 'wiki_blobs') }
= link_to search_filter_path(scope: 'wiki_blobs') do
- Wiki
+ = _("Wiki")
%span.badge.badge-pill
= @search_results.wiki_blobs_count
- if project_search_tabs?(:commits)
%li{ class: active_when(@scope == 'commits') }
= link_to search_filter_path(scope: 'commits') do
- Commits
+ = _("Commits")
%span.badge.badge-pill
= @search_results.commits_count
- elsif @show_snippets
%li{ class: active_when(@scope == 'snippet_blobs') }
= link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
- Snippet Contents
+ = _("Snippet Contents")
%span.badge.badge-pill
= @search_results.snippet_blobs_count
%li{ class: active_when(@scope == 'snippet_titles') }
= link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
- Titles and Filenames
+ = _("Titles and Filenames")
%span.badge.badge-pill
= @search_results.snippet_titles_count
- else
%li{ class: active_when(@scope == 'projects') }
= link_to search_filter_path(scope: 'projects') do
- Projects
+ = _("Projects")
%span.badge.badge-pill
= limited_count(@search_results.limited_projects_count)
%li{ class: active_when(@scope == 'issues') }
= link_to search_filter_path(scope: 'issues') do
- Issues
+ = _("Issues")
%span.badge.badge-pill
= limited_count(@search_results.limited_issues_count)
%li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do
- Merge requests
+ = _("Merge requests")
%span.badge.badge-pill
= limited_count(@search_results.limited_merge_requests_count)
%li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do
- Milestones
+ = _("Milestones")
%span.badge.badge-pill
= limited_count(@search_results.limited_milestones_count)
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 6837f64f132..c8b6a3258ab 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -3,31 +3,31 @@
- if params[:project_id].present?
= hidden_field_tag :project_id, params[:project_id]
.dropdown
- %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:", group_id: params[:group_id] } }
+ %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Group:'), group_id: params[:group_id] } }
%span.dropdown-toggle-text
- Group:
+ = _("Group:")
- if @group.present?
= @group.name
- else
- Any
+ = _("Any")
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right
- = dropdown_title("Filter results by group")
- = dropdown_filter("Search groups")
+ = dropdown_title(_("Filter results by group"))
+ = dropdown_filter(_("Search groups"))
= dropdown_content
= dropdown_loading
.dropdown.project-filter
- %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } }
+ %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Project:') } }
%span.dropdown-toggle-text
- Project:
+ = _("Project:")
- if @project.present?
= @project.full_name
- else
- Any
+ = _("Any")
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right
- = dropdown_title("Filter results by project")
- = dropdown_filter("Search projects")
+ = dropdown_title(_("Filter results by project"))
+ = dropdown_filter(_("Search projects"))
= dropdown_content
= dropdown_loading
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index a4a5cec1314..4af0c6bf84a 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -4,12 +4,12 @@
.search-holder
.search-field-holder
- = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
+ = search_field_tag :search, params[:search], placeholder: _("Search for projects, issues, etc."), class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
= icon("search", class: "search-icon")
%button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" }
= icon("times-circle")
%span.sr-only
- Clear search
+ = _("Clear search")
- unless params[:snippets].eql? 'true'
= render 'filter'
- = button_tag "Search", class: "btn btn-success btn-search"
+ = button_tag _("Search"), class: "btn btn-success btn-search"
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index ab56f48ba4d..be7a2436d16 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,4 +1,4 @@
-- if @search_objects.empty?
+- if @search_objects.to_a.empty?
= render partial: "search/results/empty"
- else
.row-content-block
@@ -6,9 +6,11 @@
= search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
- in project #{link_to @project.full_name, [@project.namespace.becomes(Namespace), @project]}
+ - link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project])
+ = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
- elsif @group
- in group #{link_to @group.name, @group}
+ - link_to_group = link_to(@group.name, @group)
+ = _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
.results.prepend-top-10
- if @scope == 'commits'
diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml
index 821a39d61f5..9d15995bb51 100644
--- a/app/views/search/results/_empty.html.haml
+++ b/app/views/search/results/_empty.html.haml
@@ -2,5 +2,5 @@
.search_glyph
%h4
= icon('search')
- We couldn't find any results matching
+ = _("We couldn't find any results matching")
%code= @search_term
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index c413c3d4abb..796782035f2 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -4,7 +4,7 @@
= link_to [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
+ %span.badge.badge-danger.prepend-left-5= _("Closed")
.float-right ##{issue.iid}
- if issue.description.present?
.description.term
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 519176af108..f0e0af11f27 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -3,9 +3,9 @@
= link_to [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
+ %span.badge.badge-primary.prepend-left-5= _("Merged")
- elsif merge_request.closed?
- %span.badge.badge-danger.prepend-left-5 Closed
+ %span.badge.badge-danger.prepend-left-5= _("Closed")
.float-right= merge_request.to_reference
- if merge_request.description.present?
.description.term
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index e4ab7b0541f..6717939d034 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -6,14 +6,14 @@
%h5.note-search-caption.str-truncated
%i.fa.fa-comment
= link_to_member(project, note.author, avatar: false)
- commented on
- = link_to project.full_name, project
+ - link_to_project = link_to(project.full_name, project)
+ = _("commented on %{link_to_project}").html_safe % { link_to_project: link_to_project }
&middot;
- if note.for_commit?
- = link_to_if(noteable_identifier, "Commit #{truncate_sha(note.commit_id)}", note_url) do
+ = link_to_if(noteable_identifier, _("Commit %{commit_id}") % { commit_id: truncate_sha(note.commit_id) }, note_url) do
= truncate_sha(note.commit_id)
- %span.light Commit deleted
+ %span.light= _("Commit deleted")
- else
%span #{note.noteable_type.titleize} ##{noteable_identifier}
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index b4ecd7bbae9..e0130f9a4b5 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -24,7 +24,7 @@
= markup(snippet.file_name, chunk[:data], legacy_render_context(params))
- else
.file-content.code
- .nothing-here-block Empty file
+ .nothing-here-block= _("Empty file")
- else
.file-content.code.js-syntax-highlight
.line-numbers
@@ -42,4 +42,4 @@
= highlight(snippet.file_name, chunk[:data])
- else
.file-content.code
- .nothing-here-block Empty file
+ .nothing-here-block= _("Empty file")
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 9c8afb2165b..1e01088d9e6 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -5,7 +5,7 @@
- if snippet_title.private?
%span.badge.badge-gray
%i.fa.fa-lock
- private
+ = _("private")
%span.cgray.monospace.tiny.float-right.term
= snippet_title.file_name
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 499697f2777..3260d05f509 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,5 +1,5 @@
- @hide_top_links = true
-- breadcrumb_title "Search"
+- breadcrumb_title _("Search")
- page_title @search_term
.prepend-top-default
diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml
index 7d3e243495f..ca392e1adfc 100644
--- a/app/views/sent_notifications/unsubscribe.html.haml
+++ b/app/views/sent_notifications/unsubscribe.html.haml
@@ -1,17 +1,16 @@
- noteable = @sent_notification.noteable
- noteable_type = @sent_notification.noteable_type.titleize.downcase
- noteable_text = %(#{noteable.title} (#{noteable.to_reference}))
-- page_title "Unsubscribe", noteable_text, noteable_type.pluralize, @sent_notification.project.full_name
+- page_title _("Unsubscribe"), noteable_text, noteable_type.pluralize, @sent_notification.project.full_name
%h3.page-title
- Unsubscribe from #{noteable_type}
+ = _("Unsubscribe from %{type}") % { type: noteable_type }
%p
- = succeed '?' do
- Are you sure you want to unsubscribe from the #{noteable_type}:
- = link_to noteable_text, url_for([@sent_notification.project.namespace.becomes(Namespace), @sent_notification.project, noteable])
+ - link_to_noteable_text = link_to(noteable_text, url_for([@sent_notification.project.namespace.becomes(Namespace), @sent_notification.project, noteable]))
+ = _("Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?").html_safe % { type: noteable_type, link_to_noteable_text: link_to_noteable_text }
%p
- = link_to 'Unsubscribe', unsubscribe_sent_notification_path(@sent_notification, force: true),
+ = link_to _('Unsubscribe'), unsubscribe_sent_notification_path(@sent_notification, force: true),
class: 'btn btn-primary append-right-10'
- = link_to 'Cancel', new_user_session_path, class: 'btn append-right-10'
+ = link_to _('Cancel'), new_user_session_path, class: 'btn append-right-10'
diff --git a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
index 0d0a3c1aa64..422be28737c 100644
--- a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
+++ b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml
@@ -1,4 +1,4 @@
-- if show_auto_devops_implicitly_enabled_banner?(project)
+- if show_auto_devops_implicitly_enabled_banner?(project, current_user)
.auto-devops-implicitly-enabled-banner.alert.alert-warning
- more_information_link = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank', class: 'alert-link'
- auto_devops_message = s_("AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}") % { more_information_link: more_information_link }
diff --git a/app/views/shared/_flash_user_callout.html.haml b/app/views/shared/_flash_user_callout.html.haml
new file mode 100644
index 00000000000..fe175195e66
--- /dev/null
+++ b/app/views/shared/_flash_user_callout.html.haml
@@ -0,0 +1,11 @@
+- callout_data = { uid: "callout_feature_#{feature_name}_dismissed", feature_id: feature_name, dismiss_endpoint: user_callouts_path }
+- extra_flash_class = local_assigns.fetch(:extra_flash_class, nil)
+
+.flash-container.flash-container-page.user-callout{ data: callout_data }
+ -# We currently only support `alert`, `warning`, `notice`, `success`
+ %div{ class: "flash-#{flash_type}" }
+ %div{ class: "#{(container_class unless fluid_layout)} #{(extra_flash_class unless @no_container)} #{@content_class}" }
+ %span= message
+ %button.btn.btn-default.close.js-close{ type: 'button',
+ 'aria-label' => _('Dismiss') }
+ = sprite_icon('close', css_class: 'dismiss-icon')
diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml
index 28b34e38b15..8607f87ce0b 100644
--- a/app/views/shared/_mini_pipeline_graph.html.haml
+++ b/app/views/shared/_mini_pipeline_graph.html.haml
@@ -7,7 +7,6 @@
.stage-container.dropdown{ class: klass }
%button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_ajax_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
= sprite_icon(icon_status)
- = icon('caret-down')
%ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
%li.js-builds-dropdown-list.scrollable-menu
diff --git a/app/views/shared/_personal_access_tokens_created_container.html.haml b/app/views/shared/_personal_access_tokens_created_container.html.haml
index 3150d39b84a..a8d3de66418 100644
--- a/app/views/shared/_personal_access_tokens_created_container.html.haml
+++ b/app/views/shared/_personal_access_tokens_created_container.html.haml
@@ -6,7 +6,7 @@
= container_title
.form-group
.input-group
- = text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-token-help-block"
+ = text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: "qa-created-personal-access-token form-control js-select-on-focus", 'aria-describedby' => "created-token-help-block"
%span.input-group-append
= clipboard_button(text: new_token_value, title: clipboard_button_title, placement: "left", class: "input-group-text btn-default btn-clipboard")
%span#created-token-help-block.form-text.text-muted.text-danger Make sure you save it - you won't be able to access it again.
diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml
index f4df7bdcd83..0891b3459ec 100644
--- a/app/views/shared/_personal_access_tokens_form.html.haml
+++ b/app/views/shared/_personal_access_tokens_form.html.haml
@@ -12,7 +12,7 @@
.row
.form-group.col-md-6
= f.label :name, class: 'label-bold'
- = f.text_field :name, class: "form-control", required: true
+ = f.text_field :name, class: "form-control qa-personal-access-token-name-field", required: true
.row
.form-group.col-md-6
@@ -26,4 +26,4 @@
= render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: token, scopes: scopes
.prepend-top-default
- = f.submit "Create #{type} token", class: "btn btn-success"
+ = f.submit "Create #{type} token", class: "btn btn-success qa-create-token-button"
diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml
index 2efd03d4867..49f3aae0f98 100644
--- a/app/views/shared/_personal_access_tokens_table.html.haml
+++ b/app/views/shared/_personal_access_tokens_table.html.haml
@@ -29,7 +29,7 @@
%span.token-never-expires-label Never
%td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
- path = impersonation ? revoke_admin_user_impersonation_token_path(token.user, token) : revoke_profile_personal_access_token_path(token)
- %td= link_to "Revoke", path, method: :put, class: "btn btn-danger float-right", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." }
+ %td= link_to "Revoke", path, method: :put, class: "btn btn-danger float-right qa-revoke-button", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." }
- else
.settings-message.text-center
This user has no active #{type} Tokens.
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index c6c5cadc3f5..307a0919a4c 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -1,6 +1,6 @@
.board{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
":data-id" => "list.id" }
- .board-inner
+ .board-inner.d-flex.flex-column
%header.board-header{ ":class" => '{ "has-border": list.label && list.label.color }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", "@click" => "toggleExpanded($event)" }
%h3.board-title.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset) }' }
%i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable",
diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml
index 1ff956649ed..c9ff63f8c45 100644
--- a/app/views/shared/boards/components/_sidebar.html.haml
+++ b/app/views/shared/boards/components/_sidebar.html.haml
@@ -1,4 +1,4 @@
-%board-sidebar{ "inline-template" => true, ":current-user" => (UserSerializer.new.represent(current_user) || {}).to_json }
+%board-sidebar{ "inline-template" => true, ":current-user" => (UserSerializer.new.represent(current_user) || {}).to_json }
%transition{ name: "boards-sidebar-slide" }
%aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" }
.issuable-sidebar
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 0ddc56dc6c3..9173b802dd4 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -1,6 +1,11 @@
- button_path = local_assigns.fetch(:button_path, false)
- project_select_button = local_assigns.fetch(:project_select_button, false)
+- show_import_button = local_assigns.fetch(:show_import_button, false) && can?(current_user, :import_issues, @project)
- has_button = button_path || project_select_button
+- closed_issues_count = issuables_count_for_state(:issues, :closed)
+- opened_issues_count = issuables_count_for_state(:issues, :opened)
+- is_opened_state = params[:state] == 'opened'
+- is_closed_state = params[:state] == 'closed'
.row.empty-state
.col-12
@@ -13,6 +18,20 @@
= _("Sorry, your filter produced no results")
%p.text-center
= _("To widen your search, change or remove filters above")
+ - if show_new_issue_link?(@project)
+ .text-center
+ = link_to _("New issue"), new_project_issue_path(@project), class: "btn btn-success", title: _("New issue"), id: "new_issue_body_link"
+ - elsif is_opened_state && opened_issues_count == 0 && closed_issues_count > 0
+ %h4.text-center
+ = _("There are no open issues")
+ %p.text-center
+ = _("To keep this project going, create a new issue")
+ - if show_new_issue_link?(@project)
+ .text-center
+ = link_to _("New issue"), new_project_issue_path(@project), class: "btn btn-success", title: _("New issue"), id: "new_issue_body_link"
+ - elsif is_closed_state && opened_issues_count > 0 && closed_issues_count == 0
+ %h4.text-center
+ = _("There are no closed issues")
- elsif current_user
%h4
= _("The Issue Tracker is the place to add things that need to be improved or solved in a project")
@@ -21,12 +40,20 @@
- if has_button
.text-center
- if project_select_button
- = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues, with_feature_enabled: 'issues'
+ = render 'shared/new_project_item_select', path: 'issues/new', label: _('New issue'), type: :issues, with_feature_enabled: 'issues'
- else
- = link_to 'New issue', button_path, class: 'btn btn-success', title: 'New issue', id: 'new_issue_link'
+ = link_to _('New issue'), button_path, class: 'btn btn-success', title: _('New issue'), id: 'new_issue_link'
+
+ - if show_import_button
+ = render 'projects/issues/import_csv/button', type: :text
+
- else
%h4.text-center= _("There are no issues to show")
%p
= _("The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.")
.text-center
= link_to _('Register / Sign In'), new_user_session_path, class: 'btn btn-success'
+
+- if show_import_button
+ = render 'projects/issues/import_csv/modal'
+
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
index bee26cd8312..a739103641e 100644
--- a/app/views/shared/empty_states/_labels.html.haml
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -1,6 +1,6 @@
.row.empty-state.labels
.col-12
- .svg-content
+ .svg-content.qa-label-svg
= image_tag 'illustrations/labels.svg'
.col-12
.text-content
diff --git a/app/views/shared/empty_states/_merge_requests.html.haml b/app/views/shared/empty_states/_merge_requests.html.haml
index 06ceb9738bc..be5b1c6b6ce 100644
--- a/app/views/shared/empty_states/_merge_requests.html.haml
+++ b/app/views/shared/empty_states/_merge_requests.html.haml
@@ -1,6 +1,11 @@
- button_path = local_assigns.fetch(:button_path, false)
- project_select_button = local_assigns.fetch(:project_select_button, false)
- has_button = button_path || project_select_button
+- closed_merged_count = issuables_count_for_state(:merged, :closed)
+- opened_merged_count = issuables_count_for_state(:merged, :opened)
+- is_opened_state = params[:state] == 'opened'
+- is_closed_state = params[:state] == 'closed'
+- can_create_merge_request = merge_request_source_project_for_project(@project)
.row.empty-state.merge-requests
.col-12
@@ -13,6 +18,20 @@
= _("Sorry, your filter produced no results")
%p.text-center
= _("To widen your search, change or remove filters above")
+ .text-center
+ - if can_create_merge_request
+ = link_to _("New merge request"), project_new_merge_request_path(@project), class: "btn btn-success", title: _("New merge request"), id: "new_merge_request_body_link"
+ - elsif is_opened_state && opened_merged_count == 0 && closed_merged_count > 0
+ %h4.text-center
+ = _("There are no open merge requests")
+ %p.text-center
+ = _("To keep this project going, create a new merge request")
+ .text-center
+ - if can_create_merge_request
+ = link_to _("New merge request"), project_new_merge_request_path(@project), class: "btn btn-success", title: _("New merge request"), id: "new_merge_request_body_link"
+ - elsif is_closed_state && opened_merged_count > 0 && closed_merged_count == 0
+ %h4.text-center
+ = _("There are no closed merge requests")
- else
%h4
= _("Merge requests are a place to propose changes you've made to a project and discuss those changes with others")
diff --git a/app/views/shared/issuable/_board_create_list_dropdown.html.haml b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
index 4597d9439fa..fd413bd68c8 100644
--- a/app/views/shared/issuable/_board_create_list_dropdown.html.haml
+++ b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
@@ -1,7 +1,7 @@
.dropdown.prepend-left-10#js-add-list
%button.btn.btn-success.btn-inverted.js-new-board-list{ type: "button", data: board_list_data }
Add list
- .dropdown-menu.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
+ .dropdown-menu.dropdown-extended-height.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
- if can?(current_user, :admin_label, board.parent)
= render partial: "shared/issuable/label_page_create"
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index ca02424215c..909eb738f95 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -2,7 +2,7 @@
%aside.issues-bulk-update.js-right-sidebar.right-sidebar{ "aria-live" => "polite", data: { 'signed-in': current_user.present? } }
.issuable-sidebar.hidden
- = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: "bulk-update" do
+ = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: "bulk-update" do
.block.issuable-sidebar-header
.filter-item.inline.update-issues-btn.float-left
= button_tag "Update all", class: "btn update-selected-issues btn-info", disabled: true
@@ -50,4 +50,3 @@
= hidden_field_tag "update[issuable_ids]", []
= hidden_field_tag :state_event, params[:state_event]
-
diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml
index d4834090413..83f60fa6fe2 100644
--- a/app/views/shared/issuable/_feed_buttons.html.haml
+++ b/app/views/shared/issuable/_feed_buttons.html.haml
@@ -1,4 +1,4 @@
-= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to RSS feed' do
+= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to RSS feed') do
= icon('rss')
-= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to calendar' do
+= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do
= custom_icon('icon_calendar')
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
deleted file mode 100644
index 2ca4657851c..00000000000
--- a/app/views/shared/issuable/_filter.html.haml
+++ /dev/null
@@ -1,32 +0,0 @@
-.issues-filters
- .issues-details-filters.row-content-block.second-block
- = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
- - if params[:search].present?
- = hidden_field_tag :search, params[:search]
- .issues-other-filters
- .filter-item.inline
- - if params[:author_id].present?
- = hidden_field_tag(:author_id, params[:author_id])
- = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
- placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user&.username, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
-
- .filter-item.inline
- - if params[:assignee_id].present?
- = hidden_field_tag(:assignee_id, params[:assignee_id])
- = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
- placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user&.username, null_user: true, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
-
- .filter-item.inline.milestone-filter
- = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true
-
- .filter-item.inline.labels-filter
- = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
-
- - unless @no_filters_set
- .float-right
- = render 'shared/issuable/sort_dropdown'
-
- - has_labels = @labels && @labels.any?
- .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) }
- - if has_labels
- = render 'shared/labels_row', labels: @labels
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index c5cce1823f0..1a59055f652 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -25,7 +25,7 @@
.value.hide-collapsed
- if issuable_sidebar[:assignee]
= link_to_member(@project, assignee, size: 32, extra_class: 'bold') do
- - if issuable_sidebar[:assignee][:can_merge]
+ - unless issuable_sidebar[:assignee][:can_merge]
%span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') }
= icon('exclamation-triangle', 'aria-hidden': 'true')
%span.username
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index 1881875b7c0..f0c4acdd07f 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -10,7 +10,7 @@
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input'
= label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do
- Remove source branch when merge request is accepted.
+ Delete source branch when merge request is accepted.
.form-group.row
.col-sm-10.offset-sm-2
@@ -19,4 +19,4 @@
= check_box_tag 'merge_request[squash]', '1', issuable.squash, class: 'form-check-input'
= label_tag 'merge_request[squash]', class: 'form-check-label' do
Squash commits when merge request is accepted.
- = link_to 'About this feature', help_page_path('user/project/merge_requests/squash_and_merge')
+ = link_to icon('question-circle'), help_page_path('user/project/merge_requests/squash_and_merge'), target: '_blank'
diff --git a/app/views/shared/labels/_nav.html.haml b/app/views/shared/labels/_nav.html.haml
index 98572db738b..e69246dd0eb 100644
--- a/app/views/shared/labels/_nav.html.haml
+++ b/app/views/shared/labels/_nav.html.haml
@@ -18,3 +18,7 @@
%button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') }
= icon("search")
= render 'shared/labels/sort_dropdown'
+ - if labels_or_filters && can_admin_label && @project
+ = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new"
+ - if labels_or_filters && can_admin_label && @group
+ = link_to _('New label'), new_group_label_path(@group), class: "btn btn-success qa-label-create-new"
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 6b3841ebbc4..2db1f67a793 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -10,7 +10,7 @@
- if user
= image_tag avatar_icon_for_user(user, 40), class: "avatar s40", alt: ''
.user-info
- = link_to user.name, user_path(user), class: 'member'
+ = link_to user.name, user_path(user), class: 'member js-user-link', data: { user_id: user.id }
= user_status(user)
%span.cgray= user.to_reference
diff --git a/app/views/shared/milestones/_search_form.html.haml b/app/views/shared/milestones/_search_form.html.haml
new file mode 100644
index 00000000000..403a0224a85
--- /dev/null
+++ b/app/views/shared/milestones/_search_form.html.haml
@@ -0,0 +1,8 @@
+= form_tag request.path, method: :get do |f|
+ = search_field_tag :search_title, params[:search_title],
+ placeholder: _('Filter by milestone name'),
+ class: 'form-control input-short',
+ spellcheck: false
+ = hidden_field_tag :state, params[:state]
+ = hidden_field_tag :sort, params[:sort]
+
diff --git a/app/views/shared/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml
index 0674c822d63..c3f5eeb0da6 100644
--- a/app/views/shared/notes/_comment_button.html.haml
+++ b/app/views/shared/notes/_comment_button.html.haml
@@ -1,30 +1,31 @@
- noteable_name = @note.noteable.human_class_name
.float-left.btn-group.append-right-10.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
- %input.btn.btn-nr.btn-success.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' }
+ %input.btn.btn-nr.btn-success.js-comment-button.js-comment-submit-button{ type: 'submit', value: _('Comment') }
- if @note.can_be_discussion_note?
- = button_tag type: 'button', class: 'btn btn-nr dropdown-toggle comment-btn js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => 'Open comment type dropdown' do
+ = button_tag type: 'button', class: 'btn btn-nr dropdown-toggle btn-success js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => _('Open comment type dropdown') do
= icon('caret-down', class: 'toggle-icon')
%ul#resolvable-comment-menu.dropdown-menu.dropdown-open-top{ data: { dropdown: true } }
- %li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => 'Comment', 'close-text' => "Comment & close #{noteable_name}", 'reopen-text' => "Comment & reopen #{noteable_name}" } }
+ %li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => _('Comment'), 'close-text' => _("Comment & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Comment & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
- %strong Comment
+ %strong= _("Comment")
%p
- Add a general comment to this #{noteable_name}.
+ = _("Add a general comment to this %{noteable_name}.") % { noteable_name: noteable_name }
%li.divider.droplab-item-ignore
- %li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => 'Start discussion', 'close-text' => "Start discussion & close #{noteable_name}", 'reopen-text' => "Start discussion & reopen #{noteable_name}" } }
+ %li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => _('Start discussion'), 'close-text' => _("Start discussion & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Start discussion & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
- %strong Start discussion
+ %strong= _("Start discussion")
%p
= succeed '.' do
- Discuss a specific suggestion or question
- if @note.noteable.supports_resolvable_notes?
- that needs to be resolved
+ = _('Discuss a specific suggestion or question that needs to be resolved')
+ - else
+ = _('Discuss a specific suggestion or question')
diff --git a/app/views/shared/notes/_edit.html.haml b/app/views/shared/notes/_edit.html.haml
index f4b3aac29b4..84a3ef9d8fe 100644
--- a/app/views/shared/notes/_edit.html.haml
+++ b/app/views/shared/notes/_edit.html.haml
@@ -1 +1 @@
-%textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: note_url(note) } }= note.note
+%textarea.hidden.js-task-list-field.original-task-list{ data: { update_url: note_url(note) } }= note.note
diff --git a/app/views/shared/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml
index fec966069b9..6fe511c2999 100644
--- a/app/views/shared/notes/_edit_form.html.haml
+++ b/app/views/shared/notes/_edit_form.html.haml
@@ -3,12 +3,12 @@
= hidden_field_tag :target_id, '', class: 'js-form-target-id'
= hidden_field_tag :target_type, '', class: 'js-form-target-type'
= render layout: 'projects/md_preview', locals: { url: preview_markdown_path(project), referenced_users: true } do
- = render 'projects/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here…"
+ = render 'projects/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', placeholder: _("Write a comment or drag your files here…")
= render 'shared/notes/hints'
.note-form-actions.clearfix
.settings-message.note-edit-warning.js-finish-edit-warning
- Finish editing this message first!
- = submit_tag 'Save comment', class: 'btn btn-nr btn-success js-comment-save-button'
+ = _("Finish editing this message first!")
+ = submit_tag _('Save comment'), class: 'btn btn-nr btn-success js-comment-save-button'
%button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
- Cancel
+ = _("Cancel")
diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml
index c360f1ffe2a..6a1eea85fde 100644
--- a/app/views/shared/notes/_form.html.haml
+++ b/app/views/shared/notes/_form.html.haml
@@ -29,7 +29,7 @@
= render 'projects/zen', f: f,
attr: :note,
classes: 'note-textarea js-note-text',
- placeholder: "Write a comment or drag your files here…",
+ placeholder: _("Write a comment or drag your files here…"),
supports_quick_actions: supports_quick_actions,
supports_autocomplete: supports_autocomplete
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
@@ -40,5 +40,5 @@
= yield(:note_actions)
- %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } }
- Discard draft
+ %a.btn.btn-cancel.js-close-discussion-note-form.hide{ role: "button", data: { cancel_text: _("Cancel") } }
+ = _('Cancel')
diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index 00eae553279..46f3f8428f1 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -1,10 +1,10 @@
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
.comment-toolbar.clearfix
.toolbar-text
- = link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
+ = link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank', tabindex: -1
- if supports_quick_actions
and
- = link_to 'quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
+ = link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
are
- else
is
@@ -24,12 +24,12 @@
= icon('file-image-o', class: 'toolbar-button-icon')
%span.uploading-error-message
-# Populated by app/assets/javascripts/dropzone_input.js
- %button.retry-uploading-link{ type: 'button' } Try again
+ %button.retry-uploading-link{ type: 'button' }= _("Try again")
or
- %button.attach-new-file.markdown-selector{ type: 'button' } attach a new file
+ %button.attach-new-file.markdown-selector{ type: 'button' }= _("attach a new file")
%button.markdown-selector.button-attach-file{ type: 'button', tabindex: '-1' }
= icon('file-image-o', class: 'toolbar-button-icon')
- Attach a file
+ = _("Attach a file")
- %button.btn.btn-default.btn-sm.hide.button-cancel-uploading-files{ type: 'button' } Cancel
+ %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 e125d7f108a..cb5c64fb91d 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -62,7 +62,7 @@
= render 'award_emoji/awards_block', awardable: note, inline: false
- if note.system
.system-note-commit-list-toggler.hide
- Toggle commit list
+ = _("Toggle commit list")
%i.fa.fa-angle-down
- if note.attachment.url
.note-attachment
@@ -74,5 +74,5 @@
= icon('paperclip')
= note.attachment_identifier
= link_to delete_attachment_project_note_path(note.project, note),
- title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
+ title: _('Delete this attachment'), method: :delete, remote: true, data: { confirm: _('Are you sure you want to remove the attachment?') }, class: 'danger js-note-attachment-delete' do
= icon('trash-o', class: 'cred')
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index 4c4050c6054..002189e6ecd 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -19,20 +19,14 @@
= render "shared/notes/form", view: diff_view, supports_autocomplete: autocomplete
- elsif !current_user
.disabled-comment.text-center.prepend-top-default
- Please
- = link_to "register", new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'), class: 'js-register-link'
- or
- = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'js-sign-in-link'
- to comment
+ - link_to_register = link_to(_("register"), new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'), class: 'js-register-link')
+ - link_to_sign_in = link_to(_("sign in"), new_session_path(:user, redirect_to_referer: 'yes'), class: 'js-sign-in-link')
+ = _("Please %{link_to_register} or %{link_to_sign_in} to comment").html_safe % { link_to_register: link_to_register, link_to_sign_in: link_to_sign_in }
- elsif discussion_locked
.disabled-comment.text-center.prepend-top-default
%span.issuable-note-warning
= sprite_icon('lock', size: 16, css_class: 'icon')
%span
- This
- = issuable.class.to_s.titleize.downcase
- is locked. Only
- %b project members
- can comment.
+ = _("This %{issuable} is locked. Only <strong>project members</strong> can comment.").html_safe % { issuable: issuable.class.to_s.titleize.downcase }
-# haml-lint:disable InlineJavaScript
%script.js-notes-data{ type: "application/json" }= initial_notes_data(autocomplete).to_json.html_safe
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 15c29e14cc0..7d90d9ca4a5 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -33,4 +33,9 @@
%span &nbsp;you have no access to.
= paginate_collection(projects, remote: remote) unless skip_pagination
- else
- .nothing-here-block No projects found
+ .nothing-here-block
+ .svg-content.svg-130
+ = image_tag 'illustrations/profile-page/personal-project.svg'
+ %div
+ %span
+ = s_('UserProfile|This user doesn\'t have any personal projects')
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 5069e2e4ca6..42af97bc6af 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -25,7 +25,7 @@
#{snippet.to_reference} &middot;
authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom', html_class: 'snippet-created-ago')}
by
- = link_to user_snippets_path(snippet.author) do
+ = link_to user_snippets_path(snippet.author), class: "js-user-link", data: { user_id: snippet.author.id } do
= snippet.author_name
- if link_project && snippet.project_id?
%span.d-none.d-sm-inline-block
diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml
index af9db5f59a8..a5d3e1c8de0 100644
--- a/app/views/shared/tokens/_scopes_form.html.haml
+++ b/app/views/shared/tokens/_scopes_form.html.haml
@@ -4,6 +4,6 @@
- scopes.each do |scope|
%fieldset.form-group.form-check
- = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: 'form-check-input'
+ = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: "form-check-input qa-#{scope}-radio"
= label_tag ("#{prefix}_scopes_#{scope}"), scope, class: 'label-bold form-check-label'
.text-secondary= t scope, scope: [:doorkeeper, :scope_desc]
diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml
index d22905ecc93..b5bc1180290 100644
--- a/app/views/users/_overview.html.haml
+++ b/app/views/users/_overview.html.haml
@@ -9,24 +9,22 @@
.col-md-12.col-lg-6
- if can?(current_user, :read_cross_project)
.activities-block
- .append-right-5
- .prepend-top-16
- .d-flex.align-items-center.border-bottom
- %h4.flex-grow
- = s_('UserProfile|Activity')
- = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
- .overview-content-list{ data: { href: user_path } }
- .center.light.loading
- = spinner nil, true
-
- .col-md-12.col-lg-6
- .projects-block
- .prepend-left-5
.prepend-top-16
.d-flex.align-items-center.border-bottom
%h4.flex-grow
- = s_('UserProfile|Personal projects')
- = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all"
- .overview-content-list{ data: { href: user_projects_path } }
+ = s_('UserProfile|Activity')
+ = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
+ .overview-content-list{ data: { href: user_path } }
.center.light.loading
= spinner nil, true
+
+ .col-md-12.col-lg-6
+ .projects-block
+ .prepend-top-16
+ .d-flex.align-items-center.border-bottom
+ %h4.flex-grow
+ = s_('UserProfile|Personal projects')
+ = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all"
+ .overview-content-list{ data: { href: user_projects_path } }
+ .center.light.loading
+ = spinner nil, true
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 8da63a29ca6..211e3eafac6 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -74,11 +74,11 @@
= link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow'
- unless @user.location.blank?
.profile-link-holder.middle-dot-divider
- = icon('map-marker')
+ = sprite_icon('location', size: 16, css_class: 'vertical-align-sub')
= @user.location
- unless @user.organization.blank?
.profile-link-holder.middle-dot-divider
- = icon('briefcase')
+ = sprite_icon('work', size: 16, css_class: 'vertical-align-sub')
= @user.organization
- if @user.bio.present?
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index d3cf21db335..4a306fc7a26 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -90,13 +90,15 @@
- object_pool:object_pool_join
- object_pool:object_pool_destroy
+- container_repository:delete_container_repository
+- container_repository:cleanup_container_repository
+
- default
- mailers # ActionMailer::DeliveryJob.queue_name
- authorized_projects
- background_migration
- create_gpg_signature
-- delete_container_repository
- delete_merged_branches
- delete_user
- email_receiver
@@ -140,3 +142,4 @@
- detect_repository_languages
- repository_cleanup
- delete_stored_files
+- import_issues_csv
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index 61d866b1f02..ae853ec9316 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -9,14 +9,26 @@ class BuildFinishedWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
- # We execute that in sync as this access the files in order to access local file, and reduce IO
- BuildTraceSectionsWorker.new.perform(build.id)
- BuildCoverageWorker.new.perform(build.id)
-
- # We execute that async as this are two independent operations that can be executed after TraceSections and Coverage
- BuildHooksWorker.perform_async(build.id)
- ArchiveTraceWorker.perform_async(build.id)
+ process_build(build)
end
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ # Processes a single CI build that has finished.
+ #
+ # This logic resides in a separate method so that EE can extend it more
+ # easily.
+ #
+ # @param [Ci::Build] build The build to process.
+ def process_build(build)
+ # We execute these in sync to reduce IO.
+ BuildTraceSectionsWorker.new.perform(build.id)
+ BuildCoverageWorker.new.perform(build.id)
+
+ # We execute these async as these are independent operations.
+ BuildHooksWorker.perform_async(build.id)
+ ArchiveTraceWorker.perform_async(build.id)
+ end
end
diff --git a/app/workers/cleanup_container_repository_worker.rb b/app/workers/cleanup_container_repository_worker.rb
new file mode 100644
index 00000000000..974ee8c8146
--- /dev/null
+++ b/app/workers/cleanup_container_repository_worker.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class CleanupContainerRepositoryWorker
+ include ApplicationWorker
+ include ExclusiveLeaseGuard
+
+ queue_namespace :container_repository
+
+ LEASE_TIMEOUT = 1.hour
+
+ attr_reader :container_repository, :current_user
+
+ def perform(current_user_id, container_repository_id, params)
+ @current_user = User.find_by_id(current_user_id)
+ @container_repository = ContainerRepository.find_by_id(container_repository_id)
+
+ return unless valid?
+
+ try_obtain_lease do
+ Projects::ContainerRepository::CleanupTagsService
+ .new(project, current_user, params)
+ .execute(container_repository)
+ end
+ end
+
+ private
+
+ def valid?
+ current_user && container_repository && project
+ end
+
+ def project
+ container_repository&.project
+ end
+
+ # For ExclusiveLeaseGuard concern
+ def lease_key
+ @lease_key ||= "container_repository:cleanup_tags:#{container_repository.id}"
+ end
+
+ # For ExclusiveLeaseGuard concern
+ def lease_timeout
+ LEASE_TIMEOUT
+ end
+
+ # For ExclusiveLeaseGuard concern
+ def lease_release?
+ # we don't allow to execute this worker
+ # more often than LEASE_TIMEOUT
+ # for given container repository
+ false
+ end
+end
diff --git a/app/workers/delete_container_repository_worker.rb b/app/workers/delete_container_repository_worker.rb
index e8fe9d82797..42e66513ff1 100644
--- a/app/workers/delete_container_repository_worker.rb
+++ b/app/workers/delete_container_repository_worker.rb
@@ -4,6 +4,8 @@ class DeleteContainerRepositoryWorker
include ApplicationWorker
include ExclusiveLeaseGuard
+ queue_namespace :container_repository
+
LEASE_TIMEOUT = 1.hour
attr_reader :container_repository
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index dce812d1ae2..251e95c68d5 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -4,8 +4,20 @@ class ExpireBuildArtifactsWorker
include ApplicationWorker
include CronjobQueue
- # rubocop: disable CodeReuse/ActiveRecord
def perform
+ if Feature.enabled?(:ci_new_expire_job_artifacts_service, default_enabled: true)
+ perform_efficient_artifacts_removal
+ else
+ perform_legacy_artifacts_removal
+ end
+ end
+
+ def perform_efficient_artifacts_removal
+ Ci::DestroyExpiredJobArtifactsService.new.execute
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform_legacy_artifacts_removal
Rails.logger.info 'Scheduling removal of build artifacts'
build_ids = Ci::Build.with_expired_artifacts.pluck(:id)
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index c96e8a0379b..148384600b6 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -11,16 +11,9 @@ class ExpirePipelineCacheWorker
pipeline = Ci::Pipeline.find_by(id: pipeline_id)
return unless pipeline
- project = pipeline.project
store = Gitlab::EtagCaching::Store.new
- store.touch(project_pipelines_path(project))
- store.touch(project_pipeline_path(project, pipeline))
- store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil?
- store.touch(new_merge_request_pipelines_path(project))
- each_pipelines_merge_request_path(project, pipeline) do |path|
- store.touch(path)
- end
+ update_etag_cache(pipeline, store)
Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(pipeline)
end
@@ -51,4 +44,23 @@ class ExpirePipelineCacheWorker
yield(path)
end
end
+
+ # Updates ETag caches of a pipeline.
+ #
+ # This logic resides in a separate method so that EE can more easily extend
+ # it.
+ #
+ # @param [Ci::Pipeline] pipeline
+ # @param [Gitlab::EtagCaching::Store] store
+ def update_etag_cache(pipeline, store)
+ project = pipeline.project
+
+ store.touch(project_pipelines_path(project))
+ store.touch(project_pipeline_path(project, pipeline))
+ store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil?
+ store.touch(new_merge_request_pipelines_path(project))
+ each_pipelines_merge_request_path(project, pipeline) do |path|
+ store.touch(path)
+ end
+ end
end
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index d3628b23189..b33e9b1f718 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -23,6 +23,7 @@ class GitGarbageCollectWorker
end
task = task.to_sym
+ project.link_pool_repository
gitaly_call(task, project.repository.raw_repository)
# Refresh the branch cache in case garbage collection caused a ref lookup to fail
diff --git a/app/workers/import_issues_csv_worker.rb b/app/workers/import_issues_csv_worker.rb
new file mode 100644
index 00000000000..d44fdfec8ae
--- /dev/null
+++ b/app/workers/import_issues_csv_worker.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class ImportIssuesCsvWorker
+ include ApplicationWorker
+
+ sidekiq_retries_exhausted do |job|
+ Upload.find(job['args'][2]).destroy
+ end
+
+ def perform(current_user_id, project_id, upload_id)
+ @user = User.find(current_user_id)
+ @project = Project.find(project_id)
+ @upload = Upload.find(upload_id)
+
+ importer = Issues::ImportCsvService.new(@user, @project, @upload.build_uploader)
+ importer.execute
+
+ @upload.destroy
+ end
+end
diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb
index 2965f3b1150..f6e98746055 100644
--- a/app/workers/namespaceless_project_destroy_worker.rb
+++ b/app/workers/namespaceless_project_destroy_worker.rb
@@ -17,7 +17,7 @@ class NamespacelessProjectDestroyWorker
return
end
- return if project.namespace # Reject doing anything for projects that *do* have a namespace
+ return if project.namespace # Reject doing anything for projects that *do* have a namespace
project.team.truncate
diff --git a/app/workers/object_pool/join_worker.rb b/app/workers/object_pool/join_worker.rb
index 07676011b2a..9c5161fd55a 100644
--- a/app/workers/object_pool/join_worker.rb
+++ b/app/workers/object_pool/join_worker.rb
@@ -5,14 +5,13 @@ module ObjectPool
include ApplicationWorker
include ObjectPoolQueue
- def perform(pool_id, project_id)
- pool = PoolRepository.find_by_id(pool_id)
- return unless pool&.joinable?
-
+ # The use of pool id is deprecated. Keeping the argument allows old jobs to
+ # still be performed.
+ def perform(_pool_id, project_id)
project = Project.find_by_id(project_id)
- return unless project
+ return unless project&.pool_repository&.joinable?
- pool.link_repository(project.repository)
+ project.link_pool_repository
Projects::HousekeepingService.new(project).execute
end
diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb
index 96ff8cd6222..7c66ac046ea 100644
--- a/app/workers/reactive_caching_worker.rb
+++ b/app/workers/reactive_caching_worker.rb
@@ -12,7 +12,7 @@ class ReactiveCachingWorker
end
return unless klass
- klass.find_by(id: id).try(:exclusively_update_reactive_cache!, *args)
+ klass.find_by(klass.primary_key => id).try(:exclusively_update_reactive_cache!, *args)
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/remote_mirror_notification_worker.rb b/app/workers/remote_mirror_notification_worker.rb
index 70c2e857d09..5bafe8e2046 100644
--- a/app/workers/remote_mirror_notification_worker.rb
+++ b/app/workers/remote_mirror_notification_worker.rb
@@ -9,7 +9,10 @@ class RemoteMirrorNotificationWorker
# We check again if there's an error because a newer run since this job was
# fired could've completed successfully.
return unless remote_mirror && remote_mirror.last_error.present?
+ return if remote_mirror.error_notification_sent?
NotificationService.new.remote_mirror_update_failed(remote_mirror)
+
+ remote_mirror.after_sent_notification
end
end
diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb
index e8494ffa002..4ec2b9d8fbe 100644
--- a/app/workers/update_head_pipeline_for_merge_request_worker.rb
+++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb
@@ -7,25 +7,8 @@ class UpdateHeadPipelineForMergeRequestWorker
queue_namespace :pipeline_processing
def perform(merge_request_id)
- merge_request = MergeRequest.find(merge_request_id)
-
- sha = merge_request.diff_head_sha
- pipeline = merge_request.all_pipelines(shas: sha).first
-
- return unless pipeline && pipeline.latest?
-
- if merge_request.diff_head_sha != pipeline.sha
- log_error_message_for(merge_request)
-
- return
+ MergeRequest.find_by_id(merge_request_id).try do |merge_request|
+ merge_request.update_head_pipeline
end
-
- merge_request.update_attribute(:head_pipeline_id, pipeline.id)
- end
-
- def log_error_message_for(merge_request)
- Rails.logger.error(
- "Outdated head pipeline for active merge request: id=#{merge_request.id}, source_branch=#{merge_request.source_branch}, diff_head_sha=#{merge_request.diff_head_sha}"
- )
end
end
diff --git a/bin/changelog b/bin/changelog
index 758c036161e..328d9495b96 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -148,7 +148,7 @@ class ChangelogEntry
def execute
assert_feature_branch!
- assert_title!
+ assert_title! unless editor
assert_new_file!
# Read type from $stdin unless is already set
@@ -162,6 +162,10 @@ class ChangelogEntry
write
amend_commit if options.amend
end
+
+ if editor
+ system("#{editor} '#{file_path}'")
+ end
end
private
@@ -180,6 +184,10 @@ class ChangelogEntry
File.write(file_path, contents)
end
+ def editor
+ ENV['EDITOR']
+ end
+
def amend_commit
fail_with "git add failed" unless system(*%W[git add #{file_path}])
diff --git a/bin/secpick b/bin/secpick
index 11acdd82226..3d032f696a2 100755
--- a/bin/secpick
+++ b/bin/secpick
@@ -1,4 +1,5 @@
#!/usr/bin/env ruby
+
# frozen_string_literal: false
require 'active_support/core_ext/object/to_query'
@@ -7,69 +8,132 @@ require 'open3'
require 'rainbow/refinement'
using Rainbow
-BRANCH_PREFIX = 'security'.freeze
-REMOTE = 'dev'.freeze
-NEW_MR_URL = 'https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/new'.freeze
-
-options = { version: nil, branch: nil, sha: nil }
-
-parser = OptionParser.new do |opts|
- opts.banner = "Usage: #{$0} [options]"
- opts.on('-v', '--version 10.0', 'Version') do |version|
- options[:version] = version&.tr('.', '-')
- end
-
- opts.on('-b', '--branch security-fix-branch', 'Original branch name (optional, defaults to current)') do |branch|
- options[:branch] = branch
- end
-
- opts.on('-s', '--sha abcd', 'SHA to cherry pick') do |sha|
- options[:sha] = sha
- end
-
- opts.on('-h', '--help', 'Displays Help') do
- puts opts
-
- exit
+module Secpick
+ BRANCH_PREFIX = 'security'.freeze
+ DEFAULT_REMOTE = 'dev'.freeze
+ NEW_MR_URL = 'https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/new'.freeze
+
+ class SecurityFix
+ def initialize
+ @options = self.class.options
+ end
+
+ def ee?
+ File.exist?('./CHANGELOG-EE.md')
+ end
+
+ def dry_run?
+ @options[:try] == true
+ end
+
+ def original_branch
+ @options[:branch].strip
+ end
+
+ def source_branch
+ branch = "#{original_branch}-#{@options[:version]}"
+ branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-")
+ branch.freeze
+ end
+
+ def security_branch
+ "#{BRANCH_PREFIX}-#{@options[:version]}".tap do |name|
+ name << "-ee" if ee?
+ end.freeze
+ end
+
+ def git_commands
+ ["git fetch #{@options[:remote]} #{security_branch}",
+ "git checkout #{security_branch}",
+ "git pull #{@options[:remote]} #{security_branch}",
+ "git checkout -B #{source_branch}",
+ "git cherry-pick #{@options[:sha]}",
+ "git push #{@options[:remote]} #{source_branch}",
+ "git checkout #{original_branch}"]
+ end
+
+ def gitlab_params
+ {
+ merge_request: {
+ source_branch: source_branch,
+ target_branch: security_branch,
+ title: "WIP: [#{@options[:version].tr('-', '.')}] ",
+ description: '/label ~security'
+ }
+ }
+ end
+
+ def new_mr_url
+ if ee?
+ NEW_MR_URL.sub('gitlabhq', 'gitlab-ee')
+ else
+ NEW_MR_URL
+ end
+ end
+
+ def create!
+ if dry_run?
+ puts git_commands.join("\n").green
+ puts "\nMerge request params: ".blue
+ pp gitlab_params
+ else
+ cmd = git_commands.join(' && ')
+ stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
+
+ puts stdout.read&.green
+ puts stderr.read&.red
+
+ if wait_thr.value.success?
+ puts "#{new_mr_url}?#{gitlab_params.to_query}".blue
+ end
+
+ stdin.close
+ stdout.close
+ stderr.close
+ end
+ end
+
+ def self.options
+ { version: nil, branch: nil, sha: nil }.tap do |options|
+ parser = OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} [options]"
+ opts.on('-v', '--version 10.0', 'Version') do |version|
+ options[:version] = version&.tr('.', '-')
+ end
+
+ opts.on('-b', '--branch security-fix-branch', 'Original branch name (optional, defaults to current)') do |branch|
+ options[:branch] = branch
+ end
+
+ opts.on('-s', '--sha abcd', 'SHA to cherry pick') do |sha|
+ options[:sha] = sha
+ end
+
+ opts.on('-r', '--remote abcd', 'Git remote name of dev.gitlab.org (optional, defaults to `dev`)') do |remote|
+ options[:remote] = remote
+ end
+
+ opts.on('-d', '--dry-run', 'Only show Git commands, without calling them') do |remote|
+ options[:try] = true
+ end
+
+ opts.on('-h', '--help', 'Displays Help') do
+ puts opts
+
+ exit
+ end
+ end
+
+ parser.parse!
+
+ options[:branch] ||= `git rev-parse --abbrev-ref HEAD`
+ options[:remote] ||= DEFAULT_REMOTE
+
+ abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil)
+ abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
+ end
+ end
end
end
-parser.parse!
-
-options[:branch] ||= `git rev-parse --abbrev-ref HEAD`
-
-abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil)
-abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
-
-ee = File.exist?('./CHANGELOG-EE.md')
-original_branch = options[:branch].strip
-branch = "#{original_branch}-#{options[:version]}"
-branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-")
-branch = branch.freeze
-stable_branch = "#{BRANCH_PREFIX}-#{options[:version]}".tap do |name|
- name << "-ee" if ee
-end.freeze
-
-command = "git fetch #{REMOTE} #{stable_branch} && git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch} && git checkout #{original_branch}"
-
-stdin, stdout, stderr, wait_thr = Open3.popen3(command)
-
-puts stdout.read&.green
-puts stderr.read&.red
-
-if wait_thr.value.success?
- params = {
- merge_request: {
- source_branch: branch,
- target_branch: stable_branch,
- title: "WIP: [#{options[:version].tr('-', '.')}] ",
- description: '/label ~security'
- }
- }
-
- puts "#{NEW_MR_URL}?#{params.to_query}".blue
-end
-
-stdin.close
-stdout.close
-stderr.close
+Secpick::SecurityFix.new.create!
diff --git a/changelogs/unreleased/18667-handle-push-opts.yml b/changelogs/unreleased/18667-handle-push-opts.yml
deleted file mode 100644
index 204293476f6..00000000000
--- a/changelogs/unreleased/18667-handle-push-opts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Handle ci.skip push option
-merge_request: 15643
-author: Jonathon Reinhart
-type: added
diff --git a/changelogs/unreleased/23367-clarify-docs-allow-failure.yml b/changelogs/unreleased/23367-clarify-docs-allow-failure.yml
deleted file mode 100644
index 221d9e83ffb..00000000000
--- a/changelogs/unreleased/23367-clarify-docs-allow-failure.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Clarifies docs about CI `allow_failure`
-merge_request: 23367
-author: C.J. Jameson
-type: other
diff --git a/changelogs/unreleased/25043-empty-states.yml b/changelogs/unreleased/25043-empty-states.yml
new file mode 100644
index 00000000000..529a8b3206f
--- /dev/null
+++ b/changelogs/unreleased/25043-empty-states.yml
@@ -0,0 +1,5 @@
+---
+title: Make issuable empty states actionable
+merge_request: 24077
+author:
+type: changed
diff --git a/changelogs/unreleased/25341-add-what-s-new-menu-item-in-top-navigation.yml b/changelogs/unreleased/25341-add-what-s-new-menu-item-in-top-navigation.yml
new file mode 100644
index 00000000000..da1777827cb
--- /dev/null
+++ b/changelogs/unreleased/25341-add-what-s-new-menu-item-in-top-navigation.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Add What's new menu item in top navigation
+merge_request: 23186
+author:
+type: added
diff --git a/changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml b/changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml
new file mode 100644
index 00000000000..02a667073ca
--- /dev/null
+++ b/changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Use delete instead of remove when referring to `git branch -D`
+merge_request: !23966
+author:
+type: changed
diff --git a/changelogs/unreleased/26375-markdown-footnotes-not-working.yml b/changelogs/unreleased/26375-markdown-footnotes-not-working.yml
new file mode 100644
index 00000000000..86adef84a2a
--- /dev/null
+++ b/changelogs/unreleased/26375-markdown-footnotes-not-working.yml
@@ -0,0 +1,5 @@
+---
+title: Footnotes now render properly in markdown
+merge_request: 24168
+author:
+type: fixed
diff --git a/changelogs/unreleased/30120-add-flat-square-badge-style.yml b/changelogs/unreleased/30120-add-flat-square-badge-style.yml
new file mode 100644
index 00000000000..a542a58d3fc
--- /dev/null
+++ b/changelogs/unreleased/30120-add-flat-square-badge-style.yml
@@ -0,0 +1,5 @@
+---
+title: Add flat-square badge style
+merge_request: 24172
+author: Fabian Schneider @fabsrc
+type: added
diff --git a/changelogs/unreleased/34758-extend-can-create-cluster-logic.yml b/changelogs/unreleased/34758-extend-can-create-cluster-logic.yml
deleted file mode 100644
index 65f5253a271..00000000000
--- a/changelogs/unreleased/34758-extend-can-create-cluster-logic.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow user to add Kubernetes cluster for clusterable when there are ancestor clusters
-merge_request: 23569
-author:
-type: other
diff --git a/changelogs/unreleased/34758-list-ancestor-clusters.yml b/changelogs/unreleased/34758-list-ancestor-clusters.yml
deleted file mode 100644
index 8fdba7ba90a..00000000000
--- a/changelogs/unreleased/34758-list-ancestor-clusters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show clusters of ancestors in cluster list page
-merge_request: 22996
-author:
-type: changed
diff --git a/changelogs/unreleased/37990-task-list-bracket.yml b/changelogs/unreleased/37990-task-list-bracket.yml
new file mode 100644
index 00000000000..ffa77cf0af7
--- /dev/null
+++ b/changelogs/unreleased/37990-task-list-bracket.yml
@@ -0,0 +1,5 @@
+---
+title: Fix ambiguous brackets in task lists
+merge_request: 18514
+author: Jared Deckard <jared.deckard@gmail.com>
+type: fixed
diff --git a/changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml b/changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml
deleted file mode 100644
index 9ea2157bfb7..00000000000
--- a/changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes all instances of deprecated Gitlab Upgrader calls
-merge_request: 23603
-author: '@jwolen'
-type: removed
diff --git a/changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml b/changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml
new file mode 100644
index 00000000000..01036253151
--- /dev/null
+++ b/changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml
@@ -0,0 +1,5 @@
+---
+title: Fix empty labels of CI builds for gitlab-pages on pipeline page
+merge_request: 24451
+author:
+type: fixed
diff --git a/changelogs/unreleased/41766-vue-component.yml b/changelogs/unreleased/41766-vue-component.yml
deleted file mode 100644
index 12343c8ce84..00000000000
--- a/changelogs/unreleased/41766-vue-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Creates component for release block
-merge_request: 23697
-author:
-type: added
diff --git a/changelogs/unreleased/41766-vuex-store.yml b/changelogs/unreleased/41766-vuex-store.yml
deleted file mode 100644
index f20fc736a6f..00000000000
--- a/changelogs/unreleased/41766-vuex-store.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Creates frontend app for releases
-merge_request: 23796
-author:
-type: added
diff --git a/changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml b/changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml
deleted file mode 100644
index 9892466ca50..00000000000
--- a/changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extend override check to also check arity
-merge_request: 23498
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml b/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml
new file mode 100644
index 00000000000..5a4ff8b3358
--- /dev/null
+++ b/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml
@@ -0,0 +1,5 @@
+---
+title: Remove expansion hover animation from pipeline status icon buttons
+merge_request: 24268
+author: Nathan Friend
+type: changed
diff --git a/changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml b/changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml
deleted file mode 100644
index ba9edc8740d..00000000000
--- a/changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Refactor issuable sidebar to use serializer
-merge_request: 23379
-author:
-type: other
diff --git a/changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml b/changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml
new file mode 100644
index 00000000000..b4cba5041d1
--- /dev/null
+++ b/changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Fix default visibility_level for new projects
+merge_request: 24120
+author: Fabian Schneider @fabsrc
+type: fixed
diff --git a/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml b/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml
new file mode 100644
index 00000000000..28e2a4cc377
--- /dev/null
+++ b/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml
@@ -0,0 +1,5 @@
+---
+title: Redesigned related merge requests in issue page.
+merge_request: 24270
+author:
+type: changed
diff --git a/changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml b/changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml
deleted file mode 100644
index fd1e4605f2d..00000000000
--- a/changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow merge after rebase without page refresh on FF repositories
-merge_request: 23572
-author:
-type: fixed
diff --git a/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml b/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml
new file mode 100644
index 00000000000..d1a80ab43cf
--- /dev/null
+++ b/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml
@@ -0,0 +1,5 @@
+---
+title: Improve milestone queries using subqueries instead of separate queries for ids
+merge_request: 24325
+author:
+type: performance
diff --git a/changelogs/unreleased/50013-add-browser-platform-flags.yml b/changelogs/unreleased/50013-add-browser-platform-flags.yml
new file mode 100644
index 00000000000..6176b8b64a7
--- /dev/null
+++ b/changelogs/unreleased/50013-add-browser-platform-flags.yml
@@ -0,0 +1,5 @@
+---
+title: Add CSS & JS global flags to represent browser and platform
+merge_request: 24017
+author:
+type: other
diff --git a/changelogs/unreleased/51485-new-issue-labels-note.yml b/changelogs/unreleased/51485-new-issue-labels-note.yml
deleted file mode 100644
index a312d379ce2..00000000000
--- a/changelogs/unreleased/51485-new-issue-labels-note.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Create system notes on issue / MR creation when labels, milestone, or due date is set
-merge_request: 23859
-author:
-type: added
diff --git a/changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml b/changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml
deleted file mode 100644
index a845234b42f..00000000000
--- a/changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Stop autofocusing on diff comment after initial mount
-merge_request: 23849
-author:
-type: fixed
diff --git a/changelogs/unreleased/51754-admin-view-private-personal-snippets.yml b/changelogs/unreleased/51754-admin-view-private-personal-snippets.yml
new file mode 100644
index 00000000000..cf3d73fce0c
--- /dev/null
+++ b/changelogs/unreleased/51754-admin-view-private-personal-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Allow users with full private access to read private personal snippets.
+merge_request: 24560
+author:
+type: fixed
diff --git a/changelogs/unreleased/51944-redesign-project-lists-ui.yml b/changelogs/unreleased/51944-redesign-project-lists-ui.yml
deleted file mode 100644
index 56f9a86a686..00000000000
--- a/changelogs/unreleased/51944-redesign-project-lists-ui.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Redesign project lists UI
-merge_request: 22682
-author:
-type: other
diff --git a/changelogs/unreleased/51970-correct-ordering-of-metrics.yml b/changelogs/unreleased/51970-correct-ordering-of-metrics.yml
deleted file mode 100644
index fbc7b58d901..00000000000
--- a/changelogs/unreleased/51970-correct-ordering-of-metrics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Correct the ordering of metrics on the performance dashboard
-merge_request: 23630
-author:
-type: fixed
diff --git a/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml b/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml
deleted file mode 100644
index 2d54cf814b7..00000000000
--- a/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disable merging of labels with same names
-merge_request: 23265
-author:
-type: changed
diff --git a/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml b/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml
new file mode 100644
index 00000000000..c1cde0ceff6
--- /dev/null
+++ b/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve In Merge Request diff screen, master is not a hyperlink
+merge_request: 23874
+author:
+type: fixed
diff --git a/changelogs/unreleased/52278-squash-checkbox-fix.yml b/changelogs/unreleased/52278-squash-checkbox-fix.yml
new file mode 100644
index 00000000000..c81748ae419
--- /dev/null
+++ b/changelogs/unreleased/52278-squash-checkbox-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve When merging an MR, the squash checkbox isnt always supported
+merge_request: 24296
+author:
+type: fixed
diff --git a/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml b/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml
new file mode 100644
index 00000000000..07cb35e6529
--- /dev/null
+++ b/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml
@@ -0,0 +1,5 @@
+---
+title: Modifies environment scope UI on cluster page
+merge_request: 24376
+author:
+type: other
diff --git a/changelogs/unreleased/52620-fix-loader-animation-alignment.yml b/changelogs/unreleased/52620-fix-loader-animation-alignment.yml
deleted file mode 100644
index 5cfb7fc019f..00000000000
--- a/changelogs/unreleased/52620-fix-loader-animation-alignment.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Aligns build loader animation with the job log
-merge_request: 23959
-author:
-type: fixed
diff --git a/changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml b/changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml
new file mode 100644
index 00000000000..f79078c1fd9
--- /dev/null
+++ b/changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: "[API] Omit `X-Total` and `X-Total-Pages` headers when items count is more than 10,000"
+merge_request: 23931
+author:
+type: performance
diff --git a/changelogs/unreleased/52888-status-emoji-should-not-be-added-to-awards-section-on-issue-page-2.yml b/changelogs/unreleased/52888-status-emoji-should-not-be-added-to-awards-section-on-issue-page-2.yml
deleted file mode 100644
index 501940d6da3..00000000000
--- a/changelogs/unreleased/52888-status-emoji-should-not-be-added-to-awards-section-on-issue-page-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent awards emoji being updated when updating status
-merge_request: 23470
-author:
-type: fixed
diff --git a/changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml b/changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml
new file mode 100644
index 00000000000..b661c55957d
--- /dev/null
+++ b/changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml
@@ -0,0 +1,5 @@
+---
+title: Make possible to toggle file tree while scrolling through diffs
+merge_request: !24103
+author:
+type: changed
diff --git a/changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml b/changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml
deleted file mode 100644
index 99da02dd31a..00000000000
--- a/changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds explanatory text to input fields on user profile settings page
-merge_request: 23673
-author:
-type: other
diff --git a/changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml b/changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml
new file mode 100644
index 00000000000..1e9c7f3913c
--- /dev/null
+++ b/changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml
@@ -0,0 +1,5 @@
+---
+title: Fix upcoming milestones filter not including group milestones
+merge_request: 23098
+author: Heinrich Lee Yu
+type: fixed
diff --git a/changelogs/unreleased/53493-list-id-email-header.yml b/changelogs/unreleased/53493-list-id-email-header.yml
deleted file mode 100644
index 09a0639f6f5..00000000000
--- a/changelogs/unreleased/53493-list-id-email-header.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add project identifier as List-Id email Header to ease filtering
-merge_request: 22817
-author: Olivier Crête
-type: added
diff --git a/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml b/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml
new file mode 100644
index 00000000000..08c5ded05d5
--- /dev/null
+++ b/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml
@@ -0,0 +1,5 @@
+---
+title: Redirect GET projects/:id to project page
+merge_request: 24467
+author:
+type: added
diff --git a/changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml b/changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml
new file mode 100644
index 00000000000..12a6509e6f7
--- /dev/null
+++ b/changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml
@@ -0,0 +1,5 @@
+---
+title: Get remote IP address of runner
+merge_request: 24624
+author:
+type: changed
diff --git a/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml b/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml
new file mode 100644
index 00000000000..d804e2df2cd
--- /dev/null
+++ b/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Fix foreground color for labels to ensure consistency of label appearance
+merge_request: 23873
+author: Nathan Friend
+type: fixed
diff --git a/changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml b/changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml
new file mode 100644
index 00000000000..1daa72fb9c4
--- /dev/null
+++ b/changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml
@@ -0,0 +1,6 @@
+---
+title: Fix suboptimal handling of checkbox and radio input events causing
+ group general settings submit button to stay disabled after changing its visibility
+merge_request: 23022
+author:
+type: fixed
diff --git a/changelogs/unreleased/53907-improve-milestone-links.yml b/changelogs/unreleased/53907-improve-milestone-links.yml
deleted file mode 100644
index 8e867e783cc..00000000000
--- a/changelogs/unreleased/53907-improve-milestone-links.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add % prefix to milestone reference links
-merge_request: 23928
-author:
-type: changed
diff --git a/changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml b/changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml
deleted file mode 100644
index 5c40a1e900c..00000000000
--- a/changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add date range in milestone change email notifications
-merge_request: 23762
-author:
-type: changed
diff --git a/changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml b/changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml
new file mode 100644
index 00000000000..adaaed7f1aa
--- /dev/null
+++ b/changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Display "commented" only for commit discussions on merge requests
+merge_request: 24427
+author:
+type: changed
diff --git a/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml b/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml
deleted file mode 100644
index 0632c1992c7..00000000000
--- a/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show message on non-diff discussions
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/54146-fix-calendar-query.yml b/changelogs/unreleased/54146-fix-calendar-query.yml
deleted file mode 100644
index dcac343108a..00000000000
--- a/changelogs/unreleased/54146-fix-calendar-query.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix project calendar feed when sorted by priority
-merge_request: 23870
-author:
-type: fixed
diff --git a/changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml b/changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml
new file mode 100644
index 00000000000..6fc8aa1a195
--- /dev/null
+++ b/changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml
@@ -0,0 +1,5 @@
+---
+title: Rename project tags to project topics
+merge_request: 24219
+author:
+type: other
diff --git a/changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml b/changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml
deleted file mode 100644
index e29987b0935..00000000000
--- a/changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Discussion filter only displayed in discussions tab for merge requests
-merge_request: 24082
-author:
-type: changed
diff --git a/changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml b/changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml
new file mode 100644
index 00000000000..d1bdbccb20a
--- /dev/null
+++ b/changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade kubeclient to 4.2.2 and swap out monkey-patch to disallow redirects
+merge_request: 24284
+author:
+type: other
diff --git a/changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml b/changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml
deleted file mode 100644
index e446d2a2781..00000000000
--- a/changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove framework/mobile.scss
-merge_request: 23301
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/54427-label-xss.yml b/changelogs/unreleased/54427-label-xss.yml
deleted file mode 100644
index 090d1832af2..00000000000
--- a/changelogs/unreleased/54427-label-xss.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Escape html entities in LabelReferenceFilter when no label found
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml b/changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml
new file mode 100644
index 00000000000..4d543db567d
--- /dev/null
+++ b/changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml
@@ -0,0 +1,5 @@
+---
+title: Ensured links to a comment or system note anchor resolves to the right note if a user has a discussion filter.
+merge_request: 24228
+author:
+type: changed
diff --git a/changelogs/unreleased/54736-sign-in-bottom-margin.yml b/changelogs/unreleased/54736-sign-in-bottom-margin.yml
deleted file mode 100644
index 32b5b44fe35..00000000000
--- a/changelogs/unreleased/54736-sign-in-bottom-margin.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix login box bottom margins on signin page
-merge_request: 23739
-author: '@gear54'
-type: fixed
diff --git a/changelogs/unreleased/54786-mr-empty-file-display.yml b/changelogs/unreleased/54786-mr-empty-file-display.yml
deleted file mode 100644
index 5adf5744755..00000000000
--- a/changelogs/unreleased/54786-mr-empty-file-display.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display empty files properly on MR diffs
-merge_request: 23671
-author: Sean Nichols
-type: fixed
diff --git a/changelogs/unreleased/54814-sidebar-styling-updates.yml b/changelogs/unreleased/54814-sidebar-styling-updates.yml
deleted file mode 100644
index 98e3836ee14..00000000000
--- a/changelogs/unreleased/54814-sidebar-styling-updates.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix label and header styles in the job details sidebar.
-merge_request: 23816
-author: Nathan Friend
-type: changed
diff --git a/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml b/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml
deleted file mode 100644
index 95fc5cb804d..00000000000
--- a/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use reports syntax for Dependency scanning in Auto DevOps
-merge_request: 24081
-author:
-type: added
diff --git a/changelogs/unreleased/54905-milestone-search.yml b/changelogs/unreleased/54905-milestone-search.yml
new file mode 100644
index 00000000000..88717242e7c
--- /dev/null
+++ b/changelogs/unreleased/54905-milestone-search.yml
@@ -0,0 +1,5 @@
+---
+title: Adds milestone search
+merge_request: 24265
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml b/changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml
deleted file mode 100644
index 8fd127acf2b..00000000000
--- a/changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Return an ApplicationSetting in CurrentSettings
-merge_request: 23766
-author:
-type: fixed
diff --git a/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml b/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml
new file mode 100644
index 00000000000..b609fc2d60b
--- /dev/null
+++ b/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Fix default_branch_protection admin setting'
+merge_request: 24398
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/55191-update-workhorse.yml b/changelogs/unreleased/55191-update-workhorse.yml
deleted file mode 100644
index d16518e673a..00000000000
--- a/changelogs/unreleased/55191-update-workhorse.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GitLab Workhorse to v8.0.0
-merge_request: 23740
-author:
-type: other
diff --git a/changelogs/unreleased/55242-skeleton-loading-releases.yml b/changelogs/unreleased/55242-skeleton-loading-releases.yml
new file mode 100644
index 00000000000..43cda64ce04
--- /dev/null
+++ b/changelogs/unreleased/55242-skeleton-loading-releases.yml
@@ -0,0 +1,5 @@
+---
+title: Adds skeleton loading to releases page
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml b/changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml
deleted file mode 100644
index 62a57085192..00000000000
--- a/changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use 'parsePikadayDate' to parse due date string
-merge_request: 24045
-author:
-type: fixed
diff --git a/changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml b/changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml
deleted file mode 100644
index c6ff52b0fa1..00000000000
--- a/changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Split bio into individual line in extended user tooltips
-merge_request: 23940
-author:
-type: other
diff --git a/changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml b/changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml
deleted file mode 100644
index 9c4d73c5323..00000000000
--- a/changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only prompt user once when navigating away from file editor
-merge_request: 23820
-author: Sam Bigelow
-type: fixed
diff --git a/changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml b/changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml
deleted file mode 100644
index 7476b9caa93..00000000000
--- a/changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Renames Milestone sort into Milestone due date
-merge_request: 24080
-author: Jacopo Beschi @jacopo-beschi
-type: changed
diff --git a/changelogs/unreleased/55484-fix-edit-button.yml b/changelogs/unreleased/55484-fix-edit-button.yml
deleted file mode 100644
index c8998cba248..00000000000
--- a/changelogs/unreleased/55484-fix-edit-button.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-title: Fix edit button disappearing in issue title
-merge_request: 23948
-author: Ruben Moya
-type: fixed
diff --git a/changelogs/unreleased/55495-teamcity-use-revision-in-query.yml b/changelogs/unreleased/55495-teamcity-use-revision-in-query.yml
new file mode 100644
index 00000000000..724de733b7c
--- /dev/null
+++ b/changelogs/unreleased/55495-teamcity-use-revision-in-query.yml
@@ -0,0 +1,5 @@
+---
+title: Build number does not need to be tweaked anymore for the TeamCity integration to work properly.
+merge_request: 23898
+author:
+type: changed
diff --git a/changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml b/changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml
new file mode 100644
index 00000000000..071036cd568
--- /dev/null
+++ b/changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml
@@ -0,0 +1,5 @@
+---
+title: Handle regular job dependencies next to parallelized job dependencies.
+merge_request: 24273
+author:
+type: fixed
diff --git a/changelogs/unreleased/55669-redesign-project-lists-ui-further-improvements.yml b/changelogs/unreleased/55669-redesign-project-lists-ui-further-improvements.yml
deleted file mode 100644
index a51a08c892a..00000000000
--- a/changelogs/unreleased/55669-redesign-project-lists-ui-further-improvements.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: UI improvements for redesigned project lists
-merge_request: 24011
-author:
-type: other
diff --git a/changelogs/unreleased/55716-update-cert-manager-chart-from-v0-5-0-to-v0-5-2.yml b/changelogs/unreleased/55716-update-cert-manager-chart-from-v0-5-0-to-v0-5-2.yml
deleted file mode 100644
index a25ace9d76d..00000000000
--- a/changelogs/unreleased/55716-update-cert-manager-chart-from-v0-5-0-to-v0-5-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update cert-manager chart from v0.5.0 to v0.5.2
-merge_request: 24025
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml b/changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml
deleted file mode 100644
index 5362a781281..00000000000
--- a/changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide spinner on empty activites list on user profile overview
-merge_request: 24063
-author:
-type: other
diff --git a/changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml b/changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml
deleted file mode 100644
index 2ac3599175b..00000000000
--- a/changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix navigation style in docs
-merge_request: 24090
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/55838-remove-gem-install-bundler-from-docker-based-ruby-environments.yml b/changelogs/unreleased/55838-remove-gem-install-bundler-from-docker-based-ruby-environments.yml
deleted file mode 100644
index 08f60d205df..00000000000
--- a/changelogs/unreleased/55838-remove-gem-install-bundler-from-docker-based-ruby-environments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove gem install bundler from Docker-based Ruby environments
-merge_request: 24093
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml b/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml
deleted file mode 100644
index 7dc783cc2b8..00000000000
--- a/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove top margin in modal header titles
-merge_request: 24108
-author:
-type: fixed
diff --git a/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml b/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml
new file mode 100644
index 00000000000..2fbf334f5e9
--- /dev/null
+++ b/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml
@@ -0,0 +1,5 @@
+---
+title: Emoji and cancel button are taller than input in set user status modal
+merge_request: 24173
+author: Dhiraj Bodicherla
+type: fixed
diff --git a/changelogs/unreleased/55945-suggested-change-preview-highlight.yml b/changelogs/unreleased/55945-suggested-change-preview-highlight.yml
new file mode 100644
index 00000000000..997290a5d50
--- /dev/null
+++ b/changelogs/unreleased/55945-suggested-change-preview-highlight.yml
@@ -0,0 +1,5 @@
+---
+title: Fix syntax highlighting for suggested changes preview
+merge_request: 24358
+author:
+type: fixed
diff --git a/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml b/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml
new file mode 100644
index 00000000000..01a162944d3
--- /dev/null
+++ b/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent checking protected_ref? for ambiguous refs.
+merge_request: 24437
+author:
+type: fixed
diff --git a/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml b/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml
new file mode 100644
index 00000000000..407346bbf22
--- /dev/null
+++ b/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml
@@ -0,0 +1,5 @@
+---
+title: Remove horizontal whitespace on user profile overview on small breakpoints
+merge_request: 24189
+author:
+type: other
diff --git a/changelogs/unreleased/56019-archived-stuck.yml b/changelogs/unreleased/56019-archived-stuck.yml
new file mode 100644
index 00000000000..de3698a327b
--- /dev/null
+++ b/changelogs/unreleased/56019-archived-stuck.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes z-index and margins of archived alert in job page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml b/changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml
new file mode 100644
index 00000000000..ff9d4f2c175
--- /dev/null
+++ b/changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml
@@ -0,0 +1,4 @@
+title: Remove multilingual translation from the word "in" in the job details sidebar.
+merge_request: 24192
+author: Nathan Friend
+type: changed
diff --git a/changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml b/changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml
new file mode 100644
index 00000000000..4da14114225
--- /dev/null
+++ b/changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml
@@ -0,0 +1,5 @@
+---
+title: Improves restriction of multiple Kubernetes clusters through API
+merge_request: 24251
+author:
+type: fixed
diff --git a/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml b/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml
new file mode 100644
index 00000000000..92592290ac4
--- /dev/null
+++ b/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml
@@ -0,0 +1,5 @@
+---
+title: Update CI YAML param table with include
+merge_request: !24309
+author:
+type: fixed
diff --git a/changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml b/changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml
new file mode 100644
index 00000000000..8a6adef5dae
--- /dev/null
+++ b/changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Runners IPv6 address overlaps other values
+merge_request: 24531
+author:
+type: fixed
diff --git a/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml b/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml
new file mode 100644
index 00000000000..7c923422534
--- /dev/null
+++ b/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml
@@ -0,0 +1,6 @@
+---
+title: Show CI artifact file size with 3 significant digits on 'browse job artifacts'
+ page
+merge_request: 24387
+author:
+type: fixed
diff --git a/changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml b/changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml
new file mode 100644
index 00000000000..fcfa29977d1
--- /dev/null
+++ b/changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml
@@ -0,0 +1,5 @@
+---
+title: Do not run spam checks on confidential issues
+merge_request: 24453
+author:
+type: fixed
diff --git a/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml b/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml
new file mode 100644
index 00000000000..3494feb9be1
--- /dev/null
+++ b/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unwanted margin above suggested changes.
+merge_request: 24419
+author:
+type: fixed
diff --git a/changelogs/unreleased/56417-update-helm-to-2-12-2.yml b/changelogs/unreleased/56417-update-helm-to-2-12-2.yml
new file mode 100644
index 00000000000..f01915c532f
--- /dev/null
+++ b/changelogs/unreleased/56417-update-helm-to-2-12-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update Helm to 2.12.2 to address Helm client vulnerability
+merge_request: 24418
+author: Takuya Noguchi
+type: security
diff --git a/changelogs/unreleased/56424-fix-gl-form-init-tag-editing.yml b/changelogs/unreleased/56424-fix-gl-form-init-tag-editing.yml
new file mode 100644
index 00000000000..b19b4d650fd
--- /dev/null
+++ b/changelogs/unreleased/56424-fix-gl-form-init-tag-editing.yml
@@ -0,0 +1,5 @@
+---
+title: Fix form functionality for edit tag page
+merge_request: 24645
+author:
+type: fixed
diff --git a/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml b/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml
new file mode 100644
index 00000000000..671e204da21
--- /dev/null
+++ b/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade KaTeX to version 0.10.0
+merge_request: 24478
+author: Andrew Harmon
+type: fixed \ No newline at end of file
diff --git a/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml b/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml
new file mode 100644
index 00000000000..9ef274f3b49
--- /dev/null
+++ b/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent Sidekiq arguments over 10 KB in size from being logged to JSON
+merge_request: 24493
+author:
+type: changed
diff --git a/changelogs/unreleased/56556-fix-markdown-table-border.yml b/changelogs/unreleased/56556-fix-markdown-table-border.yml
new file mode 100644
index 00000000000..7724f49d4e9
--- /dev/null
+++ b/changelogs/unreleased/56556-fix-markdown-table-border.yml
@@ -0,0 +1,5 @@
+---
+title: Fix markdown table border.
+merge_request: 24601
+author:
+type: fixed
diff --git a/changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml b/changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml
new file mode 100644
index 00000000000..52b2db0e999
--- /dev/null
+++ b/changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml
@@ -0,0 +1,5 @@
+---
+title: Load initUserInternalRegexPlaceholder only when required
+merge_request: 24522
+author:
+type: fixed
diff --git a/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml b/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml
new file mode 100644
index 00000000000..1f808850554
--- /dev/null
+++ b/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml
@@ -0,0 +1,5 @@
+---
+title: 'Hashed Storage: `AfterRenameService` was receiving the wrong `old_path` under some circunstances'
+merge_request: 24526
+author:
+type: fixed
diff --git a/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml b/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml
new file mode 100644
index 00000000000..cd7b56a1e05
--- /dev/null
+++ b/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml
@@ -0,0 +1,5 @@
+---
+title: Creates mixin to reduce code duplication between CE and EE in graph component
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml b/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml
new file mode 100644
index 00000000000..49511294c48
--- /dev/null
+++ b/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml
@@ -0,0 +1,5 @@
+---
+title: Proper align Projects dropdown on issue boards page
+merge_request: 24277
+author: Johann Hubert Sonntagbauer
+type: fixed
diff --git a/changelogs/unreleased/ac-pages-subgroups.yml b/changelogs/unreleased/ac-pages-subgroups.yml
new file mode 100644
index 00000000000..ef5a0c1872e
--- /dev/null
+++ b/changelogs/unreleased/ac-pages-subgroups.yml
@@ -0,0 +1,5 @@
+---
+title: Pages for subgroups
+merge_request: 23505
+author:
+type: added
diff --git a/changelogs/unreleased/ac-releases-api.yml b/changelogs/unreleased/ac-releases-api.yml
deleted file mode 100644
index 87217cce371..00000000000
--- a/changelogs/unreleased/ac-releases-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Releases API
-merge_request: 23795
-author:
-type: added
diff --git a/changelogs/unreleased/ac-releases-name-sha-author.yml b/changelogs/unreleased/ac-releases-name-sha-author.yml
deleted file mode 100644
index e84b82847eb..00000000000
--- a/changelogs/unreleased/ac-releases-name-sha-author.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add name, author_id, and sha to releases table
-merge_request: 23763
-author:
-type: added
diff --git a/changelogs/unreleased/actioncontroller-parameters-deprecations.yml b/changelogs/unreleased/actioncontroller-parameters-deprecations.yml
new file mode 100644
index 00000000000..ddd15c37542
--- /dev/null
+++ b/changelogs/unreleased/actioncontroller-parameters-deprecations.yml
@@ -0,0 +1,5 @@
+---
+title: Fix several ActionController::Parameters deprecations
+merge_request: 24332
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml b/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml
new file mode 100644
index 00000000000..e200bbaa806
--- /dev/null
+++ b/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml
@@ -0,0 +1,5 @@
+---
+title: Add badge count to projects
+merge_request: 18425
+author: George Tsiolis
+type: added
diff --git a/changelogs/unreleased/add-new-nginx-metrics.yml b/changelogs/unreleased/add-new-nginx-metrics.yml
deleted file mode 100644
index 57221056d6e..00000000000
--- a/changelogs/unreleased/add-new-nginx-metrics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add NGINX 0.16.0 and above metrics
-merge_request: 22133
-author:
-type: added
diff --git a/changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml b/changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml
new file mode 100644
index 00000000000..7d767e220f7
--- /dev/null
+++ b/changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml
@@ -0,0 +1,5 @@
+---
+title: Add uniqueness validation to url column in Releases::Link model
+merge_request: 24223
+author:
+type: other
diff --git a/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml b/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml
deleted file mode 100644
index fda3fdc28cf..00000000000
--- a/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow basic authentication on go get middleware
-merge_request: 23497
-author: Morty Choi @mortyccp
-type: changed
diff --git a/changelogs/unreleased/allow_collaboration_status_work.yml b/changelogs/unreleased/allow_collaboration_status_work.yml
deleted file mode 100644
index 3cf8f13ffea..00000000000
--- a/changelogs/unreleased/allow_collaboration_status_work.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update a condition to visibility a merge request collaboration message
-merge_request: 23104
-author: Harry Kiselev
-type: other
diff --git a/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml b/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml
new file mode 100644
index 00000000000..5365260cbae
--- /dev/null
+++ b/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid overwriting default jaeger values with nil
+merge_request: 24482
+author:
+type: fixed
diff --git a/changelogs/unreleased/an-gilab-process-name.yml b/changelogs/unreleased/an-gilab-process-name.yml
new file mode 100644
index 00000000000..72d811ee21f
--- /dev/null
+++ b/changelogs/unreleased/an-gilab-process-name.yml
@@ -0,0 +1,5 @@
+---
+title: Extract process_name from GitLab::Sentry
+merge_request: 24422
+author:
+type: other
diff --git a/changelogs/unreleased/an-opentracing-active-record-tracing.yml b/changelogs/unreleased/an-opentracing-active-record-tracing.yml
new file mode 100644
index 00000000000..59b480675ec
--- /dev/null
+++ b/changelogs/unreleased/an-opentracing-active-record-tracing.yml
@@ -0,0 +1,5 @@
+---
+title: Adds tracing support for ActiveRecord notifications
+merge_request: 24604
+author:
+type: other
diff --git a/changelogs/unreleased/an-opentracing-factory.yml b/changelogs/unreleased/an-opentracing-factory.yml
new file mode 100644
index 00000000000..c04736f3e63
--- /dev/null
+++ b/changelogs/unreleased/an-opentracing-factory.yml
@@ -0,0 +1,5 @@
+---
+title: Conditionally initialize the global opentracing tracer
+merge_request: 24186
+author:
+type: other
diff --git a/changelogs/unreleased/an-opentracing-propagation.yml b/changelogs/unreleased/an-opentracing-propagation.yml
new file mode 100644
index 00000000000..d9aa7cd0048
--- /dev/null
+++ b/changelogs/unreleased/an-opentracing-propagation.yml
@@ -0,0 +1,5 @@
+---
+title: Adds inter-service OpenTracing propagation
+merge_request: 24239
+author:
+type: other
diff --git a/changelogs/unreleased/api-nested-group-permission.yml b/changelogs/unreleased/api-nested-group-permission.yml
new file mode 100644
index 00000000000..3ec0df6893f
--- /dev/null
+++ b/changelogs/unreleased/api-nested-group-permission.yml
@@ -0,0 +1,5 @@
+---
+title: Return the maximum group access level in the projects API
+merge_request: 24403
+author:
+type: changed
diff --git a/changelogs/unreleased/api-tags-search.yml b/changelogs/unreleased/api-tags-search.yml
new file mode 100644
index 00000000000..1501acd5a9e
--- /dev/null
+++ b/changelogs/unreleased/api-tags-search.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Support searching for tags'
+merge_request: 24385
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/api-wiki-dot-slug.yml b/changelogs/unreleased/api-wiki-dot-slug.yml
new file mode 100644
index 00000000000..82c76fa7450
--- /dev/null
+++ b/changelogs/unreleased/api-wiki-dot-slug.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Support dots in wiki slugs'
+merge_request: 24383
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/auto-devops-custom-domains.yml b/changelogs/unreleased/auto-devops-custom-domains.yml
new file mode 100644
index 00000000000..37e8ee26a4d
--- /dev/null
+++ b/changelogs/unreleased/auto-devops-custom-domains.yml
@@ -0,0 +1,5 @@
+---
+title: Added support for custom hosts/domains to Auto DevOps
+merge_request: 24248
+author: walkafwalka
+type: added
diff --git a/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml b/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml
new file mode 100644
index 00000000000..1a8cdead4ac
--- /dev/null
+++ b/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml
@@ -0,0 +1,5 @@
+---
+title: Bump kubectl in Auto DevOps to 1.11.6
+merge_request: 24176
+author:
+type: other
diff --git a/changelogs/unreleased/backup_aws_sse-c.yml b/changelogs/unreleased/backup_aws_sse-c.yml
new file mode 100644
index 00000000000..78b57d7efc3
--- /dev/null
+++ b/changelogs/unreleased/backup_aws_sse-c.yml
@@ -0,0 +1,5 @@
+title: Add support for customer provided encryption keys for Amazon S3 remote backups
+merge_request: 23797
+author: Pepijn Van Eeckhoudt
+type: added
+
diff --git a/changelogs/unreleased/backup_restore_fix_issue_46891.yml b/changelogs/unreleased/backup_restore_fix_issue_46891.yml
new file mode 100644
index 00000000000..b8fe3b1b861
--- /dev/null
+++ b/changelogs/unreleased/backup_restore_fix_issue_46891.yml
@@ -0,0 +1,5 @@
+---
+title: Modify file restore to rectify tar issue
+merge_request: 24000
+author:
+type: fixed
diff --git a/changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml b/changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml
deleted file mode 100644
index dfa94c69ce0..00000000000
--- a/changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump Ruby on Rails to 5.0.7.1
-merge_request: 23396
-author: "@blackst0ne"
-type: security
diff --git a/changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml b/changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml
deleted file mode 100644
index c29cfec075c..00000000000
--- a/changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "[Rails5.1] Update functional specs to use new keyword format"
-merge_request: 23095
-author: "@blackst0ne"
-type: other
diff --git a/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml b/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml
deleted file mode 100644
index 09480499b87..00000000000
--- a/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update specs to exclude possible false positive pass
-merge_request: 23893
-author: "@blackst0ne"
-type: other
diff --git a/changelogs/unreleased/bump-ingress-chart-112.yml b/changelogs/unreleased/bump-ingress-chart-112.yml
new file mode 100644
index 00000000000..8a46fedb4b0
--- /dev/null
+++ b/changelogs/unreleased/bump-ingress-chart-112.yml
@@ -0,0 +1,5 @@
+---
+title: Bump nginx-ingress chart to 1.1.2
+merge_request: 24203
+author:
+type: other
diff --git a/changelogs/unreleased/bvl-hide-confidential-events-take2.yml b/changelogs/unreleased/bvl-hide-confidential-events-take2.yml
deleted file mode 100644
index a5abd496a9d..00000000000
--- a/changelogs/unreleased/bvl-hide-confidential-events-take2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide confidential events in the API
-merge_request: 23746
-author:
-type: other
diff --git a/changelogs/unreleased/ccr-49289_milestone_link.yml b/changelogs/unreleased/ccr-49289_milestone_link.yml
deleted file mode 100644
index 14c09752a24..00000000000
--- a/changelogs/unreleased/ccr-49289_milestone_link.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add project milestone link
-merge_request: 22552
-author:
-type: added
diff --git a/changelogs/unreleased/ci-dropdown-hidden-bug.yml b/changelogs/unreleased/ci-dropdown-hidden-bug.yml
deleted file mode 100644
index 6910f04a6d5..00000000000
--- a/changelogs/unreleased/ci-dropdown-hidden-bug.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't hide CI dropdown behind diff summary
-merge_request:
-author: gfyoung
-type: fixed
diff --git a/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml b/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml
new file mode 100644
index 00000000000..6e8dac97249
--- /dev/null
+++ b/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Cleanup legacy artifact background migration
+merge_request: 24144
+author:
+type: other
diff --git a/changelogs/unreleased/container-repository-cleanup-api.yml b/changelogs/unreleased/container-repository-cleanup-api.yml
new file mode 100644
index 00000000000..c2b23a9add0
--- /dev/null
+++ b/changelogs/unreleased/container-repository-cleanup-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add Container Registry API with cleanup function
+merge_request: 24303
+author:
+type: added
diff --git a/changelogs/unreleased/custom-helm-chart-repo.yml b/changelogs/unreleased/custom-helm-chart-repo.yml
new file mode 100644
index 00000000000..592d2f60ca2
--- /dev/null
+++ b/changelogs/unreleased/custom-helm-chart-repo.yml
@@ -0,0 +1,5 @@
+---
+title: Added feature to specify a custom Auto DevOps chart repository
+merge_request: 24162
+author: walkafwalka
+type: added
diff --git a/changelogs/unreleased/depracated-migration-inheritance.yml b/changelogs/unreleased/depracated-migration-inheritance.yml
deleted file mode 100644
index 1ea9b2df59c..00000000000
--- a/changelogs/unreleased/depracated-migration-inheritance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: ActiveRecord::Migration -> ActiveRecord::Migration[5.0]
-merge_request: 23910
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml b/changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml
deleted file mode 100644
index 9cfb00a9544..00000000000
--- a/changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove deprecated ActionDispatch::ParamsParser
-merge_request: 23848
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-alias-method-chain.yml b/changelogs/unreleased/deprecated-alias-method-chain.yml
deleted file mode 100644
index 76dd016e4cc..00000000000
--- a/changelogs/unreleased/deprecated-alias-method-chain.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: 'Fix deprecation: alias_method_chain is deprecated. Please, use Module#prepend
- instead'
-merge_request: 23887
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml b/changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml
deleted file mode 100644
index a7b9d054a4c..00000000000
--- a/changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: 'Fix deprecation: Comparing equality between ActionController::Parameters and
- a Hash is deprecated'
-merge_request: 23855
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-delete-all-params.yml b/changelogs/unreleased/deprecated-delete-all-params.yml
deleted file mode 100644
index e23fe92a738..00000000000
--- a/changelogs/unreleased/deprecated-delete-all-params.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix deprecation: Passing conditions to delete_all is deprecated'
-merge_request: 23817
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-directly-inheriting-migration.yml b/changelogs/unreleased/deprecated-directly-inheriting-migration.yml
deleted file mode 100644
index 2793cc0d44f..00000000000
--- a/changelogs/unreleased/deprecated-directly-inheriting-migration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix deprecation: Directly inheriting from ActiveRecord::Migration is deprecated.'
-merge_request: 23884
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-force-reload.yml b/changelogs/unreleased/deprecated-force-reload.yml
new file mode 100644
index 00000000000..2a0e97089e0
--- /dev/null
+++ b/changelogs/unreleased/deprecated-force-reload.yml
@@ -0,0 +1,6 @@
+---
+title: 'Fix deprecation: Passing an argument to force an association to reload is
+ now deprecated'
+merge_request: 24136
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/deprecated-insert-sql.yml b/changelogs/unreleased/deprecated-insert-sql.yml
deleted file mode 100644
index ad21fbd9dde..00000000000
--- a/changelogs/unreleased/deprecated-insert-sql.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix deprecation: insert_sql is deprecated and will be removed'
-merge_request: 23944
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-passing-activerecord-objects.yml b/changelogs/unreleased/deprecated-passing-activerecord-objects.yml
deleted file mode 100644
index e58647186b8..00000000000
--- a/changelogs/unreleased/deprecated-passing-activerecord-objects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix deprecation: Passing ActiveRecord::Base objects to sanitize_sql_hash_for_assignment'
-merge_request: 23818
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-positional-seperator-parameter.yml b/changelogs/unreleased/deprecated-positional-seperator-parameter.yml
deleted file mode 100644
index 0d952e0d5eb..00000000000
--- a/changelogs/unreleased/deprecated-positional-seperator-parameter.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Passing the separator argument as a positional parameter is deprecated
-merge_request: 23334
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-positional-spec-arguments.yml b/changelogs/unreleased/deprecated-positional-spec-arguments.yml
deleted file mode 100644
index 8e541df1ad4..00000000000
--- a/changelogs/unreleased/deprecated-positional-spec-arguments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix deprecation: Using positional arguments in integration tests'
-merge_request: 24110
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/deprecated-redirect-back.yml b/changelogs/unreleased/deprecated-redirect-back.yml
deleted file mode 100644
index 7fc567fbdb5..00000000000
--- a/changelogs/unreleased/deprecated-redirect-back.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix deprecation: redirect_to :back is deprecated'
-merge_request: 23943
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/diff-empty-state-fixes.yml b/changelogs/unreleased/diff-empty-state-fixes.yml
deleted file mode 100644
index 0d347dd17e4..00000000000
--- a/changelogs/unreleased/diff-empty-state-fixes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed merge request diffs empty states
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/diff-tree-collapse-directories.yml b/changelogs/unreleased/diff-tree-collapse-directories.yml
new file mode 100644
index 00000000000..6eae48f2352
--- /dev/null
+++ b/changelogs/unreleased/diff-tree-collapse-directories.yml
@@ -0,0 +1,5 @@
+---
+title: Collapse directory structure in merge request file tree
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/dm-note-email-image-diff-discussion.yml b/changelogs/unreleased/dm-note-email-image-diff-discussion.yml
deleted file mode 100644
index 6532052e132..00000000000
--- a/changelogs/unreleased/dm-note-email-image-diff-discussion.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix notification email for image diff notes
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml b/changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml
new file mode 100644
index 00000000000..1e1fa8295c3
--- /dev/null
+++ b/changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug that caused Suggestion Markdown toolbar button to insert snippet with leading +/-/<space>
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml b/changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml
new file mode 100644
index 00000000000..4539a9b7985
--- /dev/null
+++ b/changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml
@@ -0,0 +1,5 @@
+---
+title: Updated docs for fields in pushing mirror from GitLab to GitHub
+merge_request: 24566
+author: Joseph Yu
+type: other
diff --git a/changelogs/unreleased/expire-job-artifacts-worker.yml b/changelogs/unreleased/expire-job-artifacts-worker.yml
new file mode 100644
index 00000000000..cda6e9ff497
--- /dev/null
+++ b/changelogs/unreleased/expire-job-artifacts-worker.yml
@@ -0,0 +1,5 @@
+---
+title: Efficiently remove expired artifacts in `ExpireBuildArtifactsWorker`
+merge_request: 24450
+author:
+type: performance
diff --git a/changelogs/unreleased/feature-option-to-make-variables-protected.yml b/changelogs/unreleased/feature-option-to-make-variables-protected.yml
deleted file mode 100644
index c99c0481c35..00000000000
--- a/changelogs/unreleased/feature-option-to-make-variables-protected.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add option to make ci variables protected by default
-merge_request: 22744
-author: Alexis Reigel
-type: added
diff --git a/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml b/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml
new file mode 100644
index 00000000000..b224cace4bf
--- /dev/null
+++ b/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml
@@ -0,0 +1,5 @@
+---
+title: Document graphicsmagick installation for source installation
+merge_request: 24404
+author: Alexis Reigel
+type: added
diff --git a/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml b/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml
new file mode 100644
index 00000000000..eda69b32094
--- /dev/null
+++ b/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml
@@ -0,0 +1,5 @@
+---
+title: Show the correct error page when access is denied
+merge_request: 23932
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-55448.yml b/changelogs/unreleased/fix-55448.yml
deleted file mode 100644
index e0bdbb6eda4..00000000000
--- a/changelogs/unreleased/fix-55448.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove deprecated xhr from specs
-merge_request: 23949
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml b/changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml
new file mode 100644
index 00000000000..e33699a2112
--- /dev/null
+++ b/changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed oversized custom project notification selector dropdown
+merge_request: 24557
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-56558-move-primary-button.yml b/changelogs/unreleased/fix-56558-move-primary-button.yml
new file mode 100644
index 00000000000..4dcc896b327
--- /dev/null
+++ b/changelogs/unreleased/fix-56558-move-primary-button.yml
@@ -0,0 +1,5 @@
+---
+title: Moved primary button for labels to follow the design patterns used on rest of the site
+merge_request:
+author: Martin Hobert
+type: fixed
diff --git a/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml b/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml
new file mode 100644
index 00000000000..bb0b193a846
--- /dev/null
+++ b/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes Auto DevOps title on CI/CD admin settings
+merge_request: 24249
+author:
+type: other
diff --git a/changelogs/unreleased/fix-calendar-events-fetching-error.yml b/changelogs/unreleased/fix-calendar-events-fetching-error.yml
deleted file mode 100644
index ad4a40cd9a0..00000000000
--- a/changelogs/unreleased/fix-calendar-events-fetching-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix calendar events fetching error on private profile page
-merge_request: 23718
-author: Harry Kiselev
-type: other
diff --git a/changelogs/unreleased/fix-n-plus-1-queries-projects.yml b/changelogs/unreleased/fix-n-plus-1-queries-projects.yml
deleted file mode 100644
index cb625784267..00000000000
--- a/changelogs/unreleased/fix-n-plus-1-queries-projects.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix some N+1 queries related to Admin Dashboard, User Dashboards and Activity
- Stream
-merge_request: 23034
-author:
-type: performance
diff --git a/changelogs/unreleased/fj-44679-skip-per-commit-validations.yml b/changelogs/unreleased/fj-44679-skip-per-commit-validations.yml
deleted file mode 100644
index 3f9754409df..00000000000
--- a/changelogs/unreleased/fj-44679-skip-per-commit-validations.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Skip per-commit validations already evaluated
-merge_request: 23984
-author:
-type: performance
diff --git a/changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml b/changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml
new file mode 100644
index 00000000000..f64b29644b0
--- /dev/null
+++ b/changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml
@@ -0,0 +1,5 @@
+---
+title: Fix files/blob api endpoints content disposition
+merge_request: 24267
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-fix-lfs-image-comments-diffs.yml b/changelogs/unreleased/fj-fix-lfs-image-comments-diffs.yml
deleted file mode 100644
index dc1fe5d7417..00000000000
--- a/changelogs/unreleased/fj-fix-lfs-image-comments-diffs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix bug commenting on LFS images
-merge_request: 23812
-author:
-type: fixed
diff --git a/changelogs/unreleased/force-redeploy-on-updated-secrets.yml b/changelogs/unreleased/force-redeploy-on-updated-secrets.yml
new file mode 100644
index 00000000000..3b727c99dd5
--- /dev/null
+++ b/changelogs/unreleased/force-redeploy-on-updated-secrets.yml
@@ -0,0 +1,5 @@
+---
+title: Redeploy Auto DevOps deployment on variable updates
+merge_request: 24498
+author: walkafwalka
+type: added
diff --git a/changelogs/unreleased/force-reload-arguments-2.yml b/changelogs/unreleased/force-reload-arguments-2.yml
deleted file mode 100644
index 23ab9433b3d..00000000000
--- a/changelogs/unreleased/force-reload-arguments-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Passing an argument to force an association to reload is now deprecated
-merge_request: 23894
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/gitaly-update-1-13-0.yml b/changelogs/unreleased/gitaly-update-1-13-0.yml
new file mode 100644
index 00000000000..73de25a532d
--- /dev/null
+++ b/changelogs/unreleased/gitaly-update-1-13-0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Gitaly to 1.13.0
+merge_request: 24429
+author:
+type: other
diff --git a/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml b/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml
new file mode 100644
index 00000000000..1e0160c4d40
--- /dev/null
+++ b/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade gitlab-workhorse to 8.1.0
+merge_request: 24571
+author:
+type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-ci.yml b/changelogs/unreleased/gt-externalize-app-views-projects-ci.yml
new file mode 100644
index 00000000000..ecc878ab892
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-app-views-projects-ci.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings from `/app/views/projects/ci`
+merge_request: 24617
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml b/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml
new file mode 100644
index 00000000000..1acea10fcaa
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings from `/app/views/projects/project_members`
+merge_request: 23227
+author: Tao Wang
+type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml b/changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml
new file mode 100644
index 00000000000..e77b5376fa8
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings from `/app/views/sent_notifications`
+merge_request: 24576
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/gt-remove-unnecessary-line-before-reply-holder.yml b/changelogs/unreleased/gt-remove-unnecessary-line-before-reply-holder.yml
deleted file mode 100644
index 142a9c1f2cc..00000000000
--- a/changelogs/unreleased/gt-remove-unnecessary-line-before-reply-holder.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove unnecessary line before reply holder
-merge_request: 23092
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/gt-remove-unused-button-class.yml b/changelogs/unreleased/gt-remove-unused-button-class.yml
new file mode 100644
index 00000000000..f7889e1d6f6
--- /dev/null
+++ b/changelogs/unreleased/gt-remove-unused-button-class.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unused button classes `btn-create` and `comment-btn`
+merge_request: 23232
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml b/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml
new file mode 100644
index 00000000000..b612bb3ee39
--- /dev/null
+++ b/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml
@@ -0,0 +1,5 @@
+---
+title: Remove all `$theme-gray-{weight}` variables in favor of `$gray-{weight}`
+merge_request: 24333
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml b/changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml
deleted file mode 100644
index b1ecf2bb1ed..00000000000
--- a/changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Reorder sidebar menu item for group clusters
-merge_request: 24001
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/gt-update-environment-breadcrumb.yml b/changelogs/unreleased/gt-update-environment-breadcrumb.yml
deleted file mode 100644
index 53b9673a96c..00000000000
--- a/changelogs/unreleased/gt-update-environment-breadcrumb.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update environments breadcrumb
-merge_request: 23751
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/gt-update-navigation-theme-colors.yml b/changelogs/unreleased/gt-update-navigation-theme-colors.yml
deleted file mode 100644
index 749587a6343..00000000000
--- a/changelogs/unreleased/gt-update-navigation-theme-colors.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update header navigation theme colors
-merge_request: 23734
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/gt-update-string-struture-for-group-runners.yml b/changelogs/unreleased/gt-update-string-struture-for-group-runners.yml
new file mode 100644
index 00000000000..fa06a78adae
--- /dev/null
+++ b/changelogs/unreleased/gt-update-string-struture-for-group-runners.yml
@@ -0,0 +1,5 @@
+---
+title: Update string structure for available group runners
+merge_request: 24187
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/homepage-proj-descr-cutoff.yml b/changelogs/unreleased/homepage-proj-descr-cutoff.yml
new file mode 100644
index 00000000000..837c01f6722
--- /dev/null
+++ b/changelogs/unreleased/homepage-proj-descr-cutoff.yml
@@ -0,0 +1,5 @@
+---
+title: Increase line height of project summaries
+merge_request:
+author: gfyoung
+type: fixed
diff --git a/changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml b/changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml
new file mode 100644
index 00000000000..8025cd472bd
--- /dev/null
+++ b/changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml
@@ -0,0 +1,6 @@
+---
+title: Fix lost line number when navigating to a specific line in a protected file
+ before authenticating.
+merge_request: 19165
+author: Scott Escue
+type: fixed
diff --git a/changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml b/changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml
deleted file mode 100644
index abece81a20d..00000000000
--- a/changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add new pipeline variable CI_COMMIT_SHORT_SHA
-merge_request: 23822
-author:
-type: added
diff --git a/changelogs/unreleased/knative-show-page.yml b/changelogs/unreleased/knative-show-page.yml
new file mode 100644
index 00000000000..a48b754940f
--- /dev/null
+++ b/changelogs/unreleased/knative-show-page.yml
@@ -0,0 +1,5 @@
+---
+title: Add Knative detailed view
+merge_request: 23863
+author: Chris Baumbauer
+type: added
diff --git a/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml b/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml
new file mode 100644
index 00000000000..932850cc825
--- /dev/null
+++ b/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml
@@ -0,0 +1,5 @@
+---
+title: Fix cluster page non-interactive on form validation error
+merge_request: 24583
+author:
+type: fixed
diff --git a/changelogs/unreleased/mk-avoid-read-only-error.yml b/changelogs/unreleased/mk-avoid-read-only-error.yml
deleted file mode 100644
index 8641f5db9f0..00000000000
--- a/changelogs/unreleased/mk-avoid-read-only-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent admins from attempting hashed storage migration on read only DB
-merge_request: 23597
-author:
-type: fixed
diff --git a/changelogs/unreleased/monospace-registry-tags.yml b/changelogs/unreleased/monospace-registry-tags.yml
new file mode 100644
index 00000000000..b5992707d8c
--- /dev/null
+++ b/changelogs/unreleased/monospace-registry-tags.yml
@@ -0,0 +1,5 @@
+---
+title: Use monospace font for registry table tag id and tag name
+merge_request: 24205
+author:
+type: other
diff --git a/changelogs/unreleased/move-job-cancel-btn.yml b/changelogs/unreleased/move-job-cancel-btn.yml
new file mode 100644
index 00000000000..41f8e1be5f8
--- /dev/null
+++ b/changelogs/unreleased/move-job-cancel-btn.yml
@@ -0,0 +1,5 @@
+---
+title: Move cancel & new issue button on job page
+merge_request: 24074
+author:
+type: changed
diff --git a/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml b/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml
new file mode 100644
index 00000000000..b01962591c6
--- /dev/null
+++ b/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml
@@ -0,0 +1,5 @@
+---
+title: Add folder header to files in merge request tree list
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/mr-rebase-failing-tests.yml b/changelogs/unreleased/mr-rebase-failing-tests.yml
new file mode 100644
index 00000000000..07ae05766b1
--- /dev/null
+++ b/changelogs/unreleased/mr-rebase-failing-tests.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed rebase button not showing in merge request widget
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/none-syntax-highlighting.yml b/changelogs/unreleased/none-syntax-highlighting.yml
deleted file mode 100644
index b373aac7c02..00000000000
--- a/changelogs/unreleased/none-syntax-highlighting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add no-color theme for syntax highlighting.
-merge_request: !20170
-author: khm
-type: added
diff --git a/changelogs/unreleased/notebook-multiple-outputs.yml b/changelogs/unreleased/notebook-multiple-outputs.yml
new file mode 100644
index 00000000000..38cc52c0634
--- /dev/null
+++ b/changelogs/unreleased/notebook-multiple-outputs.yml
@@ -0,0 +1,5 @@
+---
+title: Support multiple outputs in jupyter notebooks
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/notes-awards-double-tooltip-fix.yml b/changelogs/unreleased/notes-awards-double-tooltip-fix.yml
new file mode 100644
index 00000000000..23338a60c2a
--- /dev/null
+++ b/changelogs/unreleased/notes-awards-double-tooltip-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed double tooltips on note awards buttons
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml b/changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml
deleted file mode 100644
index 7abc7d85794..00000000000
--- a/changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Improve the loading time on merge request's discussion page by caching diff
- highlight
-merge_request: 23857
-author:
-type: performance
diff --git a/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml b/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml
new file mode 100644
index 00000000000..6a2a67e7aa8
--- /dev/null
+++ b/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml
@@ -0,0 +1,5 @@
+---
+title: Cleanup stale +deleted repo paths on project removal (adjusts project removal bug)
+merge_request: 24269
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml b/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml
new file mode 100644
index 00000000000..b2ac53312ae
--- /dev/null
+++ b/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml
@@ -0,0 +1,5 @@
+---
+title: Adjusts duplicated line when commenting on unfolded diff lines (in the bottom)
+merge_request: 24201
+author:
+type: fixed
diff --git a/changelogs/unreleased/profile-project-empty-state.yml b/changelogs/unreleased/profile-project-empty-state.yml
new file mode 100644
index 00000000000..484306d5b98
--- /dev/null
+++ b/changelogs/unreleased/profile-project-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Added empty project illustration and updated text to user profile overview
+merge_request: 23973
+author: Fernando Arias
+type: changed
diff --git a/changelogs/unreleased/raise-on-unfiltered-params.yml b/changelogs/unreleased/raise-on-unfiltered-params.yml
new file mode 100644
index 00000000000..531e9ba807e
--- /dev/null
+++ b/changelogs/unreleased/raise-on-unfiltered-params.yml
@@ -0,0 +1,5 @@
+---
+title: Actually set raise_on_unfiltered_parameters to true
+merge_request: 24443
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/remote-mirror-update-failed-notification.yml b/changelogs/unreleased/remote-mirror-update-failed-notification.yml
deleted file mode 100644
index 50ec8624ae5..00000000000
--- a/changelogs/unreleased/remote-mirror-update-failed-notification.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Send a notification email to project maintainers when a mirror update fails
-merge_request: 23595
-author:
-type: added
diff --git a/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml b/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml
new file mode 100644
index 00000000000..06546bc5a8e
--- /dev/null
+++ b/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Cancel all jobs button in general jobs list view
+merge_request:
+author: Jordi Llull
+type: removed
diff --git a/changelogs/unreleased/remove-diff-coloring.yml b/changelogs/unreleased/remove-diff-coloring.yml
new file mode 100644
index 00000000000..1ee1b525c35
--- /dev/null
+++ b/changelogs/unreleased/remove-diff-coloring.yml
@@ -0,0 +1,5 @@
+---
+title: 'remove red/green colors from diff view of no-color syntax theme'
+merge_request: 24582
+author: khm
+type: changed
diff --git a/changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml b/changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml
new file mode 100644
index 00000000000..ce8e1829b48
--- /dev/null
+++ b/changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml
@@ -0,0 +1,5 @@
+---
+title: Remove extra space between MR tab bar and sticky file headers
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/remove-rails4-specific-code.yml b/changelogs/unreleased/remove-rails4-specific-code.yml
deleted file mode 100644
index c6c4c0a5d5b..00000000000
--- a/changelogs/unreleased/remove-rails4-specific-code.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove rails4 specific code
-merge_request: 23847
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/remove-rails4-support.yml b/changelogs/unreleased/remove-rails4-support.yml
deleted file mode 100644
index a05c913a70c..00000000000
--- a/changelogs/unreleased/remove-rails4-support.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove rails 4 support in CI, Gemfiles, bin/ and config/
-merge_request: 23717
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/s3-directories-get.yml b/changelogs/unreleased/s3-directories-get.yml
deleted file mode 100644
index 9f76af2bb09..00000000000
--- a/changelogs/unreleased/s3-directories-get.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Allow 'rake gitlab:cleanup:remote_upload_files' to read bucket files without
- having permissions to see all buckets.
-merge_request: 23981
-author:
-type: fixed
diff --git a/changelogs/unreleased/security-2770-verify-bundle-import-files.yml b/changelogs/unreleased/security-2770-verify-bundle-import-files.yml
new file mode 100644
index 00000000000..dea40dd1ef1
--- /dev/null
+++ b/changelogs/unreleased/security-2770-verify-bundle-import-files.yml
@@ -0,0 +1,5 @@
+---
+title: Validate bundle files before unpacking them
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-48259-private-snippet.yml b/changelogs/unreleased/security-48259-private-snippet.yml
deleted file mode 100644
index 6cf1e5dc694..00000000000
--- a/changelogs/unreleased/security-48259-private-snippet.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent private snippets from being embeddable
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-53543-user-keeps-access-to-mr-issue-when-removed-from-team.yml b/changelogs/unreleased/security-53543-user-keeps-access-to-mr-issue-when-removed-from-team.yml
deleted file mode 100644
index ab12ba539c1..00000000000
--- a/changelogs/unreleased/security-53543-user-keeps-access-to-mr-issue-when-removed-from-team.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Issuable no longer is visible to users when project can't be viewed
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-54377-label-milestone-name-xss.yml b/changelogs/unreleased/security-54377-label-milestone-name-xss.yml
deleted file mode 100644
index 76589b2eb4f..00000000000
--- a/changelogs/unreleased/security-54377-label-milestone-name-xss.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Escape label and milestone titles to prevent XSS in GFM autocomplete
-merge_request: 2693
-author:
-type: security
diff --git a/changelogs/unreleased/security-bvl-fix-cross-project-mr-exposure.yml b/changelogs/unreleased/security-bvl-fix-cross-project-mr-exposure.yml
deleted file mode 100644
index 11aae4428fb..00000000000
--- a/changelogs/unreleased/security-bvl-fix-cross-project-mr-exposure.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't expose cross project repositories through diffs when creating merge reqeusts
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-fix-ssrf-import-url-remote-mirror.yml b/changelogs/unreleased/security-fix-ssrf-import-url-remote-mirror.yml
deleted file mode 100644
index 7ba7aa21090..00000000000
--- a/changelogs/unreleased/security-fix-ssrf-import-url-remote-mirror.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix SSRF with import_url and remote mirror url
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-master-group-cicd-settings-accessible-to-maintainer.yml b/changelogs/unreleased/security-master-group-cicd-settings-accessible-to-maintainer.yml
deleted file mode 100644
index 5586fa6cd8e..00000000000
--- a/changelogs/unreleased/security-master-group-cicd-settings-accessible-to-maintainer.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow changing group CI/CD settings only for owners.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-master-guests-jobs-api.yml b/changelogs/unreleased/security-master-guests-jobs-api.yml
deleted file mode 100644
index 83022e91aca..00000000000
--- a/changelogs/unreleased/security-master-guests-jobs-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Authorize before reading job information via API.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-master-secret-ci-variables-exposed.yml b/changelogs/unreleased/security-master-secret-ci-variables-exposed.yml
deleted file mode 100644
index 702181065f5..00000000000
--- a/changelogs/unreleased/security-master-secret-ci-variables-exposed.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent leaking protected variables for ambiguous refs.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-master-url-rel.yml b/changelogs/unreleased/security-master-url-rel.yml
deleted file mode 100644
index 75f599f6bcd..00000000000
--- a/changelogs/unreleased/security-master-url-rel.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Set URL rel attribute for broken URLs.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-refs-available-to-project-guest.yml b/changelogs/unreleased/security-refs-available-to-project-guest.yml
deleted file mode 100644
index eb6804c52d3..00000000000
--- a/changelogs/unreleased/security-refs-available-to-project-guest.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Project guests no longer are able to see refs page
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-todos_not_redacted_for_guests.yml b/changelogs/unreleased/security-todos_not_redacted_for_guests.yml
deleted file mode 100644
index be0ae9a7193..00000000000
--- a/changelogs/unreleased/security-todos_not_redacted_for_guests.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Delete confidential todos for user when downgraded to Guest
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/sh-bump-omniauth-google-gem.yml b/changelogs/unreleased/sh-bump-omniauth-google-gem.yml
deleted file mode 100644
index 2b31a55f8b2..00000000000
--- a/changelogs/unreleased/sh-bump-omniauth-google-gem.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade Omniauth and JWT gems to switch away from Google+ API
-merge_request: 24068
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-cache-avatar-paths.yml b/changelogs/unreleased/sh-cache-avatar-paths.yml
deleted file mode 100644
index b59a4db413d..00000000000
--- a/changelogs/unreleased/sh-cache-avatar-paths.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache avatar URLs and paths within a request
-merge_request: 23950
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml b/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml
deleted file mode 100644
index 206253a100c..00000000000
--- a/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix object storage not working properly with Google S3 compatibility
-merge_request: 23858
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-drop-webhooks-project-export.yml b/changelogs/unreleased/sh-drop-webhooks-project-export.yml
deleted file mode 100644
index 217373bce66..00000000000
--- a/changelogs/unreleased/sh-drop-webhooks-project-export.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Drop Webhooks from project import/export config
-merge_request: 24121
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
new file mode 100644
index 00000000000..d1d4412eb50
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Fix duplicate project disk path in BackfillLegacyProjectRepositories
+merge_request: 24213
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml b/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml
new file mode 100644
index 00000000000..87405fa0a78
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Bitbucket Server importer error handling
+merge_request: 24343
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-branches-api-timeout.yml b/changelogs/unreleased/sh-fix-branches-api-timeout.yml
deleted file mode 100644
index 8cd29a7269d..00000000000
--- a/changelogs/unreleased/sh-fix-branches-api-timeout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix timeout issues retrieving branches via API
-merge_request: 24034
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-fix-github-import-without-oauth2-config.yml b/changelogs/unreleased/sh-fix-github-import-without-oauth2-config.yml
deleted file mode 100644
index ad548a6ff35..00000000000
--- a/changelogs/unreleased/sh-fix-github-import-without-oauth2-config.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow GitHub imports via token even if OAuth2 provider not configured
-merge_request: 23703
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-55822.yml b/changelogs/unreleased/sh-fix-issue-55822.yml
deleted file mode 100644
index 1267b2ace2f..00000000000
--- a/changelogs/unreleased/sh-fix-issue-55822.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix commit SHA not showing in merge request compare dropdown
-merge_request: 24084
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-9357.yml b/changelogs/unreleased/sh-fix-issue-9357.yml
new file mode 100644
index 00000000000..756cd6047b8
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-9357.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 errors with legacy appearance logos
+merge_request: 24615
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml b/changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml
new file mode 100644
index 00000000000..414c8663049
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 404s with snippet uploads in object storage
+merge_request: 24550
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml b/changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml
new file mode 100644
index 00000000000..8bc1e4b4f8a
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 404s for snippet uploads when relative URL root used
+merge_request: 24588
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-preload-associations-for-group-api.yml b/changelogs/unreleased/sh-preload-associations-for-group-api.yml
new file mode 100644
index 00000000000..24e424b7efb
--- /dev/null
+++ b/changelogs/unreleased/sh-preload-associations-for-group-api.yml
@@ -0,0 +1,5 @@
+---
+title: Eliminate N+1 queries in /api/groups/:id
+merge_request: 24513
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-remove-bitbucket-mirror-constant.yml b/changelogs/unreleased/sh-remove-bitbucket-mirror-constant.yml
new file mode 100644
index 00000000000..8c0b000220f
--- /dev/null
+++ b/changelogs/unreleased/sh-remove-bitbucket-mirror-constant.yml
@@ -0,0 +1,5 @@
+---
+title: Fix import handling errors in Bitbucket Server importer
+merge_request: 24499
+author:
+type: fixed
diff --git a/changelogs/unreleased/shared_with_group_path.yml b/changelogs/unreleased/shared_with_group_path.yml
new file mode 100644
index 00000000000..73ba9a9f30a
--- /dev/null
+++ b/changelogs/unreleased/shared_with_group_path.yml
@@ -0,0 +1,5 @@
+---
+title: Add group full path to project's shared_with_groups
+merge_request: 24052
+author: Mathieu Parent
+type: added
diff --git a/changelogs/unreleased/spec-positional-arguments.yml b/changelogs/unreleased/spec-positional-arguments.yml
deleted file mode 100644
index 9dc114e5595..00000000000
--- a/changelogs/unreleased/spec-positional-arguments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix deprecation: Using positional arguments in integration tests'
-merge_request: 24009
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/specs-positional-arguments.yml b/changelogs/unreleased/specs-positional-arguments.yml
deleted file mode 100644
index 38b831bd72c..00000000000
--- a/changelogs/unreleased/specs-positional-arguments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: convert specs in javascripts/ and support/ to new syntax
-merge_request: 23947
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/support-gitaly-tls.yml b/changelogs/unreleased/support-gitaly-tls.yml
deleted file mode 100644
index 2a15500d6da..00000000000
--- a/changelogs/unreleased/support-gitaly-tls.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support tls communication in gitaly
-merge_request: 22602
-author:
-type: added
diff --git a/changelogs/unreleased/triggermesh-knative-version.yml b/changelogs/unreleased/triggermesh-knative-version.yml
deleted file mode 100644
index 27f400962da..00000000000
--- a/changelogs/unreleased/triggermesh-knative-version.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Knative version bump 0.1.3 -> 0.2.2
-merge_request:
-author: Chris Baumbauer
-type: changed
diff --git a/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml b/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml
new file mode 100644
index 00000000000..1af1fe09f33
--- /dev/null
+++ b/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml
@@ -0,0 +1,5 @@
+---
+title: 'i18n: externalize strings from ''app/views/search'''
+merge_request: 24297
+author: Tao Wang
+type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml
deleted file mode 100644
index 24471b028b1..00000000000
--- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GitLab Runner Helm Chart to 0.1.43
-merge_request: 24083
-author:
-type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml
new file mode 100644
index 00000000000..7d92929221f
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.1.45
+merge_request: 24564
+author:
+type: other
diff --git a/changelogs/unreleased/update-gitlab-styles.yml b/changelogs/unreleased/update-gitlab-styles.yml
new file mode 100644
index 00000000000..379f0ad4486
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-styles.yml
@@ -0,0 +1,5 @@
+---
+title: Update gitlab-styles to 2.5.1
+merge_request: 24336
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/update-sidekiq-cron.yml b/changelogs/unreleased/update-sidekiq-cron.yml
new file mode 100644
index 00000000000..edce32e3753
--- /dev/null
+++ b/changelogs/unreleased/update-sidekiq-cron.yml
@@ -0,0 +1,6 @@
+---
+title: Update sidekiq-cron to 1.0.4 and use fugit to replace rufus-scheduler to parse
+ cron syntax
+merge_request: 24235
+author:
+type: other
diff --git a/changelogs/unreleased/update-smooshpack.yml b/changelogs/unreleased/update-smooshpack.yml
new file mode 100644
index 00000000000..a9222088d53
--- /dev/null
+++ b/changelogs/unreleased/update-smooshpack.yml
@@ -0,0 +1,5 @@
+---
+title: Upgraded Codesandbox smooshpack package
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml b/changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml
new file mode 100644
index 00000000000..32259bfacd4
--- /dev/null
+++ b/changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Update to GitLab SVG icon from Font Awesome in profile for location and work
+merge_request: 24671
+author: Yoginth
+type: changed
diff --git a/changelogs/unreleased/winh-add-list-dropdown-height.yml b/changelogs/unreleased/winh-add-list-dropdown-height.yml
new file mode 100644
index 00000000000..6bcedc15cc9
--- /dev/null
+++ b/changelogs/unreleased/winh-add-list-dropdown-height.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust height of "Add list" dropdown in issue boards
+merge_request: 24227
+author:
+type: fixed
diff --git a/changelogs/unreleased/winh-dropdown-title-padding.yml b/changelogs/unreleased/winh-dropdown-title-padding.yml
deleted file mode 100644
index 9d65175b536..00000000000
--- a/changelogs/unreleased/winh-dropdown-title-padding.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjust padding of .dropdown-title to comply with design specs
-merge_request: 23546
-author:
-type: changed
diff --git a/changelogs/unreleased/winh-merge-request-commit-context.yml b/changelogs/unreleased/winh-merge-request-commit-context.yml
deleted file mode 100644
index 9e12a926af4..00000000000
--- a/changelogs/unreleased/winh-merge-request-commit-context.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display commit ID for discussions made on merge request commits
-merge_request: 23837
-author:
-type: fixed
diff --git a/changelogs/unreleased/winh-princess-mononospace.yml b/changelogs/unreleased/winh-princess-mononospace.yml
deleted file mode 100644
index e2d33de375e..00000000000
--- a/changelogs/unreleased/winh-princess-mononospace.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make commit IDs in merge request discussion header monospace
-merge_request: 23562
-author:
-type: changed
diff --git a/changelogs/unreleased/winh-upgrade-gitlab-ui.yml b/changelogs/unreleased/winh-upgrade-gitlab-ui.yml
deleted file mode 100644
index b312a329f5d..00000000000
--- a/changelogs/unreleased/winh-upgrade-gitlab-ui.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade @gitlab/ui to 1.16.2
-merge_request: 23946
-author:
-type: other
diff --git a/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml b/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml
new file mode 100644
index 00000000000..0ec76f9ce02
--- /dev/null
+++ b/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Added Avatar in the settings sidebar
+merge_request: 24515
+author: Yoginth
+type: changed
diff --git a/changelogs/unreleased/zj-backup-restore-object-pools.yml b/changelogs/unreleased/zj-backup-restore-object-pools.yml
deleted file mode 100644
index 26e1d49aa04..00000000000
--- a/changelogs/unreleased/zj-backup-restore-object-pools.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Restore Object Pools when restoring an object pool
-merge_request: 23682
-author:
-type: added
diff --git a/changelogs/unreleased/zj-feature-gate-set-project-path.yml b/changelogs/unreleased/zj-feature-gate-set-project-path.yml
new file mode 100644
index 00000000000..b426a2f3fe7
--- /dev/null
+++ b/changelogs/unreleased/zj-feature-gate-set-project-path.yml
@@ -0,0 +1,5 @@
+---
+title: Allow setting of feature gates per project
+merge_request: 24184
+author:
+type: added
diff --git a/config.ru b/config.ru
index a5d055334dd..5cd79870d54 100644
--- a/config.ru
+++ b/config.ru
@@ -19,7 +19,7 @@ if defined?(Unicorn)
Unicorn::StreamInput.send(:public, :eof?) # rubocop:disable GitlabSecurity/PublicSend
end
-require ::File.expand_path('../config/environment', __FILE__)
+require ::File.expand_path('../config/environment', __FILE__)
warmup do |app|
client = Rack::MockRequest.new(app)
diff --git a/config/application.rb b/config/application.rb
index 349c7258852..92a3d031c63 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -162,6 +162,9 @@ module Gitlab
config.action_view.sanitized_allowed_protocols = %w(smb)
+ # Can be removed once upgraded to Rails 5.1 or higher
+ config.action_controller.raise_on_unfiltered_parameters = true
+
# Nokogiri is significantly faster and uses less memory than REXML
ActiveSupport::XmlMini.backend = 'Nokogiri'
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 7fe85f0e0d7..6fc33e8971e 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -635,6 +635,10 @@ production: &base
# multipart_chunk_size: 104857600
# # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
# # encryption: 'AES256'
+ # # Turns on AWS Server-Side Encryption with Amazon Customer-Provided Encryption Keys for backups, this is optional
+ # # This should be set to the 256-bit, base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data.
+ # # 'encryption' must also be set in order for this to have any effect.
+ # # encryption_key: '<base64 key>'
# # Specifies Amazon S3 storage class to use for backups, this is optional
# # storage_class: 'STANDARD'
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index db35fa96ea2..1aed41e02ab 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -392,6 +392,7 @@ Settings.backup['archive_permissions'] ||= 0600
Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
Settings.backup['upload']['multipart_chunk_size'] ||= 104857600
Settings.backup['upload']['encryption'] ||= nil
+Settings.backup['upload']['encryption_key'] ||= ENV['GITLAB_BACKUP_ENCRYPTION_KEY']
Settings.backup['upload']['storage_class'] ||= nil
#
diff --git a/config/initializers/8_devise.rb b/config/initializers/8_devise.rb
index 67eabb0b4fc..4683b02f300 100644
--- a/config/initializers/8_devise.rb
+++ b/config/initializers/8_devise.rb
@@ -178,7 +178,7 @@ Devise.setup do |config|
# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user).
- config.default_scope = :user # now have an :email scope as well, so set the default
+ config.default_scope = :user # now have an :email scope as well, so set the default
# Configure sign_out behavior.
# Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope).
diff --git a/config/initializers/ar_mysql_jsonb_support.rb b/config/initializers/ar_mysql_jsonb_support.rb
new file mode 100644
index 00000000000..63a0b05119a
--- /dev/null
+++ b/config/initializers/ar_mysql_jsonb_support.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'active_record/connection_adapters/abstract_mysql_adapter'
+require 'active_record/connection_adapters/mysql/schema_definitions'
+
+# MySQL (5.6) and MariaDB (10.1) are currently supported versions within GitLab,
+# Since they do not support native `json` datatype we force to emulate it as `text`
+
+if Gitlab::Database.mysql?
+ module ActiveRecord
+ module ConnectionAdapters
+ class AbstractMysqlAdapter
+ JSON_DATASIZE = 1.megabyte
+
+ NATIVE_DATABASE_TYPES.merge!(
+ json: { name: "text", limit: JSON_DATASIZE },
+ jsonb: { name: "text", limit: JSON_DATASIZE }
+ )
+ end
+
+ module MySQL
+ module ColumnMethods
+ # We add `jsonb` helper, as `json` is already defined for `MySQL` since Rails 5
+ def jsonb(*args, **options)
+ args.each { |name| column(name, :json, options) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb
index ae5d834a02c..e97c0fcbd6b 100644
--- a/config/initializers/doorkeeper_openid_connect.rb
+++ b/config/initializers/doorkeeper_openid_connect.rb
@@ -31,7 +31,7 @@ Doorkeeper::OpenidConnect.configure do
o.claim(:name) { |user| user.name }
o.claim(:nickname) { |user| user.username }
- o.claim(:email) { |user| user.public_email }
+ o.claim(:email) { |user| user.public_email }
o.claim(:email_verified) { |user| true if user.public_email? }
o.claim(:website) { |user| user.full_website_url if user.website_url? }
o.claim(:profile) { |user| Gitlab::Routing.url_helpers.user_url user }
diff --git a/config/initializers/kaminari_active_record_relation_methods_with_limit.rb b/config/initializers/kaminari_active_record_relation_methods_with_limit.rb
new file mode 100644
index 00000000000..cc20b83b234
--- /dev/null
+++ b/config/initializers/kaminari_active_record_relation_methods_with_limit.rb
@@ -0,0 +1,41 @@
+module Kaminari
+ # Active Record specific page scope methods implementations
+ module ActiveRecordRelationMethodsWithLimit
+ MAX_COUNT_LIMIT = 10_000
+
+ # This is a modified version of
+ # https://github.com/kaminari/kaminari/blob/c5186f5d9b7f23299d115408e62047447fd3189d/kaminari-activerecord/lib/kaminari/activerecord/active_record_relation_methods.rb#L17-L41
+ # that limit the COUNT query to 10,000 to avoid query timeouts.
+ # rubocop: disable Gitlab/ModuleWithInstanceVariables
+ def total_count_with_limit(column_name = :all, _options = nil) #:nodoc:
+ return @total_count if defined?(@total_count) && @total_count
+
+ # There are some cases that total count can be deduced from loaded records
+ if loaded?
+ # Total count has to be 0 if loaded records are 0
+ return @total_count = 0 if (current_page == 1) && @records.empty?
+ # Total count is calculable at the last page
+ return @total_count = (current_page - 1) * limit_value + @records.length if @records.any? && (@records.length < limit_value)
+ end
+
+ # #count overrides the #select which could include generated columns referenced in #order, so skip #order here, where it's irrelevant to the result anyway
+ c = except(:offset, :limit, :order)
+ # Remove includes only if they are irrelevant
+ c = c.except(:includes) unless references_eager_loaded_tables?
+ # .group returns an OrderedHash that responds to #count
+ # The following line was modified from `c = c.count(:all)`
+ c = c.limit(MAX_COUNT_LIMIT + 1).count(column_name)
+ @total_count =
+ if c.is_a?(Hash) || c.is_a?(ActiveSupport::OrderedHash)
+ c.count
+ elsif c.respond_to? :count
+ c.count(column_name)
+ else
+ c
+ end
+ end
+ # rubocop: enable Gitlab/ModuleWithInstanceVariables
+
+ Kaminari::ActiveRecordRelationMethods.prepend(self)
+ end
+end
diff --git a/config/initializers/kubeclient.rb b/config/initializers/kubeclient.rb
deleted file mode 100644
index f8fe1156aaa..00000000000
--- a/config/initializers/kubeclient.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-class Kubeclient::Client
- # Monkey patch to set `max_redirects: 0`, so that kubeclient
- # does not follow redirects and expose internal services.
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/53158
- def create_rest_client(path = nil)
- path ||= @api_endpoint.path
- options = {
- ssl_ca_file: @ssl_options[:ca_file],
- ssl_cert_store: @ssl_options[:cert_store],
- verify_ssl: @ssl_options[:verify_ssl],
- ssl_client_cert: @ssl_options[:client_cert],
- ssl_client_key: @ssl_options[:client_key],
- proxy: @http_proxy_uri,
- user: @auth_options[:username],
- password: @auth_options[:password],
- open_timeout: @timeouts[:open],
- read_timeout: @timeouts[:read],
- max_redirects: 0
- }
- RestClient::Resource.new(@api_endpoint.merge(path).to_s, options)
- end
-end
diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb
index 5adb9f7a4b4..115ee08dbb6 100644
--- a/config/initializers/new_framework_defaults.rb
+++ b/config/initializers/new_framework_defaults.rb
@@ -8,8 +8,6 @@
#
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-Rails.application.config.action_controller.raise_on_unfiltered_parameters = true
-
# Enable per-form CSRF tokens. Previous versions had false.
Rails.application.config.action_controller.per_form_csrf_tokens = false
@@ -22,6 +20,3 @@ ActiveSupport.to_time_preserves_timezone = false
# Require `belongs_to` associations by default. Previous versions had false.
Rails.application.config.active_record.belongs_to_required_by_default = false
-
-# Do not halt callback chains when a callback returns false. Previous versions had true.
-ActiveSupport.halt_callback_chains_on_return_false = true
diff --git a/config/initializers/postgresql_cte.rb b/config/initializers/postgresql_cte.rb
index 38a9cd68d57..56689bc8e74 100644
--- a/config/initializers/postgresql_cte.rb
+++ b/config/initializers/postgresql_cte.rb
@@ -108,7 +108,7 @@ module ActiveRecord
when String
with_value
when Hash
- with_value.map do |name, expression|
+ with_value.map do |name, expression|
case expression
when String
select = Arel::Nodes::SqlLiteral.new "(#{expression})"
diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb
index 70b530415f5..b066f3788ec 100644
--- a/config/initializers/postgresql_opclasses_support.rb
+++ b/config/initializers/postgresql_opclasses_support.rb
@@ -130,9 +130,12 @@ module ActiveRecord
where = inddef.scan(/WHERE (.+)$/).flatten[0]
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
opclasses = Hash[inddef.scan(/\((.+?)\)(?:$| WHERE )/).flatten[0].split(',').map do |column_and_opclass|
- column, opclass = column_and_opclass.split(' ').map(&:strip)
- [column, opclass] if opclass
- end.compact]
+ column, opclass = column_and_opclass.split(' ').map(&:strip)
+ end.reject do |column, opclass|
+ ['desc', 'asc'].include?(opclass&.downcase)
+ end.map do |column, opclass|
+ [column, opclass] if opclass
+ end.compact]
index_attrs = [table_name, index_name, unique, column_names, [], orders, where, nil, using, nil, opclasses]
@@ -151,6 +154,9 @@ module ActiveRecord
def quoted_columns_for_index(column_names, options = {})
column_opclasses = options[:opclasses] || {}
column_names.map {|name| "#{quote_column_name(name)} #{column_opclasses[name]}"}
+
+ quoted_columns = Hash[column_names.map { |name| [name.to_sym, "#{quote_column_name(name)} #{column_opclasses[name]}"] }]
+ add_options_for_index_columns(quoted_columns, options).values
end
end
end
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index 2a6c5148f71..abc91c3ae51 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -19,7 +19,7 @@ def configure_sentry
config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
# Sanitize authentication headers
config.sanitize_http_headers = %w[Authorization Private-Token]
- config.tags = { program: Gitlab::Sentry.program_context }
+ config.tags = { program: Gitlab.process_name }
end
end
end
diff --git a/config/initializers/tracing.rb b/config/initializers/tracing.rb
new file mode 100644
index 00000000000..d5bef8edb43
--- /dev/null
+++ b/config/initializers/tracing.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+if Gitlab::Tracing.enabled?
+ require 'opentracing'
+
+ Rails.application.configure do |config|
+ config.middleware.insert_after Gitlab::Middleware::CorrelationId, ::Gitlab::Tracing::RackMiddleware
+ end
+
+ # Instrument the Sidekiq client
+ Sidekiq.configure_client do |config|
+ config.client_middleware do |chain|
+ chain.add Gitlab::Tracing::Sidekiq::ClientMiddleware
+ end
+ end
+
+ # Instrument Sidekiq server calls when running Sidekiq server
+ if Sidekiq.server?
+ Sidekiq.configure_server do |config|
+ config.server_middleware do |chain|
+ chain.add Gitlab::Tracing::Sidekiq::ServerMiddleware
+ end
+ end
+ end
+
+ # Instrument Rails
+ Gitlab::Tracing::Rails::ActiveRecordSubscriber.instrument
+
+ # In multi-processed clustered architectures (puma, unicorn) don't
+ # start tracing until the worker processes are spawned. This works
+ # around issues when the opentracing implementation spawns threads
+ Gitlab::Cluster::LifecycleEvents.on_worker_start do
+ tracer = Gitlab::Tracing::Factory.create_tracer(Gitlab.process_name, Gitlab::Tracing.connection_string)
+ OpenTracing.global_tracer = tracer if tracer
+ end
+end
diff --git a/config/jsdocs.config.js b/config/jsdocs.config.js
new file mode 100644
index 00000000000..52635b1ce13
--- /dev/null
+++ b/config/jsdocs.config.js
@@ -0,0 +1,14 @@
+module.exports = {
+ source: {
+ include: ['app/assets/javascripts/'],
+ },
+ opts: {
+ template: 'node_modules/docdash',
+ destination: 'jsdoc/',
+ recurse: true,
+ },
+ docdash: {
+ search: true,
+ static: true,
+ },
+};
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 03c95b61e51..21793e7756a 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -2,6 +2,8 @@ resources :projects, only: [:index, :new, :create]
draw :git_http
+get '/projects/:id' => 'projects#resolve'
+
constraints(::Constraints::ProjectUrlConstrainer.new) do
# If the route has a wildcard segment, the segment has a regex constraint,
# the segment is potentially followed by _another_ wildcard segment, and
@@ -247,6 +249,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
namespace :serverless do
+ get '/functions/:environment_id/:id', to: 'functions#show'
resources :functions, only: [:index]
end
@@ -255,8 +258,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
- post :cancel_all
-
resources :artifacts, only: [] do
collection do
get :latest_succeeded,
@@ -361,6 +362,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
collection do
post :bulk_update
+ post :import_csv
end
end
@@ -441,10 +443,16 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ resources :error_tracking, only: [:index], controller: :error_tracking
+
# Since both wiki and repository routing contains wildcard characters
# its preferable to keep it below all other project routes
draw :wiki
draw :repository
+
+ namespace :settings do
+ resource :operations, only: [:show, :update]
+ end
end
resources(:projects,
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index 96975759709..f5201b9ddbb 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -62,7 +62,7 @@ scope format: false do
resources :protected_tags, only: [:index, :show, :create, :update, :destroy]
end
- scope constraints: { id: /.+/ } do
+ scope constraints: { id: /.+/ } do
scope controller: :blob do
get '/new/*id', action: :new, as: :new_blob
post '/create/*id', action: :create, as: :create_blob
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 3ee32678f34..5f87d04c697 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -47,7 +47,6 @@
- [project_service, 1]
- [delete_user, 1]
- [todos_destroyer, 1]
- - [delete_container_repository, 1]
- [delete_merged_branches, 1]
- [authorized_projects, 1]
- [expire_build_instance_artifacts, 1]
@@ -81,7 +80,9 @@
- [delete_diff_files, 1]
- [detect_repository_languages, 1]
- [auto_devops, 2]
+ - [container_repository, 1]
- [object_pool, 1]
- [repository_cleanup, 1]
- [delete_stored_files, 1]
- [remote_mirror_notification, 2]
+ - [import_issues_csv, 2]
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index abb36098629..c20c8b77e6a 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -2,6 +2,9 @@
require 'json'
+URL_LIMIT_SUBJECT = "https://chris.beams.io/posts/git-commit/#limit-50"
+URL_GIT_COMMIT = "https://chris.beams.io/posts/git-commit/"
+
# rubocop: disable Style/SignalException
# rubocop: disable Metrics/CyclomaticComplexity
# rubocop: disable Metrics/PerceivedComplexity
@@ -101,10 +104,7 @@ def lint_commits(commits)
elsif subject.length > 50
warn_commit(
commit,
- "This commit's subject line could be improved. " \
- 'Commit subjects are ideally no longer than roughly 50 characters, ' \
- 'though we allow up to 72 characters in the subject. ' \
- 'If possible, try to reduce the length of the subject to roughly 50 characters.'
+ "This commit's subject line is acceptable, but please try to [reduce it to 50 characters](#{URL_LIMIT_SUBJECT})."
)
end
@@ -178,7 +178,7 @@ def lint_commits(commits)
failures = true
end
- if commit.message.match?(%r(([\w\-\/]+)?(#|!|&|%)\d+))
+ if commit.message.match?(%r(([\w\-\/]+)?(#|!|&|%)\d+\b))
fail_commit(
commit,
'Use full URLs instead of short references ' \
@@ -196,7 +196,7 @@ def lint_commits(commits)
One or more commit messages do not meet our Git commit message standards.
For more information on how to write a good commit message, take a look at
- [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/).
+ [How to Write a Git Commit Message](#{URL_GIT_COMMIT}).
Here is an example of a good commit message:
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index 52af837c261..188331cc87c 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -32,7 +32,7 @@ to be reviewed.
| Tech writer | Stage(s) |
| ------------ | ------------------------------------------------------------ |
| `@marcia` | ~Create ~Release + ~"development guidelines" |
-| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitoring ~Package ~Secure |
+| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitor ~Package ~Secure |
| `@eread` | ~Manage ~Configure ~Geo ~Verify |
| `@mikelewis` | ~Plan |
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index aa8686ac7d8..9a5f7cf8175 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -1,94 +1,136 @@
require './spec/support/sidekiq'
+# rubocop:disable Rails/Output
+
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
- project_urls = [
- 'https://gitlab.com/gitlab-org/gitlab-test.git',
- 'https://gitlab.com/gitlab-org/gitlab-shell.git',
- 'https://gitlab.com/gnuwget/wget2.git',
- 'https://gitlab.com/Commit451/LabCoat.git',
- 'https://github.com/jashkenas/underscore.git',
- 'https://github.com/flightjs/flight.git',
- 'https://github.com/twitter/typeahead.js.git',
- 'https://github.com/h5bp/html5-boilerplate.git',
- 'https://github.com/google/material-design-lite.git',
- 'https://github.com/jlevy/the-art-of-command-line.git',
- 'https://github.com/FreeCodeCamp/freecodecamp.git',
- 'https://github.com/google/deepdream.git',
- 'https://github.com/jtleek/datasharing.git',
- 'https://github.com/WebAssembly/design.git',
- 'https://github.com/airbnb/javascript.git',
- 'https://github.com/tessalt/echo-chamber-js.git',
- 'https://github.com/atom/atom.git',
- 'https://github.com/mattermost/mattermost-server.git',
- 'https://github.com/purifycss/purifycss.git',
- 'https://github.com/facebook/nuclide.git',
- 'https://github.com/wbkd/awesome-d3.git',
- 'https://github.com/kilimchoi/engineering-blogs.git',
- 'https://github.com/gilbarbara/logos.git',
- 'https://github.com/reduxjs/redux.git',
- 'https://github.com/awslabs/s2n.git',
- 'https://github.com/arkency/reactjs_koans.git',
- 'https://github.com/twbs/bootstrap.git',
- 'https://github.com/chjj/ttystudio.git',
- 'https://github.com/MostlyAdequate/mostly-adequate-guide.git',
- 'https://github.com/octocat/Spoon-Knife.git',
- 'https://github.com/opencontainers/runc.git',
- 'https://github.com/googlesamples/android-topeka.git'
- ]
-
- # You can specify how many projects you need during seed execution
- size = ENV['SIZE'].present? ? ENV['SIZE'].to_i : 8
-
- project_urls.first(size).each_with_index do |url, i|
- group_path, project_path = url.split('/')[-2..-1]
-
- group = Group.find_by(path: group_path)
-
- unless group
- group = Group.new(
- name: group_path.titleize,
- path: group_path
- )
- group.description = FFaker::Lorem.sentence
- group.save
-
- group.add_owner(User.first)
- end
+ Gitlab::Seeder.without_gitaly_timeout do
+ project_urls = %w[
+ https://gitlab.com/gitlab-org/gitlab-test.git
+ https://gitlab.com/gitlab-org/gitlab-shell.git
+ https://gitlab.com/gnuwget/wget2.git
+ https://gitlab.com/Commit451/LabCoat.git
+ https://github.com/jashkenas/underscore.git
+ https://github.com/flightjs/flight.git
+ https://github.com/twitter/typeahead.js.git
+ https://github.com/h5bp/html5-boilerplate.git
+ https://github.com/google/material-design-lite.git
+ https://github.com/jlevy/the-art-of-command-line.git
+ https://github.com/FreeCodeCamp/freecodecamp.git
+ https://github.com/google/deepdream.git
+ https://github.com/jtleek/datasharing.git
+ https://github.com/WebAssembly/design.git
+ https://github.com/airbnb/javascript.git
+ https://github.com/tessalt/echo-chamber-js.git
+ https://github.com/atom/atom.git
+ https://github.com/mattermost/mattermost-server.git
+ https://github.com/purifycss/purifycss.git
+ https://github.com/facebook/nuclide.git
+ https://github.com/wbkd/awesome-d3.git
+ https://github.com/kilimchoi/engineering-blogs.git
+ https://github.com/gilbarbara/logos.git
+ https://github.com/reduxjs/redux.git
+ https://github.com/awslabs/s2n.git
+ https://github.com/arkency/reactjs_koans.git
+ https://github.com/twbs/bootstrap.git
+ https://github.com/chjj/ttystudio.git
+ https://github.com/MostlyAdequate/mostly-adequate-guide.git
+ https://github.com/octocat/Spoon-Knife.git
+ https://github.com/opencontainers/runc.git
+ https://github.com/googlesamples/android-topeka.git
+ ]
- project_path.gsub!(".git", "")
+ large_project_urls = %w[
+ https://github.com/torvalds/linux.git
+ https://gitlab.gnome.org/GNOME/gimp.git
+ https://gitlab.gnome.org/GNOME/gnome-mud.git
+ https://gitlab.com/fdroid/fdroidclient.git
+ https://gitlab.com/inkscape/inkscape.git
+ https://github.com/gnachman/iTerm2.git
+ ]
- params = {
- import_url: url,
- namespace_id: group.id,
- name: project_path.titleize,
- description: FFaker::Lorem.sentence,
- visibility_level: Gitlab::VisibilityLevel.values.sample,
- skip_disk_validation: true
- }
+ def create_project(url, force_latest_storage: false)
+ group_path, project_path = url.split('/')[-2..-1]
- if i % 2 == 0
- params[:storage_version] = Project::LATEST_STORAGE_VERSION
- end
+ group = Group.find_by(path: group_path)
+
+ unless group
+ group = Group.new(
+ name: group_path.titleize,
+ path: group_path
+ )
+ group.description = FFaker::Lorem.sentence
+ group.save
+
+ group.add_owner(User.first)
+ end
+
+ project_path.gsub!(".git", "")
+
+ params = {
+ import_url: url,
+ namespace_id: group.id,
+ name: project_path.titleize,
+ description: FFaker::Lorem.sentence,
+ visibility_level: Gitlab::VisibilityLevel.values.sample,
+ skip_disk_validation: true
+ }
+
+ if force_latest_storage
+ params[:storage_version] = Project::LATEST_STORAGE_VERSION
+ end
+
+ project = nil
- project = nil
+ Sidekiq::Worker.skipping_transaction_check do
+ project = Projects::CreateService.new(User.first, params).execute
- Sidekiq::Worker.skipping_transaction_check do
- project = Projects::CreateService.new(User.first, params).execute
+ # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
+ # hook won't run until after the fixture is loaded. That is too late
+ # since the Sidekiq::Testing block has already exited. Force clearing
+ # the `after_commit` queue to ensure the job is run now.
+ project.send(:_run_after_commit_queue)
+ project.import_state.send(:_run_after_commit_queue)
+ end
- # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
- # hook won't run until after the fixture is loaded. That is too late
- # since the Sidekiq::Testing block has already exited. Force clearing
- # the `after_commit` queue to ensure the job is run now.
- project.send(:_run_after_commit_queue)
- project.import_state.send(:_run_after_commit_queue)
+ if project.valid? && project.valid_repo?
+ print '.'
+ else
+ puts project.errors.full_messages
+ print 'F'
+ end
end
- if project.valid? && project.valid_repo?
- print '.'
- else
- puts project.errors.full_messages
- print 'F'
+ # You can specify how many projects you need during seed execution
+ size = ENV['SIZE'].present? ? ENV['SIZE'].to_i : 8
+
+ project_urls.first(size).each_with_index do |url, i|
+ create_project(url, force_latest_storage: i.even?)
+ end
+
+ if ENV['LARGE_PROJECTS'].present?
+ large_project_urls.each(&method(:create_project))
+
+ if ENV['FORK'].present?
+ puts "\nGenerating forks"
+
+ project_name = ENV['FORK'] == 'true' ? 'torvalds/linux' : ENV['FORK']
+
+ project = Project.find_by_full_path(project_name)
+
+ User.offset(1).first(5).each do |user|
+ new_project = Projects::ForkService.new(project, user).execute
+
+ if new_project.valid? && (new_project.valid_repo? || new_project.import_state.scheduled?)
+ print '.'
+ else
+ new_project.errors.full_messages.each do |error|
+ puts "#{new_project.full_path}: #{error}"
+ end
+ print 'F'
+ end
+ end
+ end
end
end
end
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 8bdc7c6556c..2051bcff8f0 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -27,6 +27,9 @@ Gitlab::Seeder.quiet do
Sidekiq::Worker.skipping_transaction_check do
MergeRequests::CreateService.new(project, developer, params).execute
+ rescue Repository::AmbiguousRefError
+ # Ignore pipelines creation errors for now, we can doing that after
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/55966. will be resolved.
end
print '.'
end
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index bdc0a2db7db..db043e39d2c 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -102,14 +102,15 @@ class Gitlab::Seeder::Pipelines
[]
end
-
def create_pipeline!(project, ref, commit)
project.ci_pipelines.create!(sha: commit.id, ref: ref, source: :push)
end
def build_create!(pipeline, opts = {})
attributes = job_attributes(pipeline, opts)
- .merge(commands: '$ build command')
+
+ attributes[:options] ||= {}
+ attributes[:options][:script] = 'build command'
Ci::Build.create!(attributes).tap do |build|
# We need to set build trace and artifacts after saving a build
diff --git a/db/migrate/20170927095921_add_ci_builds_index_for_jobscontroller.rb b/db/migrate/20170927095921_add_ci_builds_index_for_jobscontroller.rb
index 85aa78006db..3ee9c959fca 100644
--- a/db/migrate/20170927095921_add_ci_builds_index_for_jobscontroller.rb
+++ b/db/migrate/20170927095921_add_ci_builds_index_for_jobscontroller.rb
@@ -28,7 +28,7 @@ class AddCiBuildsIndexForJobscontroller < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
def up
- add_concurrent_index :ci_builds, [:project_id, :id] unless index_exists? :ci_builds, [:project_id, :id]
+ add_concurrent_index :ci_builds, [:project_id, :id] unless index_exists? :ci_builds, [:project_id, :id]
remove_concurrent_index :ci_builds, :project_id if index_exists? :ci_builds, :project_id
end
diff --git a/db/migrate/20181119132520_add_indexes_to_ci_builds_and_pipelines.rb b/db/migrate/20181119132520_add_indexes_to_ci_builds_and_pipelines.rb
new file mode 100644
index 00000000000..cb01fa113eb
--- /dev/null
+++ b/db/migrate/20181119132520_add_indexes_to_ci_builds_and_pipelines.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+class AddIndexesToCiBuildsAndPipelines < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ indexes.each do |index|
+ add_concurrent_index(*index)
+ end
+ end
+
+ def down
+ indexes.each do |index|
+ remove_concurrent_index(*index)
+ end
+ end
+
+ private
+
+ def indexes
+ [
+ [
+ :ci_pipelines,
+ [:project_id, :ref, :id],
+ {
+ order: { id: :desc },
+ name: 'index_ci_pipelines_on_project_idandrefandiddesc'
+ }
+ ],
+ [
+ :ci_builds,
+ [:commit_id, :artifacts_expire_at, :id],
+ {
+ where: "type::text = 'Ci::Build'::text AND (retried = false OR retried IS NULL) AND (name::text = ANY (ARRAY['sast'::character varying, 'dependency_scanning'::character varying, 'sast:container'::character varying, 'container_scanning'::character varying, 'dast'::character varying]::text[]))",
+ name: 'index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial'
+ }
+ ]
+ ]
+ end
+end
diff --git a/db/migrate/20181212171634_create_error_tracking_settings.rb b/db/migrate/20181212171634_create_error_tracking_settings.rb
new file mode 100644
index 00000000000..18c38bd2c47
--- /dev/null
+++ b/db/migrate/20181212171634_create_error_tracking_settings.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CreateErrorTrackingSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :project_error_tracking_settings, id: :int, primary_key: :project_id, default: nil do |t|
+ t.boolean :enabled, null: false, default: true
+ t.string :api_url, null: false
+ t.string :encrypted_token
+ t.string :encrypted_token_iv
+ t.foreign_key :projects, column: :project_id, on_delete: :cascade
+ end
+ end
+end
diff --git a/db/migrate/20181219145521_add_options_to_build_metadata.rb b/db/migrate/20181219145521_add_options_to_build_metadata.rb
new file mode 100644
index 00000000000..dc9569babc2
--- /dev/null
+++ b/db/migrate/20181219145521_add_options_to_build_metadata.rb
@@ -0,0 +1,15 @@
+# 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 AddOptionsToBuildMetadata < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_builds_metadata, :config_options, :jsonb
+ add_column :ci_builds_metadata, :config_variables, :jsonb
+ end
+end
diff --git a/db/migrate/20181228175414_create_releases_link_table.rb b/db/migrate/20181228175414_create_releases_link_table.rb
new file mode 100644
index 00000000000..03558202137
--- /dev/null
+++ b/db/migrate/20181228175414_create_releases_link_table.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class CreateReleasesLinkTable < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :release_links, id: :bigserial do |t|
+ t.references :release, null: false, index: false, foreign_key: { on_delete: :cascade }
+ t.string :url, null: false
+ t.string :name, null: false
+ t.timestamps_with_timezone null: false
+
+ t.index [:release_id, :url], unique: true
+ t.index [:release_id, :name], unique: true
+ end
+ end
+end
diff --git a/db/migrate/20190103140724_make_legacy_false_default.rb b/db/migrate/20190103140724_make_legacy_false_default.rb
new file mode 100644
index 00000000000..154035f76cd
--- /dev/null
+++ b/db/migrate/20190103140724_make_legacy_false_default.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class MakeLegacyFalseDefault < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ change_column_default :cluster_providers_gcp, :legacy_abac, from: true, to: false
+ end
+end
diff --git a/db/migrate/20190104182041_cleanup_legacy_artifact_migration.rb b/db/migrate/20190104182041_cleanup_legacy_artifact_migration.rb
new file mode 100644
index 00000000000..11659846a06
--- /dev/null
+++ b/db/migrate/20190104182041_cleanup_legacy_artifact_migration.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class CleanupLegacyArtifactMigration < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class Build < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'ci_builds'
+ self.inheritance_column = :_type_disabled
+
+ scope :with_legacy_artifacts, -> { where("artifacts_file <> ''") }
+ end
+
+ def up
+ Gitlab::BackgroundMigration.steal('MigrateLegacyArtifacts')
+
+ CleanupLegacyArtifactMigration::Build
+ .with_legacy_artifacts
+ .each_batch(of: 100) do |batch|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ Gitlab::BackgroundMigration::MigrateLegacyArtifacts.new.perform(*range)
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20190108192941_remove_partial_index_from_ci_builds_artifacts_file.rb b/db/migrate/20190108192941_remove_partial_index_from_ci_builds_artifacts_file.rb
new file mode 100644
index 00000000000..073faf721ae
--- /dev/null
+++ b/db/migrate/20190108192941_remove_partial_index_from_ci_builds_artifacts_file.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemovePartialIndexFromCiBuildsArtifactsFile < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'partial_index_ci_builds_on_id_with_legacy_artifacts'.freeze
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:ci_builds, INDEX_NAME)
+ end
+
+ def down
+ add_concurrent_index(:ci_builds, :id, where: "artifacts_file <> ''", name: INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20190114172110_add_domain_to_cluster.rb b/db/migrate/20190114172110_add_domain_to_cluster.rb
new file mode 100644
index 00000000000..58d7664b8c0
--- /dev/null
+++ b/db/migrate/20190114172110_add_domain_to_cluster.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddDomainToCluster < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :clusters, :domain, :string
+ end
+end
diff --git a/db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb b/db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb
new file mode 100644
index 00000000000..d8f979a1848
--- /dev/null
+++ b/db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddErrorNotificationSentToRemoteMirrors < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :remote_mirrors, :error_notification_sent, :boolean
+ end
+end
diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
index 50e1c8449ba..32579256299 100644
--- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb
+++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
@@ -113,7 +113,7 @@ class RenameReservedProjectNames < ActiveRecord::Migration[4.2]
# Because project path update is quite complex operation we can't safely
# copy-paste all code from GitLab. As exception we use Rails code here
if rename_project_row(project, path)
- Projects::AfterRenameService.new(project).execute
+ after_rename_service(project, path_was, namespace_path).execute
end
rescue Exception => e # rubocop: disable Lint/RescueException
Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
@@ -126,4 +126,12 @@ class RenameReservedProjectNames < ActiveRecord::Migration[4.2]
project.update(path: path) &&
defined?(Projects::AfterRenameService)
end
+
+ def after_rename_service(project, path_was, namespace_path)
+ AfterRenameService.new(
+ project,
+ path_before: path_was,
+ full_path_before: "#{namespace_path}/#{path_was}"
+ ).execute
+ end
end
diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
index bef669b459d..85c97e3687e 100644
--- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
+++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
@@ -55,7 +55,7 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration[4.2]
# Because project path update is quite complex operation we can't safely
# copy-paste all code from GitLab. As exception we use Rails code here
if rename_project_row(project, path)
- Projects::AfterRenameService.new(project).execute
+ after_rename_service(project, path_was, namespace_path).execute
end
rescue Exception => e # rubocop: disable Lint/RescueException
Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
@@ -68,4 +68,12 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration[4.2]
project.update(path: path) &&
defined?(Projects::AfterRenameService)
end
+
+ def after_rename_service(project, path_was, namespace_path)
+ AfterRenameService.new(
+ project,
+ path_before: path_was,
+ full_path_before: "#{namespace_path}/#{path_was}"
+ ).execute
+ end
end
diff --git a/db/post_migrate/20170317162059_update_upload_paths_to_system.rb b/db/post_migrate/20170317162059_update_upload_paths_to_system.rb
index a5a6f043e10..99cdca465e2 100644
--- a/db/post_migrate/20170317162059_update_upload_paths_to_system.rb
+++ b/db/post_migrate/20170317162059_update_upload_paths_to_system.rb
@@ -48,7 +48,7 @@ class UpdateUploadPathsToSystem < ActiveRecord::Migration[4.2]
end
def new_upload_dir
- File.join(base_directory, "-", "system")
+ File.join(base_directory, "-", "system")
end
def arel_table
diff --git a/db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb b/db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb
deleted file mode 100644
index 42f96750789..00000000000
--- a/db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-class BackfillProjectRepositoriesForLegacyStorageProjects < ActiveRecord::Migration[5.0]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
- BATCH_SIZE = 1_000
- DELAY_INTERVAL = 5.minutes
- MIGRATION = 'BackfillLegacyProjectRepositories'
-
- disable_ddl_transaction!
-
- class Project < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'projects'
- end
-
- def up
- queue_background_migration_jobs_by_range_at_intervals(Project, MIGRATION, DELAY_INTERVAL)
- end
-
- def down
- # no-op: since there could have been existing rows before the migration do not remove anything
- end
-end
diff --git a/db/post_migrate/20190115054215_migrate_delete_container_repository_worker.rb b/db/post_migrate/20190115054215_migrate_delete_container_repository_worker.rb
new file mode 100644
index 00000000000..4fcee326b7e
--- /dev/null
+++ b/db/post_migrate/20190115054215_migrate_delete_container_repository_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class MigrateDeleteContainerRepositoryWorker < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ sidekiq_queue_migrate('delete_container_repository', to: 'container_repository:delete_container_repository')
+ end
+
+ def down
+ sidekiq_queue_migrate('container_repository:delete_container_repository', to: 'delete_container_repository')
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0af185409a9..cd502d06bf4 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: 20181219145520) do
+ActiveRecord::Schema.define(version: 20190115054216) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -349,11 +349,11 @@ ActiveRecord::Schema.define(version: 20181219145520) do
t.string "token_encrypted"
t.index ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree
t.index ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id", using: :btree
+ t.index ["commit_id", "artifacts_expire_at", "id"], name: "index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial", where: "(((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('sast:container'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])))", using: :btree
t.index ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
t.index ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
t.index ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
t.index ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
- t.index ["id"], name: "partial_index_ci_builds_on_id_with_legacy_artifacts", where: "(artifacts_file <> ''::text)", using: :btree
t.index ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree
t.index ["project_id", "status"], name: "index_ci_builds_project_id_and_status_for_live_jobs_partial2", where: "(((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])))", using: :btree
t.index ["protected"], name: "index_ci_builds_on_protected", using: :btree
@@ -373,6 +373,8 @@ ActiveRecord::Schema.define(version: 20181219145520) do
t.integer "project_id", null: false
t.integer "timeout"
t.integer "timeout_source", default: 1, null: false
+ t.jsonb "config_options"
+ t.jsonb "config_variables"
t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true, using: :btree
t.index ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree
end
@@ -483,6 +485,7 @@ ActiveRecord::Schema.define(version: 20181219145520) do
t.index ["merge_request_id"], name: "index_ci_pipelines_on_merge_request_id", where: "(merge_request_id IS NOT NULL)", using: :btree
t.index ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree
t.index ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, where: "(iid IS NOT NULL)", using: :btree
+ t.index ["project_id", "ref", "id"], name: "index_ci_pipelines_on_project_idandrefandiddesc", order: { id: :desc }, using: :btree
t.index ["project_id", "ref", "status", "id"], name: "index_ci_pipelines_on_project_id_and_ref_and_status_and_id", using: :btree
t.index ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree
t.index ["project_id", "source"], name: "index_ci_pipelines_on_project_id_and_source", using: :btree
@@ -630,7 +633,7 @@ ActiveRecord::Schema.define(version: 20181219145520) do
t.string "endpoint"
t.text "encrypted_access_token"
t.string "encrypted_access_token_iv"
- t.boolean "legacy_abac", default: true, null: false
+ t.boolean "legacy_abac", default: false, null: false
t.index ["cluster_id"], name: "index_cluster_providers_gcp_on_cluster_id", unique: true, using: :btree
end
@@ -644,6 +647,7 @@ ActiveRecord::Schema.define(version: 20181219145520) do
t.string "name", null: false
t.string "environment_scope", default: "*", null: false
t.integer "cluster_type", limit: 2, default: 3, null: false
+ t.string "domain"
t.index ["enabled"], name: "index_clusters_on_enabled", using: :btree
t.index ["user_id"], name: "index_clusters_on_user_id", using: :btree
end
@@ -1569,6 +1573,13 @@ ActiveRecord::Schema.define(version: 20181219145520) do
t.index ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree
end
+ create_table "project_error_tracking_settings", primary_key: "project_id", id: :integer, force: :cascade do |t|
+ t.boolean "enabled", default: true, null: false
+ t.string "api_url", null: false
+ t.string "encrypted_token"
+ t.string "encrypted_token_iv"
+ end
+
create_table "project_features", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "merge_requests_access_level"
@@ -1796,6 +1807,16 @@ ActiveRecord::Schema.define(version: 20181219145520) do
t.index ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id", using: :btree
end
+ create_table "release_links", id: :bigserial, force: :cascade do |t|
+ t.integer "release_id", null: false
+ t.string "url", null: false
+ t.string "name", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.index ["release_id", "name"], name: "index_release_links_on_release_id_and_name", unique: true, using: :btree
+ t.index ["release_id", "url"], name: "index_release_links_on_release_id_and_url", unique: true, using: :btree
+ end
+
create_table "releases", force: :cascade do |t|
t.string "tag"
t.text "description"
@@ -1828,6 +1849,7 @@ ActiveRecord::Schema.define(version: 20181219145520) do
t.string "encrypted_credentials_salt"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.boolean "error_notification_sent"
t.index ["last_successful_update_at"], name: "index_remote_mirrors_on_last_successful_update_at", using: :btree
t.index ["project_id"], name: "index_remote_mirrors_on_project_id", using: :btree
end
@@ -2420,6 +2442,7 @@ ActiveRecord::Schema.define(version: 20181219145520) do
add_foreign_key "project_custom_attributes", "projects", on_delete: :cascade
add_foreign_key "project_deploy_tokens", "deploy_tokens", on_delete: :cascade
add_foreign_key "project_deploy_tokens", "projects", on_delete: :cascade
+ add_foreign_key "project_error_tracking_settings", "projects", on_delete: :cascade
add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
@@ -2437,6 +2460,7 @@ ActiveRecord::Schema.define(version: 20181219145520) do
add_foreign_key "protected_tag_create_access_levels", "users"
add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade
add_foreign_key "push_event_payloads", "events", name: "fk_36c74129da", on_delete: :cascade
+ add_foreign_key "release_links", "releases", on_delete: :cascade
add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade
add_foreign_key "releases", "users", column: "author_id", name: "fk_8e4456f90f", on_delete: :nullify
add_foreign_key "remote_mirrors", "projects", on_delete: :cascade
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
index 621d4f77d5e..0d03b481881 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
@@ -255,7 +255,7 @@ After configuring LDAP, basic authentication will be available. Users can then l
Users that are removed from the LDAP base group (e.g `OU=GitLab INT,DC=GitLab,DC=org`) will be **blocked** in GitLab. [More information](../ldap.md#security) on LDAP security.
-If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `jon.doe@example.com` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
+If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `` jon.doe@example.com `` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
## LDAP extended features on GitLab EE
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index 54ded25291a..0ac73c55580 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -335,7 +335,7 @@ group, you can use the following syntax:
```
Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at
-https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx. Support for
+<https://docs.microsoft.com/en-us/windows/desktop/ADSI/search-filter-syntax>. Support for
nested members in the user filter should not be confused with
[group sync nested groups support (EE only)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#supported-ldap-group-types-attributes).
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 5b7a61ef8ff..db0b3e1270c 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -11,7 +11,7 @@ With the Container Registry integrated into GitLab, every project can have its
own space to store its Docker images.
You can read more about the Container Registry at
-https://docs.docker.com/registry/introduction/.
+<https://docs.docker.com/registry/introduction/>.
## Enable the Container Registry
@@ -378,7 +378,7 @@ Read more about the individual driver's config options in the
> **Warning** GitLab will not backup Docker images that are not stored on the
filesystem. Remember to enable backups with your object storage provider if
desired.
->
+>
> **Important** Enabling storage driver other than `filesystem` would mean
that your Docker client needs to be able to access the storage backend directly.
So you must use an address that resolves and is accessible outside GitLab server.
@@ -606,15 +606,15 @@ information in [issue 18239][ce-18239].
## Troubleshooting
-When using AWS S3 with the GitLab registry, an error may occur when pushing
+When using AWS S3 with the GitLab registry, an error may occur when pushing
large images. Look in the Registry log for the following error:
```
-level=error msg="response completed with error" err.code=unknown err.detail="unexpected EOF" err.message="unknown error"
+level=error msg="response completed with error" err.code=unknown err.detail="unexpected EOF" err.message="unknown error"
```
-To resolve the error specify a `chunksize` value in the Registry configuration.
-Start with a value between `25000000` (25MB) and `50000000` (50MB).
+To resolve the error specify a `chunksize` value in the Registry configuration.
+Start with a value between `25000000` (25MB) and `50000000` (50MB).
**For Omnibus installations**
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index cf37eaa0b61..abef7a6cd33 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -49,6 +49,25 @@ 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.
+### Network architecture
+
+- gitlab-rails shards repositories into "repository storages"
+- gitlab-rails/config/gitlab.yml contains a map from storage names to
+ (Gitaly address, Gitaly token) pairs
+- the `storage name` -\> `(Gitaly address, Gitaly token)` map in
+ gitlab.yml is the single source of truth for the Gitaly network
+ topology
+- a (Gitaly address, Gitaly token) corresponds to a Gitaly server
+- a Gitaly server hosts one or more storages
+- Gitaly addresses must be specified in such a way that they resolve
+ correctly for ALL Gitaly clients
+- Gitaly clients are: unicorn, sidekiq, gitlab-workhorse,
+ gitlab-shell, and Gitaly itself
+- special case: a Gitaly server must be able to make RPC calls **to
+ itself** via its own (Gitaly address, Gitaly token) pair as
+ specified in gitlab-rails/config/gitlab.yml
+- Gitaly servers must not be exposed to the public internet
+
Gitaly network traffic is unencrypted so you should use a firewall to
restrict access to your Gitaly server.
@@ -217,6 +236,8 @@ repository from your GitLab server over HTTP.
## TLS support
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22602) in GitLab 11.7.
+
Gitaly supports TLS credentials for GRPC authentication. To be able to communicate
with a gitaly instance that listens for secure connections you will need to use `tls://` url
scheme in the `gitaly_address` of the corresponding storage entry in the gitlab configuration.
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index 833c1f367dd..987a0b9f350 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -545,10 +545,10 @@ discussed in [Redis setup overview](#redis-setup-overview) and
Here is a list and description of each **machine** and the assigned **IP**:
-* `10.0.0.1`: Redis Master + Sentinel 1
-* `10.0.0.2`: Redis Slave 1 + Sentinel 2
-* `10.0.0.3`: Redis Slave 2 + Sentinel 3
-* `10.0.0.4`: GitLab application
+- `10.0.0.1`: Redis Master + Sentinel 1
+- `10.0.0.2`: Redis Slave 1 + Sentinel 2
+- `10.0.0.3`: Redis Slave 2 + Sentinel 3
+- `10.0.0.4`: GitLab application
Please note that after the initial configuration, if a failover is initiated
by the Sentinel nodes, the Redis nodes will be reconfigured and the **Master**
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index 2101d36d2b6..14e2784c419 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -214,10 +214,10 @@ For this example, **Sentinel 1** will be configured in the same machine as the
Here is a list and description of each **machine** and the assigned **IP**:
-* `10.0.0.1`: Redis Master + Sentinel 1
-* `10.0.0.2`: Redis Slave 1 + Sentinel 2
-* `10.0.0.3`: Redis Slave 2 + Sentinel 3
-* `10.0.0.4`: GitLab application
+- `10.0.0.1`: Redis Master + Sentinel 1
+- `10.0.0.2`: Redis Slave 1 + Sentinel 2
+- `10.0.0.3`: Redis Slave 2 + Sentinel 3
+- `10.0.0.4`: GitLab application
Please note that after the initial configuration, if a failover is initiated
by the Sentinel nodes, the Redis nodes will be reconfigured and the **Master**
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index bb621b788f1..058346df56d 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -17,19 +17,18 @@ The housekeeping function will run a `repack` or `gc` depending on the
For example in the following scenario a `git repack -d` will be executed:
-+ Project: pushes since gc counter (`pushes_since_gc`) = `10`
-+ Git GC period = `200`
-+ Full repack period = `50`
+- Project: pushes since gc counter (`pushes_since_gc`) = `10`
+- Git GC period = `200`
+- Full repack period = `50`
When the `pushes_since_gc` value is 50 a `repack -A -d --pack-kept-objects` will run, similarly when
the `pushes_since_gc` value is 200 a `git gc` will be run.
-+ `git gc` ([man page][man-gc]) runs a number of housekeeping tasks,
-such as compressing filerevisions (to reduce disk space and increase performance)
-and removing unreachable objects which may have been created from prior invocations of
-`git add`.
-
-+ `git repack` ([man page][man-repack]) re-organize existing packs into a single, more efficient pack.
+- `git gc` ([man page][man-gc]) runs a number of housekeeping tasks,
+ such as compressing filerevisions (to reduce disk space and increase performance)
+ and removing unreachable objects which may have been created from prior invocations of
+ `git add`.
+- `git repack` ([man page][man-repack]) re-organize existing packs into a single, more efficient pack.
You can find this option under your **[Project] > Edit Project**.
@@ -39,4 +38,4 @@ You can find this option under your **[Project] > Edit Project**.
[ce-2371]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2371 "Housekeeping merge request"
[man-gc]: https://www.kernel.org/pub/software/scm/git/docs/git-gc.html "git gc man page"
-[man-repack]: https://www.kernel.org/pub/software/scm/git/docs/git-repack.html \ No newline at end of file
+[man-repack]: https://www.kernel.org/pub/software/scm/git/docs/git-repack.html
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 27a3710632d..05873e01a08 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -13,43 +13,45 @@ GitLab has several features based on receiving incoming emails:
## Requirements
-Handling incoming emails requires an [IMAP]-enabled email account. GitLab
-requires one of the following three strategies:
+Handling incoming emails requires an [IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol)-enabled
+email account. GitLab requires one of the following three strategies:
-- Email sub-addressing
-- Dedicated email address
+- Email sub-addressing (recommended)
- Catch-all mailbox
+- Dedicated email address (supports Reply by Email only)
Let's walk through each of these options.
-**If your provider or server supports email sub-addressing, we recommend using it.
-Most features (other than reply by email) only work with sub-addressing.**
-
-[IMAP]: https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol
-
### Email sub-addressing
[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is
-a feature where any email to `user+some_arbitrary_tag@example.com` will end up
-in the mailbox for `user@example.com`, and is supported by providers such as
-Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the
-[Postfix mail server] which you can run on-premises.
-
-[Postfix mail server]: reply_by_email_postfix_setup.md
+a mail server feature where any email to `user+arbitrary_tag@example.com` will end up
+in the mailbox for `user@example.com` . It is supported by providers such as
+Gmail, Google Apps, Yahoo! Mail, Outlook.com, and iCloud, as well as the
+[Postfix mail server](reply_by_email_postfix_setup.md), which you can run on-premises.
+
+TIP: **Tip:**
+If your provider or server supports email sub-addressing, we recommend using it.
+A dedicated email address only supports Reply by Email functionality.
+A catch-all mailbox supports the same features as sub-addressing as of GitLab 11.7,
+but sub-addressing is still preferred because only one email address is used,
+leaving a catch-all available for other purposes beyond GitLab.
-### Dedicated email address
+### Catch-all mailbox
-This solution is really simple to set up: you just have to create an email
-address dedicated to receive your users' replies to GitLab notifications.
+A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain
+receives all emails addressed to the domain that do not match any addresses that
+exist on the mail server.
-### Catch-all mailbox
+As of GitLab 11.7, catch-all mailboxes support the same features as
+email sub-addressing, but email sub-addressing remains our recommendation so that you
+can reserve your catch-all mailbox for other purposes.
-A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will
-"catch all" the emails addressed to the domain that do not exist in the mail
-server.
+### Dedicated email address
-GitLab can be set up to allow users to comment on issues and merge requests by
-replying to notification emails.
+This solution is relatively simple to set up: you just need to create an email
+address dedicated to receive your users' replies to GitLab notifications. However,
+this method only supports replies, and not the other features of [incoming email](#incoming-email).
## Set it up
@@ -160,14 +162,16 @@ for a real-world example of this exploit.
gitlab_rails['incoming_email_idle_timeout'] = 60
```
- Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes
- mailbox incoming@exchange.example.com
+ Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes the
+ catch-all mailbox incoming@exchange.example.com
```ruby
gitlab_rails['incoming_email_enabled'] = true
- # The email address replies are sent to - Exchange does not support sub-addressing so %{key} is not used here
- gitlab_rails['incoming_email_address'] = "incoming@exchange.example.com"
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+ # Exchange does not support sub-addressing, so a catch-all mailbox must be used.
+ gitlab_rails['incoming_email_address'] = "incoming-%{key}@exchange.example.com"
# Email account username
# Typically this is the userPrincipalName (UPN)
@@ -279,15 +283,17 @@ for a real-world example of this exploit.
idle_timeout: 60
```
- Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes
- mailbox incoming@exchange.example.com
+ Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes the
+ catch-all mailbox incoming@exchange.example.com
```yaml
incoming_email:
enabled: true
- # The email address replies are sent to - Exchange does not support sub-addressing so %{key} is not used here
- address: "incoming@exchange.example.com"
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+ # Exchange does not support sub-addressing, so a catch-all mailbox must be used.
+ address: "incoming-%{key}@exchange.example.com"
# Email account username
# Typically this is the userPrincipalName (UPN)
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 89132cd95f0..ecb0801bac4 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -41,13 +41,14 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Polling](polling.md): Configure how often the GitLab UI polls for updates.
- [GitLab Pages configuration](pages/index.md): Enable and configure GitLab Pages.
- [GitLab Pages configuration for GitLab source installations](pages/source.md): Enable and configure GitLab Pages on
-[source installations](../install/installation.md#installation-from-source).
+ [source installations](../install/installation.md#installation-from-source).
- [Environment variables](environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab.
- [Plugins](plugins.md): With custom plugins, GitLab administrators can introduce custom integrations without modifying GitLab's source code.
- [Enforcing Terms of Service](../user/admin_area/settings/terms.md)
- [Third party offers](../user/admin_area/settings/third_party_offers.md)
- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
- [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages.
+- [Broadcast Messages](../user/admin_area/broadcast_messages.md): Send messages to GitLab users through the UI.
#### Customizing GitLab's appearance
@@ -80,7 +81,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/): Integrate with [Mattermost](https://about.mattermost.com/), an open source, private cloud workplace for web messaging.
- [PlantUML](integration/plantuml.md): Create simple diagrams in AsciiDoc and Markdown documents
-created in snippets, wikis, and repos.
+ created in snippets, wikis, and repos.
- [Web terminals](integration/terminal.md): Provide terminal access to your applications deployed to Kubernetes from within GitLab's CI/CD [environments](../ci/environments.md#web-terminals).
## User settings and permissions
diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md
index fa58d0ef15f..40e03093743 100644
--- a/doc/administration/integration/terminal.md
+++ b/doc/administration/integration/terminal.md
@@ -14,20 +14,33 @@ A detailed overview of the architecture of web terminals and how they work
can be found in [this document](https://gitlab.com/gitlab-org/gitlab-workhorse/blob/master/doc/terminal.md).
In brief:
-* GitLab relies on the user to provide their own Kubernetes credentials, and to
+- GitLab relies on the user to provide their own Kubernetes credentials, and to
appropriately label the pods they create when deploying.
-* When a user navigates to the terminal page for an environment, they are served
+- When a user navigates to the terminal page for an environment, they are served
a JavaScript application that opens a WebSocket connection back to GitLab.
-* The WebSocket is handled in [Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse),
+- The WebSocket is handled in [Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse),
rather than the Rails application server.
-* Workhorse queries Rails for connection details and user permissions; Rails
- queries Kubernetes for them in the background, using [Sidekiq](../troubleshooting/sidekiq.md)
-* Workhorse acts as a proxy server between the user's browser and the Kubernetes
+- Workhorse queries Rails for connection details and user permissions. Rails
+ queries Kubernetes for them in the background using [Sidekiq](../troubleshooting/sidekiq.md).
+- Workhorse acts as a proxy server between the user's browser and the Kubernetes
API, passing WebSocket frames between the two.
-* Workhorse regularly polls Rails, terminating the WebSocket connection if the
+- Workhorse regularly polls Rails, terminating the WebSocket connection if the
user no longer has permission to access the terminal, or if the connection
details have changed.
+## Security
+
+GitLab and [GitLab Runner](https://docs.gitlab.com/runner/) take some
+precautions to keep interactive web terminal data encrypted between them, and
+everything protected with authorization guards. This is described in more
+detail below.
+
+- Interactive web terminals are completely disabled unless [`[session_server]`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section) is configured.
+- Every time the runner starts, it will generate an `x509` certificate that will be used for a `wss` (Web Socket Secure) connection.
+- For every created job, a random URL is generated which is discarded at the end of the job. This URL is used to establish a web socket connection. The URL for the session is in the format `(IP|HOST):PORT/session/$SOME_HASH`, where the `IP/HOST` and `PORT` are the configured [`listen_address`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section).
+- Every session URL that is created has an authorization header that needs to be sent, to establish a `wss` connection.
+- The session URL is not exposed to the users in any way. GitLab holds all the state internally and proxies accordingly.
+
## Enabling and disabling terminal support
As web terminals use WebSockets, every HTTP/HTTPS reverse proxy in front of
@@ -40,10 +53,10 @@ However, if you run a [load balancer](../high_availability/load_balancer.md) in
front of GitLab, you may need to make some changes to your configuration. These
guides document the necessary steps for a selection of popular reverse proxies:
-* [Apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html)
-* [NGINX](https://www.nginx.com/blog/websocket-nginx/)
-* [HAProxy](http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/)
-* [Varnish](https://www.varnish-cache.org/docs/4.1/users-guide/vcl-example-websockets.html)
+- [Apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html)
+- [NGINX](https://www.nginx.com/blog/websocket-nginx/)
+- [HAProxy](http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/)
+- [Varnish](https://www.varnish-cache.org/docs/4.1/users-guide/vcl-example-websockets.html)
Workhorse won't let WebSocket requests through to non-WebSocket endpoints, so
it's safe to enable support for these headers globally. If you'd rather had a
@@ -60,8 +73,8 @@ the `Connection` and `Upgrade` hop-by-hop headers in the *first* HTTP reverse
proxy in the chain. For most users, this will be the NGINX server bundled with
Omnibus GitLab, in which case, you need to:
-* Find the `nginx['proxy_set_headers']` section of your `gitlab.rb` file
-* Ensure the whole block is uncommented, and then comment out or remove the
+- Find the `nginx['proxy_set_headers']` section of your `gitlab.rb` file
+- Ensure the whole block is uncommented, and then comment out or remove the
`Connection` and `Upgrade` lines.
For your own load balancer, just reverse the configuration changes recommended
diff --git a/doc/administration/issue_closing_pattern.md b/doc/administration/issue_closing_pattern.md
index 35f25e55414..160da47c780 100644
--- a/doc/administration/issue_closing_pattern.md
+++ b/doc/administration/issue_closing_pattern.md
@@ -17,7 +17,7 @@ The default pattern can be located in [gitlab.yml.example] under the
"Automatic issue closing" section.
> **Tip:**
-You are advised to use http://rubular.com to test the issue closing pattern.
+You are advised to use <http://rubular.com> to test the issue closing pattern.
Because Rubular doesn't understand `%{issue_ref}`, you can replace this by
`#\d+` when testing your patterns, which matches only local issue references like `#123`.
diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md
index 7947b0fedc4..1f431f8bd62 100644
--- a/doc/administration/monitoring/performance/grafana_configuration.md
+++ b/doc/administration/monitoring/performance/grafana_configuration.md
@@ -33,7 +33,7 @@ Test Connection to ensure the configuration is correct.
- **Name**: InfluxDB
- **Default**: Checked
- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x)
-- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB
+- **Url**: `https://localhost:8086` (Or the remote URL if you've installed InfluxDB
on a separate server)
- **Access**: proxy
- **Database**: gitlab
diff --git a/doc/administration/operations/unicorn.md b/doc/administration/operations/unicorn.md
index bad61151bda..0e2079cb093 100644
--- a/doc/administration/operations/unicorn.md
+++ b/doc/administration/operations/unicorn.md
@@ -60,7 +60,17 @@ Unicorn master then automatically replaces the worker process.
This is a robust way to handle memory leaks: Unicorn is designed to handle
workers that 'crash' so no user requests will be dropped. The
unicorn-worker-killer gem is designed to only terminate a worker process _in
-between requests_, so no user requests are affected.
+between requests_, so no user requests are affected. You can set the minimum and
+maximum memory threshold (in bytes) for the Unicorn worker killer by
+setting the following values `/etc/gitlab/gitlab.rb`:
+
+```ruby
+unicorn['worker_memory_limit_min'] = "400 * 1 << 20"
+unicorn['worker_memory_limit_max'] = "650 * 1 << 20"
+```
+
+Otherwise, you can set the `GITLAB_UNICORN_MEMORY_MIN` and `GITLAB_UNICORN_MEMORY_MIN`
+[environment variables](../environment_variables.md).
This is what a Unicorn worker memory restart looks like in unicorn_stderr.log.
You see that worker 4 (PID 125918) is inspecting itself and decides to exit.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index cbd3032bd4e..10ae8c7dedf 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -332,6 +332,42 @@ The maximum size of the unpacked archive per project can be configured in the
Admin area under the Application settings in the **Maximum size of pages (MB)**.
The default is 100MB.
+## Running GitLab Pages in a separate server
+
+You may want to run GitLab Pages daemon on a separate server in order to decrease the load on your main application server.
+Follow the steps below to configure GitLab Pages in a separate server.
+
+1. Suppose you have the main GitLab application server named `app1`. Prepare
+new Linux server (let's call it `app2`), create NFS share there and configure access to
+this share from `app1`. Let's use the default GitLab Pages folder `/var/opt/gitlab/gitlab-rails/shared/pages`
+as the shared folder on `app2` and mount it to `/mnt/pages` on `app1`.
+
+1. On `app2` install GitLab omnibus and modify `/etc/gitlab/gitlab.rb` this way:
+
+ ```shell
+ external_url 'http://<ip-address-of-the-server>'
+ pages_external_url "http://<your-pages-domain>"
+ postgresql['enable'] = false
+ redis['enable'] = false
+ prometheus['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ gitaly['enable'] = false
+ alertmanager['enable'] = false
+ node_exporter['enable'] = false
+ ```
+1. Run `sudo gitlab-ctl reconfigure`.
+1. On `app1` apply the following changes to `/etc/gitlab/gitlab.rb`:
+
+ ```shell
+ gitlab_pages['enable'] = false
+ pages_external_url "http://<your-pages-domain>"
+ gitlab_rails['pages_path'] = "/mnt/pages"
+ ```
+
+1. Run `sudo gitlab-ctl reconfigure`.
+
## Backup
Pages are part of the [regular backup][backup] so there is nothing to configure.
diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md
index 0ca1d77f1d0..d8f80965c21 100644
--- a/doc/administration/raketasks/check.md
+++ b/doc/administration/raketasks/check.md
@@ -52,9 +52,9 @@ and these checks will verify them against current files.
Currently, integrity checks are supported for the following types of file:
-* CI artifacts (Available from version 10.7.0)
-* LFS objects (Available from version 10.6.0)
-* User uploads (Available from version 10.6.0)
+- CI artifacts (Available from version 10.7.0)
+- LFS objects (Available from version 10.6.0)
+- User uploads (Available from version 10.6.0)
**Omnibus Installation**
diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md
index 4c42cb7756a..d57fc67c83e 100644
--- a/doc/administration/reply_by_email_postfix_setup.md
+++ b/doc/administration/reply_by_email_postfix_setup.md
@@ -333,6 +333,6 @@ If all the tests were successful, Postfix is all set up and ready to receive ema
---
-_This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._
+_This document was adapted from <https://help.ubuntu.com/community/PostfixBasicSetupHowto>, by contributors to the Ubuntu documentation wiki._
[incoming email]: incoming_email.md
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 12238ba7b32..51e1518d73f 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -7,8 +7,8 @@
Legacy Storage is the storage behavior prior to version 10.0. For historical
reasons, GitLab replicated the same mapping structure from the projects URLs:
-* Project's repository: `#{namespace}/#{project_name}.git`
-* Project's wiki: `#{namespace}/#{project_name}.wiki.git`
+- Project's repository: `#{namespace}/#{project_name}.git`
+- Project's wiki: `#{namespace}/#{project_name}.wiki.git`
This structure made it simple to migrate from existing solutions to GitLab and
easy for Administrators to find where the repository is stored.
diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md
index 643c5b9fe80..8f7280d5128 100644
--- a/doc/administration/troubleshooting/debug.md
+++ b/doc/administration/troubleshooting/debug.md
@@ -158,7 +158,7 @@ are concerned about affecting others during a production system, you can run a
separate Rails process to debug the issue:
1. Log in to your GitLab account.
-1. Copy the URL that is causing problems (e.g. https://gitlab.com/ABC).
+1. Copy the URL that is causing problems (e.g. `https://gitlab.com/ABC`).
1. Create a Personal Access Token for your user (Profile Settings -> Access Tokens).
1. Bring up the GitLab Rails console. For omnibus users, run:
@@ -211,5 +211,5 @@ The output in `/tmp/unicorn.txt` may help diagnose the root cause.
# More information
-* [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/)
-* [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt)
+- [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/)
+- [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt)
diff --git a/doc/api/README.md b/doc/api/README.md
index fd5e88cb9d5..692f63a400c 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -16,6 +16,7 @@ The following API resources are available:
- [Broadcast messages](broadcast_messages.md)
- [Code snippets](snippets.md)
- [Commits](commits.md)
+- [Container Registry](container_registry.md)
- [Custom attributes](custom_attributes.md)
- [Deploy keys](deploy_keys.md), and [deploy keys for multiple projects](deploy_key_multiple_projects.md)
- [Deployments](deployments.md)
@@ -49,6 +50,7 @@ The following API resources are available:
- [Projects](projects.md) including setting Webhooks
- [Project access requests](access_requests.md)
- [Project badges](project_badges.md)
+ - [Project clusters](project_clusters.md)
- [Project-level variables](project_level_variables.md)
- [Project import/export](project_import_export.md)
- [Project members](members.md)
@@ -68,6 +70,9 @@ The following API resources are available:
- [Sidekiq metrics](sidekiq_metrics.md)
- [System hooks](system_hooks.md)
- [Tags](tags.md)
+- [Releases](releases/index.md)
+- Release Assets
+ - [Links](releases/links.md)
- [Todos](todos.md)
- [Users](users.md)
- [Validate CI configuration](lint.md) (linting)
@@ -194,13 +199,13 @@ You can use a [personal access token][pat] to authenticate with the API by passi
Example of using the personal access token in a parameter:
```shell
-curl https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK
+curl https://gitlab.example.com/api/v4/projects?private_token=<your_access_token>
```
Example of using the personal access token in a header:
```shell
-curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects
+curl --header "Private-Token: <your_access_token>" https://gitlab.example.com/api/v4/projects
```
Read more about [personal access tokens][pat].
@@ -319,22 +324,22 @@ Example of a valid API call and a request using cURL with sudo request,
providing a username:
```
-GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username
+GET /projects?private_token=<your_access_token>&sudo=username
```
```shell
-curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" --header "Sudo: username" "https://gitlab.example.com/api/v4/projects"
+curl --header "Private-Token: <your_access_token>" --header "Sudo: username" "https://gitlab.example.com/api/v4/projects"
```
Example of a valid API call and a request using cURL with sudo request,
providing an ID:
```
-GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
+GET /projects?private_token=<your_access_token>&sudo=23
```
```shell
-curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" --header "Sudo: 23" "https://gitlab.example.com/api/v4/projects"
+curl --header "Private-Token: <your_access_token>" --header "Sudo: 23" "https://gitlab.example.com/api/v4/projects"
```
## Status codes
@@ -383,7 +388,7 @@ resources you can pass the following parameters:
In the example below, we list 50 [namespaces](namespaces.md) per page.
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/namespaces?per_page=50
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/namespaces?per_page=50
```
### Pagination Link header
@@ -397,7 +402,7 @@ and we request the second page (`page=2`) of [comments](notes.md) of the issue
with ID `8` which belongs to the project with ID `8`:
```bash
-curl --head --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/8/issues/8/notes?per_page=3&page=2
+curl --head --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/8/issues/8/notes?per_page=3&page=2
```
The response will then be:
@@ -434,6 +439,14 @@ Additional pagination headers are also sent back.
| `X-Next-Page` | The index of the next page |
| `X-Prev-Page` | The index of the previous page |
+CAUTION: **Caution:**
+For performance reasons since
+[GitLab 11.8](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23931)
+and **behind the `api_kaminari_count_with_limit`
+[feature flag](../development/feature_flags.md)**, if the number of resources is
+more than 10,000, the `X-Total` and `X-Total-Pages` headers as well as the
+`rel="last"` `Link` are not present in the response headers.
+
## Namespaced path encoding
If using namespaced API calls, make sure that the `NAMESPACE/PROJECT_NAME` is
@@ -465,7 +478,7 @@ We can call the API with `array` and `hash` types parameters as shown below:
`import_sources` is a parameter of type `array`:
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
-d "import_sources[]=github" \
-d "import_sources[]=bitbucket" \
"https://gitlab.example.com/api/v4/some_endpoint
@@ -476,7 +489,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
`override_params` is a parameter of type `hash`:
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
--form "namespace=email" \
--form "path=impapi" \
--form "file=@/path/to/somefile.txt"
@@ -592,7 +605,7 @@ Content-Type: application/json
## Encoding `+` in ISO 8601 dates
If you need to include a `+` in a query parameter, you may need to use `%2B` instead due
-a [W3 recommendation](http://www.w3.org/Addressing/URL/4_URI_Recommentations.html) that
+to a [W3 recommendation](http://www.w3.org/Addressing/URL/4_URI_Recommentations.html) that
causes a `+` to be interpreted as a space. For example, in an ISO 8601 date, you may want to pass
a time in Mountain Standard Time, such as:
diff --git a/doc/api/access_requests.md b/doc/api/access_requests.md
index 4b2014ca843..973c3968d90 100644
--- a/doc/api/access_requests.md
+++ b/doc/api/access_requests.md
@@ -28,8 +28,8 @@ GET /projects/:id/access_requests
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/access_requests
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/access_requests
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/access_requests
```
Example response:
@@ -69,8 +69,8 @@ POST /projects/:id/access_requests
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/access_requests
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/access_requests
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/access_requests
```
Example response:
@@ -102,8 +102,8 @@ PUT /projects/:id/access_requests/:user_id/approve
| `access_level` | integer | no | A valid access level (defaults: `30`, developer access level) |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests/:user_id/approve?access_level=20
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/access_requests/:user_id/approve?access_level=20
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/access_requests/:user_id/approve?access_level=20
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/access_requests/:user_id/approve?access_level=20
```
Example response:
@@ -134,6 +134,6 @@ DELETE /projects/:id/access_requests/:user_id
| `user_id` | integer | yes | The user ID of the access requester |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/access_requests/:user_id
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/access_requests/:user_id
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/access_requests/:user_id
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/access_requests/:user_id
```
diff --git a/doc/api/applications.md b/doc/api/applications.md
index d74a3cdf5c1..82955f0c1db 100644
--- a/doc/api/applications.md
+++ b/doc/api/applications.md
@@ -1,29 +1,37 @@
# Applications API
-> [Introduced][ce-8160] in GitLab 10.5
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8160) in GitLab 10.5.
-[ce-8160]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8160
+Applications API operates on OAuth applications for:
-Only admin user can use the Applications API.
+- [Using GitLab as an authentication provider](../integration/oauth_provider.md).
+- [Allowing access to GitLab resources on a user's behalf](oauth2.md).
-## Create a application
+NOTE: **Note:**
+Only admin users can use the Applications API.
-Create a application by posting a JSON payload.
+## Create an application
+
+Create an application by posting a JSON payload.
Returns `200` if the request succeeds.
-```
+```text
POST /applications
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `name` | string | yes | The name of the application |
-| `redirect_uri` | string | yes | The redirect URI of the application |
-| `scopes` | string | yes | The scopes of the application |
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:---------------|:-------|:---------|:---------------------------------|
+| `name` | string | yes | Name of the application. |
+| `redirect_uri` | string | yes | Redirect URI of the application. |
+| `scopes` | string | yes | Scopes of the application. |
+
+Example request:
-```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "name=MyApplication&redirect_uri=http://redirect.uri&scopes=" https://gitlab.example.com/api/v4/applications
+```sh
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "name=MyApplication&redirect_uri=http://redirect.uri&scopes=" https://gitlab.example.com/api/v4/applications
```
Example response:
@@ -42,12 +50,14 @@ Example response:
List all registered applications.
-```
+```text
GET /applications
```
-```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/applications
+Example request:
+
+```sh
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/applications
```
Example response:
@@ -63,7 +73,8 @@ Example response:
]
```
-> Note: the `secret` value will not be exposed by this API.
+NOTE: **Note:**
+The `secret` value will not be exposed by this API.
## Delete an application
@@ -71,7 +82,7 @@ Delete a specific application.
Returns `204` if the request succeeds.
-```
+```text
DELETE /applications/:id
```
@@ -79,6 +90,8 @@ Parameters:
- `id` (required) - The id of the application (not the application_id)
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/applications/:id
+Example request:
+
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/applications/:id
```
diff --git a/doc/api/avatar.md b/doc/api/avatar.md
index aa6f7c185ae..e55fffba4b2 100644
--- a/doc/api/avatar.md
+++ b/doc/api/avatar.md
@@ -1,33 +1,41 @@
# Avatar API
-> [Introduced][ce-19121] in GitLab 11.0
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19121) in GitLab 11.0.
## Get a single avatar URL
-Get a single avatar URL for a given email address. If user with matching public
-email address is not found, results from external avatar services are returned.
-This endpoint can be accessed without authentication. In case public visibility
-is restricted, response will be `403 Forbidden` when unauthenticated.
+Get a single [avatar](../user/profile/index.md#profile-settings) URL for a user with the given email address.
-```
+If:
+
+- No user with the given public email address is found, results from external avatar services are
+ returned.
+- Public visibility is restricted, response will be `403 Forbidden` when unauthenticated.
+
+NOTE: **Note:**
+This endpoint can be accessed without authentication.
+
+```text
GET /avatar?email=admin@example.com
```
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | --------------------- |
-| `email` | string | yes | Public email address of the user |
-| `size` | integer | no | Single pixel dimension (since images are squares). Only used for avatar lookups at `Gravatar` or at the configured `Libravatar` server |
+Parameters:
-```bash
-curl https://gitlab.example.com/api/v4/avatar?email=admin@example.com
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------|
+| `email` | string | yes | Public email address of the user. |
+| `size` | integer | no | Single pixel dimension (since images are squares). Only used for avatar lookups at `Gravatar` or at the configured `Libravatar` server. |
+
+Example request:
+
+```sh
+curl https://gitlab.example.com/api/v4/avatar?email=admin@example.com&size=32
```
Example response:
```json
{
- "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon"
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=64&d=identicon"
}
```
-
-[ce-19121]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19121
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 3f9542d6653..92936a277ac 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -1,19 +1,26 @@
# Award Emoji API
-> [Introduced][ce-4575] in GitLab 8.9, Snippet support in 8.12
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4575) in GitLab 8.9. Snippet support added in 8.12.
+An [awarded emoji](../user/award_emojis.md) tells a thousand words.
-An awarded emoji tells a thousand words, and can be awarded on issues, merge
-requests, snippets, and notes/comments. Issues, merge requests, snippets, and notes are further called
-`awardables`.
+Emoji can be awarded on the following (known as "awardables"):
+
+- [Issues](../user/project/issues/index.md) ([API](issues.md)).
+- [Merge requests](../user/project/merge_requests/index.md) ([API](merge_requests.md)).
+- [Snippets](../user/snippets.md) ([API](snippets.md)).
+
+Emoji can also [be awarded](../user/award_emojis.html#award-emoji-for-comments) on comments (also known as notes). See also [Notes API](notes.md).
## Issues, merge requests, and snippets
+See [Award Emoji on Comments](#award-emoji-on-comments) for information on using these endpoints with comments.
+
### List an awardable's award emoji
-Gets a list of all award emoji
+Get a list of all award emoji for a specified awardable.
-```
+```text
GET /projects/:id/issues/:issue_iid/award_emoji
GET /projects/:id/merge_requests/:merge_request_iid/award_emoji
GET /projects/:id/snippets/:snippet_id/award_emoji
@@ -21,16 +28,18 @@ GET /projects/:id/snippets/:snippet_id/award_emoji
Parameters:
-| 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 |
-| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
-```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji
```
-Example Response:
+Example response:
```json
[
@@ -71,9 +80,9 @@ Example Response:
### Get single award emoji
-Gets a single award emoji from an issue, snippet, or merge request.
+Get a single award emoji from an issue, snippet, or merge request.
-```
+```text
GET /projects/:id/issues/:issue_iid/award_emoji/:award_id
GET /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
@@ -81,17 +90,19 @@ GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
Parameters:
-| 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 |
-| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
-| `award_id` | integer | yes | The ID of the award emoji |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `award_id` | integer | yes | ID of the award emoji. |
+
+Example request:
-```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/1
+```sh
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/1
```
-Example Response:
+Example response:
```json
{
@@ -114,9 +125,9 @@ Example Response:
### Award a new emoji
-This end point creates an award emoji on the specified resource
+Create an award emoji on the specified awardable.
-```
+```text
POST /projects/:id/issues/:issue_iid/award_emoji
POST /projects/:id/merge_requests/:merge_request_iid/award_emoji
POST /projects/:id/snippets/:snippet_id/award_emoji
@@ -124,14 +135,14 @@ POST /projects/:id/snippets/:snippet_id/award_emoji
Parameters:
-| 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 |
-| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
-| `name` | string | yes | The name of the emoji, without colons |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `name` | string | yes | Name of the emoji without colons. |
-```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji?name=blowfish
+```sh
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji?name=blowfish
```
Example Response:
@@ -157,10 +168,12 @@ Example Response:
### Delete an award emoji
-Sometimes its just not meant to be, and you'll have to remove your award. Only available to
-admins or the author of the award.
+Sometimes it's just not meant to be and you'll have to remove the award.
-```
+NOTE: **Note:**
+Only available to administrators or the author of the award.
+
+```text
DELETE /projects/:id/issues/:issue_iid/award_emoji/:award_id
DELETE /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
@@ -168,43 +181,47 @@ DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
Parameters:
-| 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 |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `award_id` | integer | yes | The ID of an award_emoji |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `award_id` | integer | yes | ID of an award emoji. |
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/344
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/344
```
-## Award Emoji on Notes
+## Award Emoji on Comments
-The endpoints documented above are available for Notes as well. Notes
-are a sub-resource of Issues, Merge Requests, or Snippets. The examples below
-describe working with Award Emoji on notes for an Issue, but can be
-easily adapted for notes on a Merge Request.
+Comments (also known as notes) are a sub-resource of issues, merge requests, and snippets.
-### List a note's award emoji
+NOTE: **Note:**
+The examples below describe working with award emoji on comments for an issue, but can be
+easily adapted for comments on a merge request.
-```
+### List a comment's award emoji
+
+Get all award emoji for a comment (note).
+
+```text
GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
```
Parameters:
-| 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 |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+Example request:
-```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji
+```sh
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji
```
-Example Response:
+Example response:
```json
[
@@ -227,26 +244,30 @@ Example Response:
]
```
-### Get single note's award emoji
+### Get an award emoji for a comment
-```
+Get a single award emoji for a comment (note).
+
+```text
GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
```
Parameters:
-| 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 |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `award_id` | integer | yes | The ID of the award emoji |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+| `award_id` | integer | yes | ID of the award emoji. |
+
+Example request:
-```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji/2
+```sh
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji/2
```
-Example Response:
+Example response:
```json
{
@@ -267,26 +288,30 @@ Example Response:
}
```
-### Award a new emoji on a note
+### Award a new emoji on a comment
-```
+Create an award emoji on the specified comment (note).
+
+```text
POST /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
```
Parameters:
-| 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 |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `name` | string | yes | The name of the emoji, without colons |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+| `name` | string | yes | Name of the emoji without colons. |
-```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji?name=rocket
+Example request:
+
+```sh
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji?name=rocket
```
-Example Response:
+Example response:
```json
{
@@ -307,26 +332,28 @@ Example Response:
}
```
-### Delete an award emoji
+### Delete an award emoji from a comment
-Sometimes its just not meant to be, and you'll have to remove your award. Only available to
-admins or the author of the award.
+Sometimes it's just not meant to be and you'll have to remove the award.
-```
+NOTE: **Note:**
+Only available to administrators or the author of the award.
+
+```text
DELETE /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
```
Parameters:
-| 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 |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `award_id` | integer | yes | The ID of an award_emoji |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+| `award_id` | integer | yes | ID of an award_emoji. |
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/345
-```
+Example request:
-[ce-4575]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4575
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/345
+```
diff --git a/doc/api/boards.md b/doc/api/boards.md
index 5f006f4f012..2a2622736c3 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -18,7 +18,7 @@ GET /projects/:id/boards
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards
```
Example response:
@@ -87,7 +87,7 @@ GET /projects/:id/boards/:board_id
| `board_id` | integer | yes | The ID of a board |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1
```
Example response:
@@ -156,7 +156,7 @@ GET /projects/:id/boards/:board_id/lists
| `board_id` | integer | yes | The ID of a board |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1/lists
```
Example response:
@@ -208,7 +208,7 @@ GET /projects/:id/boards/:board_id/lists/:list_id
| `list_id`| integer | yes | The ID of a board's list |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1
```
Example response:
@@ -240,7 +240,7 @@ POST /projects/:id/boards/:board_id/lists
| `label_id` | integer | yes | The ID of a label |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists?label_id=5
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1/lists?label_id=5
```
Example response:
@@ -273,7 +273,7 @@ PUT /projects/:id/boards/:board_id/lists/:list_id
| `position` | integer | yes | The position of the list |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1?position=2
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1?position=2
```
Example response:
@@ -305,5 +305,5 @@ DELETE /projects/:id/boards/:board_id/lists/:list_id
| `list_id` | integer | yes | The ID of a board's list |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1
```
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 4abf0639eb0..8d5f333ba77 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -1,22 +1,32 @@
# Branches API
+This API operates on [repository branches](../user/project/repository/branches/index.md).
+
+TIP: **Tip:**
+See also [Protected branches API](protected_branches.md).
+
## List repository branches
Get a list of repository branches from a project, sorted by name alphabetically.
-This endpoint can be accessed without authentication if the repository is
-publicly accessible.
-```
+NOTE: **Note:**
+This endpoint can be accessed without authentication if the repository is publicly accessible.
+
+```text
GET /projects/:id/repository/branches
```
-| 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 |
-| `search` | string | no | Return list of branches matching the search criteria. |
+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. |
+| `search` | string | no | Return list of branches matching the search criteria. |
+
+Example request:
-```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches
+```sh
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches
```
Example response:
@@ -53,20 +63,26 @@ Example response:
## Get single repository branch
-Get a single project repository branch. This endpoint can be accessed without
-authentication if the repository is publicly accessible.
+Get a single project repository branch.
-```
+NOTE: **Note:**
+This endpoint can be accessed without authentication if the repository is publicly accessible.
+
+```text
GET /projects/:id/repository/branches/:branch
```
-| 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 |
-| `branch` | string | yes | The name of the branch |
+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. |
+| `branch` | string | yes | Name of the branch. |
-```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches/master
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/master
```
Example response:
@@ -100,120 +116,34 @@ Example response:
## Protect repository branch
->**Note:** This API endpoint is deprecated in favor of `POST /projects/:id/protected_branches`.
-
-Protects a single project repository branch. This is an idempotent function,
-protecting an already protected repository branch still returns a `200 OK`
-status code.
-
-```
-PUT /projects/:id/repository/branches/:branch/protect
-```
-
-```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true
-```
-
-| 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 |
-| `branch` | string | yes | The name of the branch |
-| `developers_can_push` | boolean | no | Flag if developers can push to the branch |
-| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch |
-
-Example response:
-
-```json
-{
- "commit": {
- "author_email": "john@example.com",
- "author_name": "John Smith",
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00",
- "committer_email": "john@example.com",
- "committer_name": "John Smith",
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "short_id": "7b5c3cc",
- "title": "add projects API",
- "message": "add projects API",
- "parent_ids": [
- "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- ]
- },
- "name": "master",
- "merged": false,
- "protected": true,
- "default": true,
- "developers_can_push": true,
- "developers_can_merge": true,
- "can_push": true
-}
-```
+See [`POST /projects/:id/protected_branches`](protected_branches.md#protect-repository-branches) for
+information on protecting repository branches.
## Unprotect repository branch
->**Note:** This API endpoint is deprecated in favor of `DELETE /projects/:id/protected_branches/:name`
+See [`DELETE /projects/:id/protected_branches/:name`](protected_branches.md#unprotect-repository-branches)
+for information on unprotecting repository branches.
-Unprotects a single project repository branch. This is an idempotent function,
-unprotecting an already unprotected repository branch still returns a `200 OK`
-status code.
-
-```
-PUT /projects/:id/repository/branches/:branch/unprotect
-```
-
-```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches/master/unprotect
-```
-
-| 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 |
-| `branch` | string | yes | The name of the branch |
+## Create repository branch
-Example response:
+Create a new branch in the repository.
-```json
-{
- "commit": {
- "author_email": "john@example.com",
- "author_name": "John Smith",
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00",
- "committer_email": "john@example.com",
- "committer_name": "John Smith",
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "short_id": "7b5c3cc",
- "title": "add projects API",
- "message": "add projects API",
- "parent_ids": [
- "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- ]
- },
- "name": "master",
- "merged": false,
- "protected": false,
- "default": true,
- "developers_can_push": false,
- "developers_can_merge": false,
- "can_push": true
-}
+```text
+POST /projects/:id/repository/branches
```
-## Create repository branch
+Parameters:
-```
-POST /projects/:id/repository/branches
-```
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:-------------------------------------------------------------------------------------------------------------|
+| `id` | integer | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `branch` | string | yes | Name of the branch. |
+| `ref` | string | yes | Branch name or commit SHA to create branch from. |
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
-| `ref` | string | yes | The branch name or commit SHA to create branch from |
+Example request:
-```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/branches?branch=newbranch&ref=master"
+```sh
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches?branch=newbranch&ref=master
```
Example response:
@@ -247,36 +177,47 @@ Example response:
## Delete repository branch
-```
+Delete a branch from the repository.
+
+NOTE: **Note:**
+In the case of an error, an explanation message is provided.
+
+```text
DELETE /projects/:id/repository/branches/:branch
```
-| 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 |
-| `branch` | string | yes | The name of the branch |
+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. |
+| `branch` | string | yes | Name of the branch. |
-In case of an error, an explaining message is provided.
+Example request:
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/branches/newbranch"
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/newbranch
```
## Delete merged branches
Will delete all branches that are merged into the project's default branch.
-Protected branches will not be deleted as part of this operation.
+NOTE: **Note:**
+[Protected branches](../user/project/protected_branches.md) will not be deleted as part of this operation.
-```
+```text
DELETE /projects/:id/repository/merged_branches
```
-| 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 |
+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. |
+Example request:
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/merged_branches"
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/merged_branches
```
diff --git a/doc/api/broadcast_messages.md b/doc/api/broadcast_messages.md
index a8a248a17f4..fe370682308 100644
--- a/doc/api/broadcast_messages.md
+++ b/doc/api/broadcast_messages.md
@@ -13,7 +13,7 @@ GET /broadcast_messages
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/broadcast_messages
```
Example response:
@@ -43,7 +43,7 @@ GET /broadcast_messages/:id
| `id` | integer | yes | Broadcast message ID |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/broadcast_messages/1
```
Example response:
@@ -75,7 +75,7 @@ POST /broadcast_messages
| `font` | string | no | Foreground color hex code |
```bash
-curl --data "message=Deploy in progress&color=#cecece" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages
+curl --data "message=Deploy in progress&color=#cecece" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/broadcast_messages
```
Example response:
@@ -108,7 +108,7 @@ PUT /broadcast_messages/:id
| `font` | string | no | Foreground color hex code |
```bash
-curl --request PUT --data "message=Update message&color=#000" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages/1
+curl --request PUT --data "message=Update message&color=#000" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/broadcast_messages/1
```
Example response:
@@ -136,5 +136,5 @@ DELETE /broadcast_messages/:id
| `id` | integer | yes | Broadcast message ID |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/broadcast_messages/1
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/broadcast_messages/1
```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 6c16216429d..14742f034e0 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -20,7 +20,7 @@ GET /projects/:id/repository/commits
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
```
Example response:
@@ -127,7 +127,7 @@ PAYLOAD=$(cat << 'JSON'
}
JSON
)
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data "$PAYLOAD" https://gitlab.example.com/api/v4/projects/1/repository/commits
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data "$PAYLOAD" https://gitlab.example.com/api/v4/projects/1/repository/commits
```
Example response:
@@ -173,7 +173,7 @@ Parameters:
| `stats` | boolean | no | Include commit stats. Default is true |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master
```
Example response:
@@ -229,7 +229,7 @@ Parameters:
| `type` | string | no | The scope of commits. Possible values `branch`, `tag`, `all`. Default is `all`. |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/5937ac0a7beb003549fc5fd26fc247adbce4a52e/refs?type=all"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits/5937ac0a7beb003549fc5fd26fc247adbce4a52e/refs?type=all"
```
Example response:
@@ -263,7 +263,7 @@ Parameters:
| `branch` | string | yes | The name of the branch |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/cherry_pick"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/cherry_pick"
```
Example response:
@@ -307,7 +307,7 @@ Parameters:
| `branch` | string | yes | Target branch name |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/a738f717824ff53aebad8b090c1b79a14f2bd9e8/revert"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/a738f717824ff53aebad8b090c1b79a14f2bd9e8/revert"
```
Example response:
@@ -345,7 +345,7 @@ Parameters:
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/diff"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/diff"
```
Example response:
@@ -381,7 +381,7 @@ Parameters:
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/comments"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/comments"
```
Example response:
@@ -434,7 +434,7 @@ POST /projects/:id/repository/commits/:sha/comments
| `line_type` | string | no | The line type. Takes `new` or `old` as arguments |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "note=Nice picture man\!" --form "path=dudeism.md" --form "line=11" --form "line_type=new" https://gitlab.example.com/api/v4/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/comments
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "note=Nice picture man\!" --form "path=dudeism.md" --form "line=11" --form "line_type=new" https://gitlab.example.com/api/v4/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/comments
```
Example response:
@@ -480,7 +480,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| `all` | boolean | no | Return all statuses, not only the latest ones
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses
```
Example response:
@@ -556,7 +556,7 @@ POST /projects/:id/statuses/:sha
| `coverage` | float | no | The total code coverage
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success"
```
Example response:
@@ -603,7 +603,7 @@ GET /projects/:id/repository/commits/:sha/merge_requests
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/af5b13261899fb2c0db30abdd0af8b07cb44fdc5/merge_requests"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/commits/af5b13261899fb2c0db30abdd0af8b07cb44fdc5/merge_requests"
```
Example response:
diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md
new file mode 100644
index 00000000000..b70854103e8
--- /dev/null
+++ b/doc/api/container_registry.md
@@ -0,0 +1,200 @@
+# Container Registry API
+
+This is the API docs of the [GitLab Container Registry](../user/project/container_registry.md).
+
+## List registry repositories
+
+Get a list of registry repositories in a project.
+
+```
+GET /projects/:id/registry/repositories
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "",
+ "path": "group/project",
+ "location": "gitlab.example.com:5000/group/project",
+ "created_at": "2019-01-10T13:38:57.391Z"
+ },
+ {
+ "id": 2,
+ "name": "releases",
+ "path": "group/project/releases",
+ "location": "gitlab.example.com:5000/group/project/releases",
+ "created_at": "2019-01-10T13:39:08.229Z"
+ }
+]
+```
+
+## Delete registry repository
+
+Get a list of repository commits in a project.
+
+This operation is executed asynchronously and might take some time to get executed.
+
+```
+DELETE /projects/:id/registry/repositories/:repository_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. |
+| `repository_id` | integer | yes | The ID of registry repository. |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2"
+```
+
+## List repository tags
+
+Get a list of tags for given registry repository.
+
+```
+GET /projects/:id/registry/repositories/:repository_id/tags
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `repository_id` | integer | yes | The ID of registry repository. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2/tags"
+```
+
+Example response:
+
+```json
+[
+ {
+ "name": "A",
+ "path": "group/project:A",
+ "location": "gitlab.example.com:5000/group/project:A"
+ },
+ {
+ "name": "latest",
+ "path": "group/project:latest",
+ "location": "gitlab.example.com:5000/group/project:latest"
+ }
+]
+```
+
+## Get details of a repository tag
+
+Get details of a registry repository tag.
+
+```
+GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `repository_id` | integer | yes | The ID of registry repository. |
+| `tag_name` | string | yes | The name of tag. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2/tags/v10.0.0"
+```
+
+Example response:
+
+```json
+{
+ "name": "v10.0.0",
+ "path": "group/project:latest",
+ "location": "gitlab.example.com:5000/group/project:latest",
+ "revision": "e9ed9d87c881d8c2fd3a31b41904d01ba0b836e7fd15240d774d811a1c248181",
+ "short_revision": "e9ed9d87c",
+ "digest": "sha256:c3490dcf10ffb6530c1303522a1405dfaf7daecd8f38d3e6a1ba19ea1f8a1751",
+ "created_at": "2019-01-06T16:49:51.272+00:00",
+ "total_size": 350224384
+}
+```
+
+## Delete a repository tag
+
+Delete a registry repository tag.
+
+```
+DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `repository_id` | integer | yes | The ID of registry repository. |
+| `tag_name` | string | yes | The name of tag. |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2/tags/v10.0.0"
+```
+
+## Delete repository tags in bulk
+
+Delete repository tags in bulk based on given criteria.
+
+```
+DELETE /projects/:id/registry/repositories/:repository_id/tags
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `repository_id` | integer | yes | The ID of registry repository. |
+| `name_regex` | string | yes | The regex of the name to delete. To delete all tags specify `.*`. |
+| `keep_n` | integer | no | The amount of latest tags of given name to keep. |
+| `older_than` | string | no | Tags to delete that are older than the given time, written in human readable form `1h`, `1d`, `1month`. |
+
+This API call performs the following operations:
+
+1. It orders all tags by creation date. The creation date is the time of the
+ manifest creation, not the time of tag push.
+1. It removes only the tags matching the given `name_regex`.
+1. It never removes the tag named `latest`.
+1. It keeps N latest matching tags (if `keep_n` is specified).
+1. It only removes tags that are older than X amount of time (if `older_than` is specified).
+1. It schedules the asynchronous job to be executed in the background.
+
+These operations are executed asynchronously and it might
+take time to get executed. You can run this at most
+once an hour for a given container repository.
+
+NOTE: **Note:**
+Due to a [Docker Distribution deficiency](https://gitlab.com/gitlab-org/gitlab-ce/issues/21405),
+it doesn't remove tags whose manifest is shared by multiple tags.
+
+Examples:
+
+1. Remove tag names that are matching the regex (Git SHA), keep always at least 5,
+ and remove ones that are older than 2 days:
+
+ ```bash
+ curl --request DELETE --data 'name_regex=[0-9a-z]{40}' --data 'keep_n=5' --data 'older_than=2d' --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2/tags"
+ ```
+
+2. Remove all tags, but keep always the latest 5:
+
+ ```bash
+ curl --request DELETE --data 'name_regex=.*' --data 'keep_n=5' --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2/tags"
+ ```
+
+3. Remove all tags that are older than 1 month:
+
+ ```bash
+ curl --request DELETE --data 'name_regex=.*' --data 'older_than=1month' --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2/tags"
+ ```
diff --git a/doc/api/custom_attributes.md b/doc/api/custom_attributes.md
index 91d1b0e1520..d270b804ad5 100644
--- a/doc/api/custom_attributes.md
+++ b/doc/api/custom_attributes.md
@@ -20,7 +20,7 @@ GET /projects/:id/custom_attributes
| `id` | integer | yes | The ID of a resource |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/custom_attributes
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/42/custom_attributes
```
Example response:
@@ -54,7 +54,7 @@ GET /projects/:id/custom_attributes/:key
| `key` | string | yes | The key of the custom attribute |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/custom_attributes/location
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/42/custom_attributes/location
```
Example response:
@@ -84,7 +84,7 @@ PUT /projects/:id/custom_attributes/:key
| `value` | string | yes | The value of the custom attribute |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "value=Greenland" https://gitlab.example.com/api/v4/users/42/custom_attributes/location
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --data "value=Greenland" https://gitlab.example.com/api/v4/users/42/custom_attributes/location
```
Example response:
@@ -112,5 +112,5 @@ DELETE /projects/:id/custom_attributes/:key
| `key` | string | yes | The key of the custom attribute |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/custom_attributes/location
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/42/custom_attributes/location
```
diff --git a/doc/api/deploy_key_multiple_projects.md b/doc/api/deploy_key_multiple_projects.md
index 127f9a196de..0c9e3e66cae 100644
--- a/doc/api/deploy_key_multiple_projects.md
+++ b/doc/api/deploy_key_multiple_projects.md
@@ -7,23 +7,23 @@ First, find the ID of the projects you're interested in, by either listing all
projects:
```
-curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v4/projects
+curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/projects
```
Or finding the ID of a group and then listing all projects in that group:
```
-curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v4/groups
+curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups
# For group 1234:
-curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v4/groups/1234
+curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups/1234
```
With those IDs, add the same deploy key to all:
```
for project_id in 321 456 987; do
- curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" \
+ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \
--data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v4/projects/${project_id}/deploy_keys
done
```
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 698fa22a438..1d7523fcc3d 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -9,7 +9,7 @@ GET /deploy_keys
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/deploy_keys"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/deploy_keys"
```
Example response:
@@ -44,7 +44,7 @@ GET /projects/:id/deploy_keys
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/deploy_keys"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/deploy_keys"
```
Example response:
@@ -84,7 +84,7 @@ Parameters:
| `key_id` | integer | yes | The ID of the deploy key |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/deploy_keys/11"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/deploy_keys/11"
```
Example response:
@@ -118,7 +118,7 @@ POST /projects/:id/deploy_keys
| `can_push` | boolean | no | Can deploy key push to the project's repository |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA...", "can_push": "true"}' "https://gitlab.example.com/api/v4/projects/5/deploy_keys/"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA...", "can_push": "true"}' "https://gitlab.example.com/api/v4/projects/5/deploy_keys/"
```
Example response:
@@ -148,7 +148,7 @@ PUT /projects/:id/deploy_keys/:key_id
| `can_push` | boolean | no | Can deploy key push to the project's repository |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "New deploy key", "can_push": true}' "https://gitlab.example.com/api/v4/projects/5/deploy_keys/11"
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"title": "New deploy key", "can_push": true}' "https://gitlab.example.com/api/v4/projects/5/deploy_keys/11"
```
Example response:
@@ -177,7 +177,7 @@ DELETE /projects/:id/deploy_keys/:key_id
| `key_id` | integer | yes | The ID of the deploy key |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/deploy_keys/13"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/deploy_keys/13"
```
## Enable a deploy key
@@ -185,7 +185,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gi
Enables a deploy key for a project so this can be used. Returns the enabled key, with a status code 201 when successful.
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/deploy_keys/13/enable
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/deploy_keys/13/enable
```
| Attribute | Type | Required | Description |
diff --git a/doc/api/deployments.md b/doc/api/deployments.md
index 1963b0a21de..0a67f134d54 100644
--- a/doc/api/deployments.md
+++ b/doc/api/deployments.md
@@ -15,7 +15,7 @@ GET /projects/:id/deployments
| `sort` | string | no | Return deployments sorted in `asc` or `desc` order. Default is `asc` |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/deployments"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/deployments"
```
Example of response
@@ -155,7 +155,7 @@ GET /projects/:id/deployments/:deployment_id
| `deployment_id` | integer | yes | The ID of the deployment |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/deployments/1"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/deployments/1"
```
Example of response
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index 3538a577c8e..79090ea5254 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -97,7 +97,7 @@ GET /projects/:id/issues/:issue_iid/discussions
```
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions
```
### Get single issue discussion
@@ -117,7 +117,7 @@ Parameters:
| `discussion_id` | integer | yes | The ID of a discussion |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
### Create new issue discussion
@@ -139,7 +139,7 @@ Parameters:
| `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: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions?body=comment
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions?body=comment
```
### Add note to existing issue discussion
@@ -162,7 +162,7 @@ Parameters:
| `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: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
```
### Modify existing issue discussion note
@@ -184,7 +184,7 @@ Parameters:
| `body` | string | yes | The content of a discussion |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
```
### Delete an issue discussion note
@@ -205,7 +205,7 @@ Parameters:
| `note_id` | integer | yes | The ID of a discussion note |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/636
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/636
```
## Snippets
@@ -303,7 +303,7 @@ GET /projects/:id/snippets/:snippet_id/discussions
```
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions
```
### Get single snippet discussion
@@ -323,7 +323,7 @@ Parameters:
| `discussion_id` | integer | yes | The ID of a discussion |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
### Create new snippet discussion
@@ -345,7 +345,7 @@ Parameters:
| `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: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions?body=comment
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions?body=comment
```
### Add note to existing snippet discussion
@@ -368,7 +368,7 @@ Parameters:
| `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: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
```
### Modify existing snippet discussion note
@@ -390,7 +390,7 @@ Parameters:
| `body` | string | yes | The content of a discussion |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
```
### Delete an snippet discussion note
@@ -411,7 +411,7 @@ Parameters:
| `note_id` | integer | yes | The ID of a discussion note |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/636
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/636
```
## Merge requests
@@ -562,7 +562,7 @@ Diff comments contain also position:
```
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions
```
### Get single merge request discussion
@@ -582,7 +582,7 @@ Parameters:
| `discussion_id` | integer | yes | The ID of a discussion |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
### Create new merge request discussion
@@ -617,7 +617,7 @@ Parameters:
| `position[y]` | integer | no | Y coordinate (for 'image' diff notes) |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions?body=comment
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions?body=comment
```
### Resolve a merge request discussion
@@ -638,7 +638,7 @@ Parameters:
| `resolved` | boolean | yes | Resolve/unresolve the discussion |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7?resolved=true
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7?resolved=true
```
@@ -662,7 +662,7 @@ Parameters:
| `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: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
```
### Modify an existing merge request discussion note
@@ -685,13 +685,13 @@ Parameters:
| `resolved` | boolean | no | Resolve/unresolve the note (exactly one of `body` or `resolved` must be set |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
```
Resolving a note:
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved=true
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved=true
```
### Delete a merge request discussion note
@@ -712,7 +712,7 @@ Parameters:
| `note_id` | integer | yes | The ID of a discussion note |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/636
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/636
```
## Commits
@@ -855,7 +855,7 @@ Diff comments contain also position:
```
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions
```
### Get single commit discussion
@@ -875,7 +875,7 @@ Parameters:
| `discussion_id` | integer | yes | The ID of a discussion |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
### Create new commit discussion
@@ -910,7 +910,7 @@ Parameters:
| `position[y]` | integer | no | Y coordinate (for 'image' diff notes) |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions?body=comment
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions?body=comment
```
### Add note to existing commit discussion
@@ -933,7 +933,7 @@ Parameters:
| `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: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
```
### Modify an existing commit discussion note
@@ -955,13 +955,13 @@ Parameters:
| `body` | string | no | The content of a note |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
```
Resolving a note:
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved=true
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved=true
```
### Delete a commit discussion note
@@ -982,5 +982,5 @@ Parameters:
| `note_id` | integer | yes | The ID of a discussion note |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/636
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/636
```
diff --git a/doc/api/environments.md b/doc/api/environments.md
index 29da4590a59..4a38dd73747 100644
--- a/doc/api/environments.md
+++ b/doc/api/environments.md
@@ -13,7 +13,7 @@ GET /projects/:id/environments
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/environments
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/environments
```
Example response:
@@ -46,7 +46,7 @@ POST /projects/:id/environments
| `external_url` | string | no | Place to link to for this environment |
```bash
-curl --data "name=deploy&external_url=https://deploy.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments"
+curl --data "name=deploy&external_url=https://deploy.example.gitlab.com" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/environments"
```
Example response:
@@ -78,7 +78,7 @@ PUT /projects/:id/environments/:environments_id
| `external_url` | string | no | The new external_url |
```bash
-curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments/1"
+curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/environments/1"
```
Example response:
@@ -106,7 +106,7 @@ DELETE /projects/:id/environments/:environment_id
| `environment_id` | integer | yes | The ID of the environment |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments/1"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/environments/1"
```
## Stop an environment
@@ -123,7 +123,7 @@ POST /projects/:id/environments/:environment_id/stop
| `environment_id` | integer | yes | The ID of the environment |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/environments/1/stop"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/environments/1/stop"
```
Example response:
diff --git a/doc/api/events.md b/doc/api/events.md
index e1c6b801a77..6dca8e52f69 100644
--- a/doc/api/events.md
+++ b/doc/api/events.md
@@ -71,7 +71,7 @@ Parameters:
Example request:
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/events?target_type=issue&action=created&after=2017-01-31&before=2017-03-01
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/events?target_type=issue&action=created&after=2017-01-31&before=2017-03-01
```
Example response:
@@ -143,7 +143,7 @@ Parameters:
| `sort` | string | no | Sort events in `asc` or `desc` order by `created_at`. Default is `desc` |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/:id/events
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/:id/events
```
Example response:
@@ -276,7 +276,7 @@ Parameters:
Example request:
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:project_id/events?target_type=issue&action=created&after=2017-01-31&before=2017-03-01
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:project_id/events?target_type=issue&action=created&after=2017-01-31&before=2017-03-01
```
Example response:
diff --git a/doc/api/features.md b/doc/api/features.md
index 6ee1c36ef5b..47f104e1f20 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -14,7 +14,7 @@ GET /features
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/features
```
Example response:
@@ -60,12 +60,13 @@ POST /features/:name
| `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
| `feature_group` | string | no | A Feature group name |
| `user` | string | no | A GitLab username |
+| `project` | string | no | A projects path, for example 'gitlab-org/gitlab-ce' |
-Note that you can enable or disable a feature for both a `feature_group` and a
-`user` with a single API call.
+Note that you can enable or disable a feature for a `feature_group`, a `user`,
+and a `project` in a single API call.
```bash
-curl --data "value=30" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/new_library
+curl --data "value=30" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/features/new_library
```
Example response:
diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md
index 71922318227..ec48bf4940b 100644
--- a/doc/api/graphql/index.md
+++ b/doc/api/graphql/index.md
@@ -24,7 +24,7 @@ feature flag. You can enable the feature using the [features api][features-api]
For example:
```shell
-curl --data "value=100" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/graphql
+curl --data "value=100" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/features/graphql
```
## Available queries
diff --git a/doc/api/group_badges.md b/doc/api/group_badges.md
index f2353542a5c..f88689d80c6 100644
--- a/doc/api/group_badges.md
+++ b/doc/api/group_badges.md
@@ -28,7 +28,7 @@ GET /groups/:id/badges
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/badges
```
Example response:
@@ -68,7 +68,7 @@ GET /groups/:id/badges/:badge_id
| `badge_id` | integer | yes | The badge ID |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
```
Example response:
@@ -99,7 +99,7 @@ POST /groups/:id/badges
| `image_url` | string | yes | URL of the badge image |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/groups/:id/badges
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/groups/:id/badges
```
Example response:
@@ -131,7 +131,7 @@ PUT /groups/:id/badges/:badge_id
| `image_url` | string | no | URL of the badge image |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
```
Example response:
@@ -161,7 +161,7 @@ DELETE /groups/:id/badges/:badge_id
| `badge_id` | integer | yes | The badge ID |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id
```
## Preview a badge from a group
@@ -179,7 +179,7 @@ GET /groups/:id/badges/render
| `image_url` | string | yes | URL of the badge image |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge
```
Example response:
diff --git a/doc/api/group_boards.md b/doc/api/group_boards.md
index 373904e50c4..9b0ac23b41c 100644
--- a/doc/api/group_boards.md
+++ b/doc/api/group_boards.md
@@ -18,7 +18,7 @@ GET /groups/:id/boards
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/boards
```
Example response:
@@ -75,7 +75,7 @@ GET /groups/:id/boards/:board_id
| `board_id` | integer | yes | The ID of a board |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/boards/1
```
Example response:
@@ -131,7 +131,7 @@ GET /groups/:id/boards/:board_id/lists
| `board_id` | integer | yes | The ID of a board |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1/lists
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/boards/1/lists
```
Example response:
@@ -183,7 +183,7 @@ GET /groups/:id/boards/:board_id/lists/:list_id
| `list_id` | integer | yes | The ID of a board's list |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1/lists/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/boards/1/lists/1
```
Example response:
@@ -215,7 +215,7 @@ POST /groups/:id/boards/:board_id/lists
| `label_id` | integer | yes | The ID of a label |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1/lists?label_id=5
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/boards/1/lists?label_id=5
```
Example response:
@@ -248,7 +248,7 @@ PUT /groups/:id/boards/:board_id/lists/:list_id
| `position` | integer | yes | The position of the list |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/group/5/boards/1/lists/1?position=2
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/group/5/boards/1/lists/1?position=2
```
Example response:
@@ -280,5 +280,5 @@ DELETE /groups/:id/boards/:board_id/lists/:list_id
| `list_id` | integer | yes | The ID of a board's list |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/boards/1/lists/1
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/boards/1/lists/1
```
diff --git a/doc/api/group_level_variables.md b/doc/api/group_level_variables.md
index 33c6da08018..3551bfa3f8b 100644
--- a/doc/api/group_level_variables.md
+++ b/doc/api/group_level_variables.md
@@ -15,7 +15,7 @@ GET /groups/:id/variables
| `id` | integer/string | yes | The ID of a group or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/variables"
```
```json
@@ -45,7 +45,7 @@ GET /groups/:id/variables/:key
| `key` | string | yes | The `key` of a variable |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables/TEST_VARIABLE_1"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/variables/TEST_VARIABLE_1"
```
```json
@@ -71,7 +71,7 @@ POST /groups/:id/variables
| `protected` | boolean | no | Whether the variable is protected |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
```
```json
@@ -98,7 +98,7 @@ PUT /groups/:id/variables/:key
| `protected` | boolean | no | Whether the variable is protected |
```
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables/NEW_VARIABLE" --form "value=updated value"
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/variables/NEW_VARIABLE" --form "value=updated value"
```
```json
@@ -123,7 +123,7 @@ DELETE /groups/:id/variables/:key
| `key` | string | yes | The `key` of a variable |
```
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/1/variables/VARIABLE_1"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/variables/VARIABLE_1"
```
[ce-34519]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34519
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index e396f4411e6..7be01ce9c6d 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -26,7 +26,7 @@ Parameters:
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/milestones
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/milestones
```
Example Response:
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 59444a98086..907b443d355 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -220,7 +220,7 @@ Parameters:
| `with_projects` | boolean | no | Include details from projects that belong to the specified group (defaults to `true`). |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/4
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4
```
Example response:
@@ -357,12 +357,14 @@ Example response:
{
"group_id": 4,
"group_name": "Twitter",
+ "group_full_path": "twitter",
"group_access_level": 30,
"expires_at": null
},
{
"group_id": 3,
"group_name": "Gitlab Org",
+ "group_full_path": "gitlab-org",
"group_access_level": 10,
"expires_at": "2018-08-14"
}
@@ -375,7 +377,7 @@ Example response:
When adding the parameter `with_projects=false`, projects will not be returned.
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/4?with_projects=false
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4?with_projects=false
```
Example response:
@@ -452,7 +454,7 @@ PUT /groups/:id
| `file_template_project_id` | integer | no | **(Premium)** The ID of a project to load custom file templates from |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/5?name=Experimental"
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5?name=Experimental"
```
diff --git a/doc/api/import.md b/doc/api/import.md
new file mode 100644
index 00000000000..9f8e0d232c6
--- /dev/null
+++ b/doc/api/import.md
@@ -0,0 +1,33 @@
+# Import API
+
+## Import repository from GitHub
+
+Import your projects from GitHub to GitLab via the API.
+
+```
+POST /import/github
+```
+
+| Attribute | Type | Required | Description |
+|------------|---------|----------|---------------------|
+| `personal_access_token` | string | yes | GitHub personal access token |
+| `repo_id` | integer | yes | GitHub repository ID |
+| `new_name` | string | no | New repo name |
+| `target_namespace` | string | yes | Namespace to import repo into |
+
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "personal_access_token=abc123&repo_id=12345&target_namespace=root" https://gitlab.example.com/api/v4/import/github
+```
+
+Example response:
+
+```json
+{
+ "id": 27,
+ "name": "my-repo",
+ "full_path": "/root/my-repo",
+ "full_name": "Administrator / my-repo"
+}
+```
+
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 6a99c52234d..6d8683601f6 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -52,7 +52,7 @@ GET /issues?my_reaction_emoji=star
| `updated_before` | datetime | no | Return issues updated on or before the given time |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues
```
Example response:
@@ -106,6 +106,8 @@ Example response:
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
"labels" : [],
+ "upvotes": 4,
+ "downvotes": 0,
"user_notes_count": 1,
"due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/6",
@@ -166,7 +168,7 @@ GET /groups/:id/issues?my_reaction_emoji=star
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/4/issues
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4/issues
```
Example response:
@@ -214,6 +216,8 @@ Example response:
"name" : "Dr. Luella Kovacek"
},
"labels" : [],
+ "upvotes": 4,
+ "downvotes": 0,
"id" : 41,
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
@@ -279,7 +283,7 @@ GET /projects/:id/issues?my_reaction_emoji=star
| `updated_before` | datetime | no | Return issues updated on or before the given time |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues
```
Example response:
@@ -327,6 +331,8 @@ Example response:
"name" : "Dr. Luella Kovacek"
},
"labels" : [],
+ "upvotes": 4,
+ "downvotes": 0,
"id" : 41,
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
@@ -373,7 +379,7 @@ GET /projects/:id/issues/:issue_iid
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/41
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues/41
```
Example response:
@@ -421,6 +427,8 @@ Example response:
"name" : "Dr. Luella Kovacek"
},
"labels" : [],
+ "upvotes": 4,
+ "downvotes": 0,
"id" : 41,
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
@@ -476,7 +484,7 @@ POST /projects/:id/issues
| `discussion_to_resolve` | string | no | The ID of a discussion to resolve. This will fill in the issue with a default description and mark the discussion as resolved. Use in combination with `merge_request_to_resolve_discussions_of`. |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues?title=Issues%20with%20auth&labels=bug
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues?title=Issues%20with%20auth&labels=bug
```
Example response:
@@ -494,6 +502,8 @@ Example response:
"labels" : [
"bug"
],
+ "upvotes": 4,
+ "downvotes": 0,
"author" : {
"name" : "Alexandra Bashirian",
"avatar_url" : null,
@@ -558,7 +568,7 @@ PUT /projects/:id/issues/:issue_iid
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close
```
Example response:
@@ -592,6 +602,8 @@ Example response:
"labels" : [
"bug"
],
+ "upvotes": 4,
+ "downvotes": 0,
"id" : 85,
"assignees" : [],
"assignee" : null,
@@ -635,7 +647,7 @@ DELETE /projects/:id/issues/:issue_iid
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues/85
```
## Move an issue
@@ -658,7 +670,7 @@ POST /projects/:id/issues/:issue_iid/move
| `to_project_id` | integer | yes | The ID of the new project |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form to_project_id=5 https://gitlab.example.com/api/v4/projects/4/issues/85/move
+curl --header "PRIVATE-TOKEN: <your_access_token>" --form to_project_id=5 https://gitlab.example.com/api/v4/projects/4/issues/85/move
```
Example response:
@@ -676,6 +688,8 @@ Example response:
"closed_at": null,
"closed_by": null,
"labels": [],
+ "upvotes": 4,
+ "downvotes": 0,
"milestone": null,
"assignees": [{
"name": "Miss Monserrate Beier",
@@ -740,7 +754,7 @@ POST /projects/:id/issues/:issue_iid/subscribe
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/subscribe
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/subscribe
```
Example response:
@@ -758,6 +772,8 @@ Example response:
"closed_at": null,
"closed_by": null,
"labels": [],
+ "upvotes": 4,
+ "downvotes": 0,
"milestone": null,
"assignees": [{
"name": "Miss Monserrate Beier",
@@ -823,7 +839,7 @@ POST /projects/:id/issues/:issue_iid/unsubscribe
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/unsubscribe
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/unsubscribe
```
Example response:
@@ -839,6 +855,8 @@ Example response:
"created_at": "2016-04-05T21:41:45.217Z",
"updated_at": "2016-04-07T13:02:37.905Z",
"labels": [],
+ "upvotes": 4,
+ "downvotes": 0,
"milestone": null,
"assignee": {
"name": "Edwardo Grady",
@@ -882,7 +900,7 @@ POST /projects/:id/issues/:issue_iid/todo
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/todo
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/todo
```
Example response:
@@ -988,7 +1006,7 @@ POST /projects/:id/issues/:issue_iid/time_estimate
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_estimate?duration=3h30m
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/time_estimate?duration=3h30m
```
Example response:
@@ -1016,7 +1034,7 @@ POST /projects/:id/issues/:issue_iid/reset_time_estimate
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_time_estimate
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_time_estimate
```
Example response:
@@ -1045,7 +1063,7 @@ POST /projects/:id/issues/:issue_iid/add_spent_time
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/add_spent_time?duration=1h
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/add_spent_time?duration=1h
```
Example response:
@@ -1073,7 +1091,7 @@ POST /projects/:id/issues/:issue_iid/reset_spent_time
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_spent_time
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/reset_spent_time
```
Example response:
@@ -1099,7 +1117,7 @@ GET /projects/:id/issues/:issue_iid/time_stats
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/time_stats
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/time_stats
```
Example response:
@@ -1127,7 +1145,7 @@ GET /projects/:id/issues/:issue_id/related_merge_requests
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```sh
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/11/related_merge_requests
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/11/related_merge_requests
```
Example response:
@@ -1214,7 +1232,7 @@ GET /projects/:id/issues/:issue_iid/closed_by
| `issue_iid` | integer | yes | The internal ID of a project issue |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/issues/11/closed_by
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/11/closed_by
```
Example response:
@@ -1281,7 +1299,7 @@ GET /projects/:id/issues/:issue_iid/participants
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/participants
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/participants
```
Example response:
@@ -1326,7 +1344,7 @@ GET /projects/:id/issues/:issue_iid/user_agent_detail
| `issue_iid` | integer | yes | The internal ID of a project's issue |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/user_agent_detail
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/93/user_agent_detail
```
Example response:
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index d2dd9c676e3..085e321b35f 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -14,7 +14,7 @@ GET /projects/:id/jobs
| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
```sh
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope[]=pending&scope[]=running'
+curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope[]=pending&scope[]=running'
```
Example of response
@@ -147,7 +147,7 @@ GET /projects/:id/pipelines/:pipeline_id/jobs
| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
```sh
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/pipelines/6/jobs?scope[]=pending&scope[]=running'
+curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/1/pipelines/6/jobs?scope[]=pending&scope[]=running'
```
Example of response
@@ -279,7 +279,7 @@ GET /projects/:id/jobs/:job_id
| `job_id` | integer | yes | The ID of a job. |
```sh
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8"
```
Example of response
@@ -356,7 +356,7 @@ GET /projects/:id/jobs/:job_id/artifacts
Example requests:
```sh
-curl --location --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts"
+curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts"
```
Possible response status codes:
@@ -392,7 +392,7 @@ Parameters
Example requests:
```sh
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
```
Possible response status codes:
@@ -427,7 +427,7 @@ Parameters
Example request:
```sh
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/5/artifacts/some/release/file.pdf"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/5/artifacts/some/release/file.pdf"
```
Possible response status codes:
@@ -462,7 +462,7 @@ Parameters:
Example request:
```sh
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/raw/some/release/file.pdf?job=pdf"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/raw/some/release/file.pdf?job=pdf"
```
Possible response status codes:
@@ -487,7 +487,7 @@ GET /projects/:id/jobs/:job_id/trace
| job_id | integer | yes | The ID of a job. |
```sh
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace"
```
Possible response status codes:
@@ -511,7 +511,7 @@ POST /projects/:id/jobs/:job_id/cancel
| `job_id` | integer | yes | The ID of a job. |
```sh
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/cancel"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/cancel"
```
Example of response
@@ -559,7 +559,7 @@ POST /projects/:id/jobs/:job_id/retry
| `job_id` | integer | yes | The ID of a job. |
```sh
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/retry"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/retry"
```
Example of response
@@ -611,7 +611,7 @@ Parameters
Example of request
```sh
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/erase"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/erase"
```
Example of response
@@ -664,7 +664,7 @@ Parameters
Example request:
```sh
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts/keep"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts/keep"
```
Example response:
@@ -713,7 +713,7 @@ POST /projects/:id/jobs/:job_id/play
| `job_id` | integer | yes | The ID of a job. |
```sh
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/play"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/play"
```
Example of response
diff --git a/doc/api/labels.md b/doc/api/labels.md
index ec93cf50e7a..aec1a2c7592 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -13,7 +13,7 @@ GET /projects/:id/labels
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/labels
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels
```
Example response:
@@ -95,7 +95,7 @@ POST /projects/:id/labels
| `priority` | integer | no | The priority of the label. Must be greater or equal than zero or `null` to remove the priority. |
```bash
-curl --data "name=feature&color=#5843AD" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/labels"
+curl --data "name=feature&color=#5843AD" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels"
```
Example response:
@@ -128,7 +128,7 @@ DELETE /projects/:id/labels
| `name` | string | yes | The name of the label |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/labels?name=bug"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels?name=bug"
```
## Edit an existing label
@@ -151,7 +151,7 @@ PUT /projects/:id/labels
```bash
-curl --request PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/labels"
+curl --request PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels"
```
Example response:
@@ -186,7 +186,7 @@ POST /projects/:id/labels/:label_id/subscribe
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/labels/1/subscribe
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/labels/1/subscribe
```
Example response:
@@ -221,5 +221,5 @@ POST /projects/:id/labels/:label_id/unsubscribe
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/labels/1/unsubscribe
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/labels/1/unsubscribe
```
diff --git a/doc/api/lint.md b/doc/api/lint.md
index c37a8bff749..a0307f7081d 100644
--- a/doc/api/lint.md
+++ b/doc/api/lint.md
@@ -20,7 +20,7 @@ Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very pic
Example responses:
-* Valid content:
+- Valid content:
```json
{
@@ -29,7 +29,7 @@ Example responses:
}
```
-* Invalid content:
+- Invalid content:
```json
{
@@ -40,7 +40,7 @@ Example responses:
}
```
-* Without the content attribute:
+- Without the content attribute:
```json
{
@@ -49,4 +49,3 @@ Example responses:
```
[ce-5953]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5953
-
diff --git a/doc/api/members.md b/doc/api/members.md
index bb4fae35f52..0593d2c20ea 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -28,8 +28,8 @@ GET /projects/:id/members
| `query` | string | no | A query string to search for members |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/members
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/members
```
Example response:
@@ -75,8 +75,8 @@ GET /projects/:id/members/all
| `query` | string | no | A query string to search for members |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members/all
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members/all
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/members/all
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/members/all
```
Example response:
@@ -131,8 +131,8 @@ GET /projects/:id/members/:user_id
| `user_id` | integer | yes | The user ID of the member |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members/:user_id
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members/:user_id
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/members/:user_id
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/members/:user_id
```
Example response:
@@ -167,8 +167,8 @@ POST /projects/:id/members
| `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v4/groups/:id/members
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v4/projects/:id/members
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v4/groups/:id/members
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v4/projects/:id/members
```
Example response:
@@ -203,8 +203,8 @@ PUT /projects/:id/members/:user_id
| `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members/:user_id?access_level=40
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members/:user_id?access_level=40
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/members/:user_id?access_level=40
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/members/:user_id?access_level=40
```
Example response:
@@ -237,8 +237,8 @@ DELETE /projects/:id/members/:user_id
| `user_id` | integer | yes | The user ID of the member |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/members/:user_id
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/members/:user_id
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/members/:user_id
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/members/:user_id
```
## Give a group access to a project
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 9ff6c73b1b6..c9b271eada3 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -967,7 +967,7 @@ DELETE /projects/:id/merge_requests/:merge_request_iid
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/merge_requests/85
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/merge_requests/85
```
## Accept MR
@@ -1230,7 +1230,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid/rebase
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/rebase
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/rebase
```
This is an asynchronous request. The API will return an empty `202 Accepted`
@@ -1286,7 +1286,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/closes_issues
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/closes_issues
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/76/merge_requests/1/closes_issues
```
Example response when the GitLab issue tracker is used:
@@ -1362,7 +1362,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/subscribe
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/subscribe
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/subscribe
```
Example response:
@@ -1483,7 +1483,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/unsubscribe
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/unsubscribe
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/17/unsubscribe
```
Example response:
@@ -1604,7 +1604,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/todo
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/27/todo
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/27/todo
```
Example response:
@@ -1707,7 +1707,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/versions
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions
```
Example response:
@@ -1749,7 +1749,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/versions/:version_id
| `version_id` | integer | yes | The ID of the merge request diff version |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/merge_requests/1/versions/1
```
Example response:
@@ -1816,7 +1816,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/time_estimate
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_estimate?duration=3h30m
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_estimate?duration=3h30m
```
Example response:
@@ -1844,7 +1844,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/reset_time_estimate
| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_time_estimate
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_time_estimate
```
Example response:
@@ -1873,7 +1873,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/add_spent_time
| `duration` | string | yes | The duration in human format. e.g: 3h30m |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/add_spent_time?duration=1h
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/add_spent_time?duration=1h
```
Example response:
@@ -1901,7 +1901,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/reset_spent_time
| `merge_request_iid` | integer | yes | The internal ID of a project's merge_request |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_spent_time
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/reset_spent_time
```
Example response:
@@ -1927,7 +1927,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/time_stats
| `merge_request_iid` | integer | yes | The internal ID of the merge request |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_stats
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/93/time_stats
```
Example response:
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 7ac97edc7ae..fa8f8a0bcf0 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -23,7 +23,7 @@ Parameters:
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/milestones
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/milestones
```
Example Response:
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index 656bf07bb55..b8bc4c40124 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -19,7 +19,7 @@ GET /namespaces
Example request:
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/namespaces
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/namespaces
```
Example response:
@@ -71,7 +71,7 @@ GET /namespaces?search=foobar
Example request:
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/namespaces?search=twitter
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/namespaces?search=twitter
```
Example response:
@@ -105,7 +105,7 @@ GET /namespaces/:id
Example request:
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/namespaces/2
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/namespaces/2
```
Example response:
@@ -125,7 +125,7 @@ Example response:
Example request:
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/namespaces/group1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/namespaces/group1
```
Example response:
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 9f6740ad86a..dd4e18b14e6 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -66,7 +66,7 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at
```
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/notes
```
### Get single issue note
@@ -84,7 +84,7 @@ Parameters:
- `note_id` (required) - The ID of an issue note
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes/1
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/notes/1
```
### Create new issue note
@@ -103,7 +103,7 @@ Parameters:
- `created_at` (optional) - 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: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note
```
### Modify existing issue note
@@ -122,7 +122,7 @@ Parameters:
- `body` (required) - The content of a note
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note
```
### Delete an issue note
@@ -142,7 +142,7 @@ Parameters:
| `note_id` | integer | yes | The ID of a note |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/notes/636
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/notes/636
```
## Snippets
@@ -164,7 +164,7 @@ GET /projects/:id/snippets/:snippet_id/notes?sort=asc&order_by=updated_at
| `order_by` | string | no | Return snippet notes ordered by `created_at` or `updated_at` fields. Default is `created_at`
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes
```
### Get single snippet note
@@ -201,7 +201,7 @@ Parameters:
```
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes/11
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes/11
```
### Create new snippet note
@@ -221,7 +221,7 @@ Parameters:
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
```
### Modify existing snippet note
@@ -240,7 +240,7 @@ Parameters:
- `body` (required) - The content of a note
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes?body=note
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes?body=note
```
### Delete a snippet note
@@ -260,7 +260,7 @@ Parameters:
| `note_id` | integer | yes | The ID of a note |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/52/notes/1659
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/52/notes/1659
```
## Merge Requests
@@ -282,7 +282,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/notes?sort=asc&order_by=upda
| `order_by` | string | no | Return merge request notes ordered by `created_at` or `updated_at` fields. Default is `created_at`
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes
```
### Get single merge request note
@@ -323,7 +323,7 @@ Parameters:
```
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes/1
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes/1
```
### Create new merge request note
@@ -359,7 +359,7 @@ Parameters:
- `body` (required) - The content of a note
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes?body=note
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes?body=note
```
### Delete a merge request note
@@ -379,5 +379,5 @@ Parameters:
| `note_id` | integer | yes | The ID of a note |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/7/notes/1602
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/7/notes/1602
```
diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md
index 165b9a11c7a..e21e73c7dac 100644
--- a/doc/api/notification_settings.md
+++ b/doc/api/notification_settings.md
@@ -43,7 +43,7 @@ GET /notification_settings
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/notification_settings
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/notification_settings
```
Example response:
@@ -64,7 +64,7 @@ PUT /notification_settings
```
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/notification_settings?level=watch
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/notification_settings?level=watch
```
| Attribute | Type | Required | Description |
@@ -105,8 +105,8 @@ GET /projects/:id/notification_settings
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/notification_settings
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/8/notification_settings
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/notification_settings
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/8/notification_settings
```
| Attribute | Type | Required | Description |
@@ -131,8 +131,8 @@ PUT /projects/:id/notification_settings
```
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/notification_settings?level=watch
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/8/notification_settings?level=custom&new_note=true
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/notification_settings?level=watch
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/8/notification_settings?level=custom&new_note=true
```
| Attribute | Type | Required | Description |
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 8a1e6b52092..6e156a14b25 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -1,126 +1,165 @@
# GitLab as an OAuth2 provider
-This document covers using the [OAuth2](https://oauth.net/2/) protocol to allow other services access GitLab resources on user's behalf.
+This document covers using the [OAuth2](https://oauth.net/2/) protocol to allow
+other services to access GitLab resources on user's behalf.
-If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [OAuth2 provider](../integration/oauth_provider.md)
-documentation.
+If you want GitLab to be an OAuth authentication service provider to sign into
+other services, see the [OAuth2 provider](../integration/oauth_provider.md)
+documentation. This functionality is based on the
+[doorkeeper Ruby gem](https://github.com/doorkeeper-gem/doorkeeper).
-This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper).
+## Supported OAuth2 flows
-## Supported OAuth2 Flows
+GitLab currently supports the following authorization flows:
-GitLab currently supports following authorization flows:
+- **Web application flow:** Most secure and common type of flow, designed for
+ applications with secure server-side.
+- **Implicit grant flow:** This flow is designed for user-agent only apps (e.g., single
+ page web application running on GitLab Pages).
+- **Resource owner password credentials flow:** To be used **only** for securely
+ hosted, first-party services.
-* *Web Application Flow* - Most secure and common type of flow, designed for the applications with secure server-side.
-* *Implicit Flow* - This flow is designed for user-agent only apps (e.g. single page web application running on GitLab Pages).
-* *Resource Owner Password Credentials Flow* - To be used **only** for securely hosted, first-party services.
+Refer to the [OAuth RFC](https://tools.ietf.org/html/rfc6749) to find out
+how all those flows work and pick the right one for your use case.
-Please refer to [OAuth RFC](https://tools.ietf.org/html/rfc6749) to find out in details how all those flows work and pick the right one for your use case.
+Both **web application** and **implicit grant** flows require `application` to be
+registered first via the `/profile/applications` page in your user's account.
+During registration, by enabling proper scopes, you can limit the range of
+resources which the `application` can access. Upon creation, you'll obtain the
+`application` credentials: _Application ID_ and _Client Secret_ - **keep them secure**.
-Both *web application* and *implicit* flows require `application` to be registered first via `/profile/applications` page
-in your user's account. During registration, by enabling proper scopes you can limit the range of resources which the `application` can access. Upon creation
-you'll obtain `application` credentials: _Application ID_ and _Client Secret_ - **keep them secure**.
+CAUTION: **Important:**
+OAuth specification advises sending the `state` parameter with each request to
+`/oauth/authorize`. We highly recommended sending a unique value with each request
+and validate it against the one in the redirect request. This is important in
+order to prevent [CSRF attacks](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)).
+The `state` parameter really should have been a requirement in the standard!
->**Important:** OAuth specification advises sending `state` parameter with each request to `/oauth/authorize`. We highly recommended to send a unique
-value with each request and validate it against the one in redirect request. This is important to prevent [CSRF attacks]. The `state` param really should
-have been a requirement in the standard!
+In the following sections you will find detailed instructions on how to obtain
+authorization with each flow.
-In the following sections you will find detailed instructions on how to obtain authorization with each flow.
+### Web application flow
-### Web Application Flow
+NOTE: **Note:**
+Check the [RFC spec](https://tools.ietf.org/html/rfc6749#section-4.1) for a
+detailed flow description.
-Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.1) for a detailed flow description
+The web application flow is:
-#### 1. Requesting authorization code
+1. Request authorization code. To do that, you should redirect the user to the
+ `/oauth/authorize` endpoint with the following GET parameters:
-To request the authorization code, you should redirect the user to the `/oauth/authorize` endpoint with following GET parameters:
+ ```
+ https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH
+ ```
-```
-https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH
-```
+ This will ask the user to approve the applications access to their account and
+ then redirect back to the `REDIRECT_URI` you provided. The redirect will
+ include the GET `code` parameter, for example:
-This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect will
-include the GET `code` parameter, for example:
+ ```
+ http://myapp.com/oauth/redirect?code=1234567890&state=YOUR_UNIQUE_STATE_HASH
+ ```
-`http://myapp.com/oauth/redirect?code=1234567890&state=YOUR_UNIQUE_STATE_HASH`
+ You should then use `code` to request an access token.
-You should then use the `code` to request an access token.
+1. Once you have the authorization code you can request an `access_token` using the
+ code. You can do that by using any HTTP client. In the following example,
+ we are using Ruby's `rest-client`:
-#### 2. Requesting access token
+ ```ruby
+ parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
+ RestClient.post 'http://gitlab.example.com/oauth/token', parameters
+ ```
-Once you have the authorization code you can request an `access_token` using the code, to do that you can use any HTTP client. In the following example,
-we are using Ruby's `rest-client`:
+ Example response:
-```
-parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
-RestClient.post 'http://gitlab.example.com/oauth/token', parameters
+ ```json
+ {
+ "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
+ "token_type": "bearer",
+ "expires_in": 7200,
+ "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
+ }
+ ```
-# The response will be
-{
- "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
- "token_type": "bearer",
- "expires_in": 7200,
- "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
-}
-```
->**Note:**
-The `redirect_uri` must match the `redirect_uri` used in the original authorization request.
+NOTE: **Note:**
+The `redirect_uri` must match the `redirect_uri` used in the original
+authorization request.
You can now make requests to the API with the access token returned.
+### Implicit grant flow
-### Implicit Grant
-
-Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.2) for a detailed flow description.
+NOTE: **Note:**
+Check the [RFC spec](https://tools.ietf.org/html/rfc6749#section-4.2) for a
+detailed flow description.
-Unlike the web flow, the client receives an `access token` immediately as a result of the authorization request. The flow does not use client secret
-or authorization code because all of the application code and storage is easily accessible, therefore __secrets__ can leak easily.
+CAUTION: **Important:**
+Avoid using this flow for applications that store data outside of the GitLab
+instance. If you do, make sure to verify `application id` associated with the
+access token before granting access to the data
+(see [/oauth/token/info](https://github.com/doorkeeper-gem/doorkeeper/wiki/API-endpoint-descriptions-and-examples#get----oauthtokeninfo)).
->**Important:** Avoid using this flow for applications that store data outside of the GitLab instance. If you do, make sure to verify `application id`
-associated with access token before granting access to the data
-(see [/oauth/token/info](https://github.com/doorkeeper-gem/doorkeeper/wiki/API-endpoint-descriptions-and-examples#get----oauthtokeninfo)).
-
+Unlike the web flow, the client receives an `access token` immediately as a
+result of the authorization request. The flow does not use the client secret
+or the authorization code because all of the application code and storage is
+easily accessible, therefore secrets can leak easily.
-#### 1. Requesting access token
-
-To request the access token, you should redirect the user to the `/oauth/authorize` endpoint using `token` response type:
+To request the access token, you should redirect the user to the
+`/oauth/authorize` endpoint using `token` response type:
```
https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH
```
-This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect
-will include a fragment with `access_token` as well as token details in GET parameters, for example:
+This will ask the user to approve the application's access to their account and
+then redirect them back to the `REDIRECT_URI` you provided. The redirect
+will include a fragment with `access_token` as well as token details in GET
+parameters, for example:
```
http://myapp.com/oauth/redirect#access_token=ABCDExyz123&state=YOUR_UNIQUE_STATE_HASH&token_type=bearer&expires_in=3600
```
-### Resource Owner Password Credentials
+### Resource owner password credentials flow
-Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.3) for a detailed flow description.
+NOTE: **Note:**
+Check the [RFC spec](https://tools.ietf.org/html/rfc6749#section-4.3) for a
+detailed flow description.
-> **Deprecation notice:** Starting in GitLab 8.11, the Resource Owner Password Credentials has been *disabled* for users with two-factor authentication
-turned on. These users can access the API using [personal access tokens] instead.
+NOTE: **Note:**
+The Resource Owner Password Credentials is disabled for users with [two-factor
+authentication](../user/profile/account/two_factor_authentication.md) turned on.
+These users can access the API using [personal access tokens](../user/profile/personal_access_tokens.md)
+instead.
-In this flow, a token is requested in exchange for the resource owner credentials (username and password).
-The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the
-client is part of the device operating system or a highly privileged application), and when other authorization grant types are not
-available (such as an authorization code).
+In this flow, a token is requested in exchange for the resource owner credentials
+(username and password).
->**Important:**
-Never store the users credentials and only use this grant type when your client is deployed to a trusted environment, in 99% of cases [personal access tokens]
-are a better choice.
+The credentials should only be used when:
-Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used
-for a single request and are exchanged for an access token. This grant type can eliminate the need for the client to store the
-resource owner credentials for future use, by exchanging the credentials with a long-lived access token or refresh token.
+- There is a high degree of trust between the resource owner and the client. For
+ example, the client is part of the device operating system or a highly
+ privileged application.
+- Other authorization grant types are not available (such as an authorization code).
-#### 1. Requesting access token
+CAUTION: **Important:**
+Never store the user's credentials and only use this grant type when your client
+is deployed to a trusted environment, in 99% of cases
+[personal access tokens](../user/profile/personal_access_tokens.md) are a better
+choice.
-POST request to `/oauth/token` with parameters:
+Even though this grant type requires direct client access to the resource owner
+credentials, the resource owner credentials are used for a single request and
+are exchanged for an access token. This grant type can eliminate the need for
+the client to store the resource owner credentials for future use, by exchanging
+the credentials with a long-lived access token or refresh token.
-```
+To request an access token, you must make a POST request to `/oauth/token` with
+the following parameters:
+
+```json
{
"grant_type" : "password",
"username" : "user@example.com",
@@ -128,6 +167,13 @@ POST request to `/oauth/token` with parameters:
}
```
+Example cURL request:
+
+```sh
+echo 'grant_type=password&username=<your_username>&password=<your_password>' > auth.txt
+curl --data "@auth.txt" --request POST https://gitlab.example.com/oauth/token
+```
+
Then, you'll receive the access token back in the response:
```
@@ -138,7 +184,7 @@ Then, you'll receive the access token back in the response:
}
```
-For testing you can use the oauth2 ruby gem:
+For testing, you can use the `oauth2` Ruby gem:
```
client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com")
@@ -148,7 +194,9 @@ puts access_token.token
## Access GitLab API with `access token`
-The `access token` allows you to make requests to the API on a behalf of a user. You can pass the token either as GET parameter
+The `access token` allows you to make requests to the API on behalf of a user.
+You can pass the token either as GET parameter:
+
```
GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN
```
@@ -159,5 +207,3 @@ or you can put the token to the Authorization header:
curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/user
```
-[personal access tokens]: ../user/profile/personal_access_tokens.md
-[CSRF attacks]: http://www.oauthsecurity.com/#user-content-authorization-code-flow \ No newline at end of file
diff --git a/doc/api/pages_domains.md b/doc/api/pages_domains.md
index da2ffcfe40a..4c41350dcdb 100644
--- a/doc/api/pages_domains.md
+++ b/doc/api/pages_domains.md
@@ -13,7 +13,7 @@ GET /pages/domains
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/pages/domains
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/pages/domains
```
```json
@@ -43,7 +43,7 @@ GET /projects/:id/pages/domains
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/pages/domains
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/pages/domains
```
```json
@@ -79,7 +79,7 @@ GET /projects/:id/pages/domains/:domain
| `domain` | string | yes | The domain |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/pages/domains/www.domain.example
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/pages/domains/www.domain.example
```
```json
@@ -90,7 +90,7 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/a
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example
```
```json
@@ -122,11 +122,11 @@ POST /projects/:id/pages/domains
| `key` | file/string | no | The certificate key in PEM format. |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "domain=ssl.domain.example" --form "certificate=@/path/to/cert.pem" --form "key=@/path/to/key.pem" https://gitlab.example.com/api/v4/projects/5/pages/domains
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "domain=ssl.domain.example" --form "certificate=@/path/to/cert.pem" --form "key=@/path/to/key.pem" https://gitlab.example.com/api/v4/projects/5/pages/domains
```
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "domain=ssl.domain.example" --form "certificate=$CERT_PEM" --form "key=$KEY_PEM" https://gitlab.example.com/api/v4/projects/5/pages/domains
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "domain=ssl.domain.example" --form "certificate=$CERT_PEM" --form "key=$KEY_PEM" https://gitlab.example.com/api/v4/projects/5/pages/domains
```
```json
@@ -158,11 +158,11 @@ PUT /projects/:id/pages/domains/:domain
| `key` | file/string | no | The certificate key in PEM format. |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "certificate=@/path/to/cert.pem" --form "key=@/path/to/key.pem" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form "certificate=@/path/to/cert.pem" --form "key=@/path/to/key.pem" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example
```
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "certificate=$CERT_PEM" --form "key=$KEY_PEM" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form "certificate=$CERT_PEM" --form "key=$KEY_PEM" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example
```
```json
@@ -192,5 +192,5 @@ DELETE /projects/:id/pages/domains/:domain
| `domain` | string | yes | The domain |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/pages/domains/ssl.domain.example
```
diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md
index e881e61d4ef..736312df116 100644
--- a/doc/api/pipeline_triggers.md
+++ b/doc/api/pipeline_triggers.md
@@ -15,7 +15,7 @@ GET /projects/:id/triggers
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
@@ -46,7 +46,7 @@ GET /projects/:id/triggers/:trigger_id
| `trigger_id` | integer | yes | The trigger id |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
```
```json
@@ -75,7 +75,7 @@ POST /projects/:id/triggers
| `description` | string | yes | The trigger name |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
@@ -105,7 +105,7 @@ PUT /projects/:id/triggers/:trigger_id
| `description` | string | no | The trigger name |
```
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers/10"
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers/10"
```
```json
@@ -134,7 +134,7 @@ POST /projects/:id/triggers/:trigger_id/take_ownership
| `trigger_id` | integer | yes | The trigger id |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/10/take_ownership"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/triggers/10/take_ownership"
```
```json
@@ -163,5 +163,5 @@ DELETE /projects/:id/triggers/:trigger_id
| `trigger_id` | integer | yes | The trigger id |
```
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
```
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index 7b4c9a8fbb3..43bbf463c8d 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -22,7 +22,7 @@ GET /projects/:id/pipelines
| `sort` | string | no | Sort pipelines in `asc` or `desc` order (default: `desc`) |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipelines"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines"
```
Example of response
@@ -60,7 +60,7 @@ GET /projects/:id/pipelines/:pipeline_id
| `pipeline_id` | integer | yes | The ID of a pipeline |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipelines/46"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/46"
```
Example of response
@@ -108,7 +108,7 @@ POST /projects/:id/pipeline
| `variables` | array | no | An array containing the variables available in the pipeline, matching the structure [{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }] |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipeline?ref=master"
```
Example of response
@@ -155,7 +155,7 @@ POST /projects/:id/pipelines/:pipeline_id/retry
| `pipeline_id` | integer | yes | The ID of a pipeline |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/retry"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/retry"
```
Response:
@@ -202,7 +202,7 @@ POST /projects/:id/pipelines/:pipeline_id/cancel
| `pipeline_id` | integer | yes | The ID of a pipeline |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/cancel"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/cancel"
```
Response:
@@ -249,7 +249,7 @@ DELETE /projects/:id/pipelines/:pipeline_id
| `pipeline_id` | integer | yes | The ID of a pipeline |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --request "DELETE" "https://gitlab.example.com/api/v4/projects/1/pipelines/46"
+curl --header "PRIVATE-TOKEN: <your_access_token>" --request "DELETE" "https://gitlab.example.com/api/v4/projects/1/pipelines/46"
```
[ce-5837]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5837
diff --git a/doc/api/project_badges.md b/doc/api/project_badges.md
index 94389273e9c..3a7b3d8975e 100644
--- a/doc/api/project_badges.md
+++ b/doc/api/project_badges.md
@@ -25,7 +25,7 @@ GET /projects/:id/badges
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/badges
```
Example response:
@@ -65,7 +65,7 @@ GET /projects/:id/badges/:badge_id
| `badge_id` | integer | yes | The badge ID |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
```
Example response:
@@ -96,7 +96,7 @@ POST /projects/:id/badges
| `image_url` | string | yes | URL of the badge image |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/projects/:id/badges
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/projects/:id/badges
```
Example response:
@@ -128,7 +128,7 @@ PUT /projects/:id/badges/:badge_id
| `image_url` | string | no | URL of the badge image |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
```
Example response:
@@ -158,7 +158,7 @@ DELETE /projects/:id/badges/:badge_id
| `badge_id` | integer | yes | The badge ID |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id
```
## Preview a badge from a project
@@ -176,7 +176,7 @@ GET /projects/:id/badges/render
| `image_url` | string | yes | URL of the badge image |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge
```
Example response:
diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md
new file mode 100644
index 00000000000..c51a3564211
--- /dev/null
+++ b/doc/api/project_clusters.md
@@ -0,0 +1,346 @@
+# Project clusters API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23922)
+in GitLab 11.7.
+
+NOTE: **Note:**
+User will need at least maintainer access to use these endpoints.
+
+## List project clusters
+
+Returns a list of project clusters.
+
+```
+GET /projects/:id/clusters
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters
+```
+
+Example response:
+
+```json
+[
+ {
+ "id":18,
+ "name":"cluster-1",
+ "created_at":"2019-01-02T20:18:12.563Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"project_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://104.197.68.152",
+ "namespace":"cluster-1-namespace",
+ "authorization_type":"rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
+ }
+ },
+ {
+ "id":19,
+ "name":"cluster-2",
+ ...
+ }
+]
+```
+
+## Get a single project cluster
+
+Gets a single project cluster.
+
+```bash
+GET /projects/:id/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+| `cluster_id` | integer | yes | The ID of the cluster |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/18
+```
+
+Example response:
+
+```json
+{
+ "id":18,
+ "name":"cluster-1",
+ "created_at":"2019-01-02T20:18:12.563Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"project_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://104.197.68.152",
+ "namespace":"cluster-1-namespace",
+ "authorization_type":"rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
+ },
+ "project":
+ {
+ "id":26,
+ "description":"",
+ "name":"project-with-clusters-api",
+ "name_with_namespace":"Administrator / project-with-clusters-api",
+ "path":"project-with-clusters-api",
+ "path_with_namespace":"root/project-with-clusters-api",
+ "created_at":"2019-01-02T20:13:32.600Z",
+ "default_branch":null,
+ "tag_list":[],
+ "ssh_url_to_repo":"ssh://gitlab.example.com/root/project-with-clusters-api.git",
+ "http_url_to_repo":"https://gitlab.example.com/root/project-with-clusters-api.git",
+ "web_url":"https://gitlab.example.com/root/project-with-clusters-api",
+ "readme_url":null,
+ "avatar_url":null,
+ "star_count":0,
+ "forks_count":0,
+ "last_activity_at":"2019-01-02T20:13:32.600Z",
+ "namespace":
+ {
+ "id":1,
+ "name":"root",
+ "path":"root",
+ "kind":"user",
+ "full_path":"root",
+ "parent_id":null
+ }
+ }
+}
+```
+
+## Add existing cluster to project
+
+Adds an existing Kubernetes cluster to the project.
+
+```bash
+POST /projects/:id/clusters/user
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+| `name` | String | yes | The name of the cluster |
+| `enabled` | Boolean | no | Determines if cluster is active or not, defaults to true |
+| `platform_kubernetes_attributes[api_url]` | String | yes | The URL to access the Kubernetes API |
+| `platform_kubernetes_attributes[token]` | String | yes | The token to authenticate against Kubernetes |
+| `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`. |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/user \
+-H "Accept: application/json" \
+-H "Content-Type:application/json" \
+-X POST --data '{"name":"cluster-5", "platform_kubernetes_attributes":{"api_url":"https://35.111.51.20","token":"12345","namespace":"cluster-5-namespace","ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"}}'
+```
+
+Example response:
+
+```json
+{
+ "id":24,
+ "name":"cluster-5",
+ "created_at":"2019-01-03T21:53:40.610Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"project_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://35.111.51.20",
+ "namespace":"cluster-5-namespace",
+ "authorization_type":"rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
+ },
+ "project":
+ {
+ "id":26,
+ "description":"",
+ "name":"project-with-clusters-api",
+ "name_with_namespace":"Administrator / project-with-clusters-api",
+ "path":"project-with-clusters-api",
+ "path_with_namespace":"root/project-with-clusters-api",
+ "created_at":"2019-01-02T20:13:32.600Z",
+ "default_branch":null,
+ "tag_list":[],
+ "ssh_url_to_repo":"ssh:://gitlab.example.com/root/project-with-clusters-api.git",
+ "http_url_to_repo":"https://gitlab.example.com/root/project-with-clusters-api.git",
+ "web_url":"https://gitlab.example.com/root/project-with-clusters-api",
+ "readme_url":null,
+ "avatar_url":null,
+ "star_count":0,
+ "forks_count":0,
+ "last_activity_at":"2019-01-02T20:13:32.600Z",
+ "namespace":
+ {
+ "id":1,
+ "name":"root",
+ "path":"root",
+ "kind":"user",
+ "full_path":"root",
+ "parent_id":null
+ }
+ }
+}
+```
+
+## Edit project cluster
+
+Updates an existing project cluster.
+
+```bash
+PUT /projects/:id/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+| `name` | String | no | The name of the cluster |
+| `platform_kubernetes_attributes[api_url]` | String | no | The URL to access the Kubernetes API |
+| `platform_kubernetes_attributes[token]` | String | no | The token to authenticate against Kubernetes |
+| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
+| `platform_kubernetes_attributes[namespace]` | String | no | The unique namespace related to the project |
+
+NOTE: **Note:**
+`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
+through the ["Add an existing Kubernetes Cluster"](../user/project/clusters/index.md#adding-an-existing-kubernetes-cluster) option or
+through the ["Add existing cluster to project"](#add-existing-cluster-to-project) endpoint.
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/24 \
+-H "Content-Type:application/json" \
+-X PUT --data '{"name":"new-cluster-name","api_url":"https://new-api-url.com"}'
+```
+
+Example response:
+
+```json
+{
+ "id":24,
+ "name":"new-cluster-name",
+ "created_at":"2019-01-03T21:53:40.610Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"project_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://new-api-url.com",
+ "namespace":"cluster-5-namespace",
+ "authorization_type":"rbac",
+ "ca_cert":null
+ },
+ "project":
+ {
+ "id":26,
+ "description":"",
+ "name":"project-with-clusters-api",
+ "name_with_namespace":"Administrator / project-with-clusters-api",
+ "path":"project-with-clusters-api",
+ "path_with_namespace":"root/project-with-clusters-api",
+ "created_at":"2019-01-02T20:13:32.600Z",
+ "default_branch":null,
+ "tag_list":[],
+ "ssh_url_to_repo":"ssh:://gitlab.example.com/root/project-with-clusters-api.git",
+ "http_url_to_repo":"https://gitlab.example.com/root/project-with-clusters-api.git",
+ "web_url":"https://gitlab.example.com/root/project-with-clusters-api",
+ "readme_url":null,
+ "avatar_url":null,
+ "star_count":0,
+ "forks_count":0,
+ "last_activity_at":"2019-01-02T20:13:32.600Z",
+ "namespace":
+ {
+ "id":1,
+ "name":"root",
+ "path":"root",
+ "kind":"user",
+ "full_path":"root",
+ "parent_id":null
+ }
+ }
+}
+
+```
+
+## Delete project cluster
+
+Deletes an existing project cluster.
+
+```
+DELETE /projects/:id/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project owned by the authenticated user |
+| `cluster_id` | integer | yes | The ID of the cluster |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/23'
+```
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
index 83e405141f1..fc91c5741da 100644
--- a/doc/api/project_import_export.md
+++ b/doc/api/project_import_export.md
@@ -30,7 +30,7 @@ POST /projects/:id/export
```console
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export \
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/export \
--data "upload[http_method]=PUT" \
--data-urlencode "upload[url]=https://example-bucket.s3.eu-west-3.amazonaws.com/backup?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMBJHN2O62W8IELQ%2F20180312%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20180312T110328Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=8413facb20ff33a49a147a0b4abcff4c8487cc33ee1f7e450c46e8f695569dbd"
```
@@ -54,7 +54,7 @@ GET /projects/:id/export
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```console
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/export
```
Status can be one of `none`, `started`, `after_export_action` or `finished`. The
@@ -95,7 +95,7 @@ GET /projects/:id/export/download
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```console
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --remote-header-name --remote-name https://gitlab.example.com/api/v4/projects/5/export/download
+curl --header "PRIVATE-TOKEN: <your_access_token>" --remote-header-name --remote-name https://gitlab.example.com/api/v4/projects/5/export/download
```
```console
@@ -125,7 +125,7 @@ The `file=` parameter must point to a file on your file system and be preceded
by `@`. For example:
```console
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import
```
cURL doesn't support posting a file from a remote server. Importing a project from a remote server can be accomplished through something like the following:
@@ -145,7 +145,7 @@ data = {
"namespace": "example-group"
}
headers = {
- 'Private-Token': "9koXpg98eAheJpvBs5tK"
+ 'Private-Token': "<your_access_token>"
}
requests.post(url, headers=headers, data=data, files=files)
@@ -177,7 +177,7 @@ GET /projects/:id/import
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```console
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/import
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/import
```
Status can be one of `none`, `scheduled`, `failed`, `started`, or `finished`.
diff --git a/doc/api/project_level_variables.md b/doc/api/project_level_variables.md
index 82ac0b09027..438bebe62f5 100644
--- a/doc/api/project_level_variables.md
+++ b/doc/api/project_level_variables.md
@@ -13,7 +13,7 @@ GET /projects/:id/variables
| `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 |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables"
```
```json
@@ -43,7 +43,7 @@ GET /projects/:id/variables/:key
| `key` | string | yes | The `key` of a variable |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables/TEST_VARIABLE_1"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/TEST_VARIABLE_1"
```
```json
@@ -69,7 +69,7 @@ POST /projects/:id/variables
| `protected` | boolean | no | Whether the variable is protected |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
+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"
```
```json
@@ -96,7 +96,7 @@ PUT /projects/:id/variables/:key
| `protected` | boolean | no | Whether the variable is protected |
```
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
```
```json
@@ -121,5 +121,5 @@ DELETE /projects/:id/variables/:key
| `key` | string | yes | The `key` of a variable |
```
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/variables/VARIABLE_1"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/VARIABLE_1"
```
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index cc495c5d091..8f4640fcbd6 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -137,7 +137,7 @@ GET /projects/:id/snippets/:snippet_id/user_agent_detail
| `snippet_id` | Integer | yes | The ID of a snippet |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/snippets/2/user_agent_detail
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/snippets/2/user_agent_detail
```
Example response:
diff --git a/doc/api/projects.md b/doc/api/projects.md
index ef51ea20e7f..1296b435792 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -7,30 +7,29 @@ This is determined by the `visibility` field in the project.
Values for the project visibility level are:
-* `private`:
+- `private`:
Project access must be granted explicitly for each user.
-* `internal`:
+- `internal`:
The project can be cloned by any logged in user.
-* `public`:
+- `public`:
The project can be cloned without any authentication.
## Project merge method
There are currently three options for `merge_method` to choose from:
-* `merge`:
+- `merge`:
A merge commit is created for every merge, and merging is allowed as long as there are no conflicts.
-* `rebase_merge`:
+- `rebase_merge`:
A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible.
This way you could make sure that if this merge request would build, after merging to target branch it would also build.
-* `ff`:
+- `ff`:
No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.
-
## List all projects
Get a list of all visible projects across GitLab for the authenticated user.
@@ -526,11 +525,13 @@ GET /projects/:id
{
"group_id": 4,
"group_name": "Twitter",
+ "group_full_path": "twitter",
"group_access_level": 30
},
{
"group_id": 3,
"group_name": "Gitlab Org",
+ "group_full_path": "gitlab-org",
"group_access_level": 10
}
],
@@ -798,7 +799,7 @@ GET /projects/:id/forks
| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md) |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/forks"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/forks"
```
Example responses:
@@ -878,7 +879,7 @@ POST /projects/:id/star
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/star"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/star"
```
Example response:
@@ -964,7 +965,7 @@ POST /projects/:id/unstar
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/unstar"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/unstar"
```
Example response:
@@ -1046,7 +1047,7 @@ GET /projects/:id/languages
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/languages"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/languages"
```
Example response:
@@ -1074,7 +1075,7 @@ POST /projects/:id/archive
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/archive"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/archive"
```
Example response:
@@ -1178,7 +1179,7 @@ POST /projects/:id/unarchive
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/unarchive"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/unarchive"
```
Example response:
@@ -1299,7 +1300,7 @@ The `file=` parameter must point to a file on your filesystem and be preceded
by `@`. For example:
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "file=@dk.png" https://gitlab.example.com/api/v4/projects/5/uploads
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "file=@dk.png" https://gitlab.example.com/api/v4/projects/5/uploads
```
Returned object:
@@ -1345,7 +1346,7 @@ DELETE /projects/:id/share/:group_id
| `group_id` | integer | yes | The ID of the group |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/share/17
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/share/17
```
## Hooks
@@ -1513,7 +1514,7 @@ GET /projects
| `sort` | string | no | Return requests sorted in `asc` or `desc` order |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects?search=test
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects?search=test
```
## Start the Housekeeping task for a Project
diff --git a/doc/api/protected_branches.md b/doc/api/protected_branches.md
index ed8837574a0..fa04680d406 100644
--- a/doc/api/protected_branches.md
+++ b/doc/api/protected_branches.md
@@ -24,7 +24,7 @@ GET /projects/:id/protected_branches
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches'
+curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_branches'
```
Example response:
@@ -64,7 +64,7 @@ GET /projects/:id/protected_branches/:name
| `name` | string | yes | The name of the branch or wildcard |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches/master'
+curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_branches/master'
```
Example response:
@@ -97,7 +97,7 @@ POST /projects/:id/protected_branches
```
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches?name=*-stable&push_access_level=30&merge_access_level=30'
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_branches?name=*-stable&push_access_level=30&merge_access_level=30'
```
| Attribute | Type | Required | Description |
@@ -136,7 +136,7 @@ DELETE /projects/:id/protected_branches/:name
```
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_branches/*-stable'
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_branches/*-stable'
```
| Attribute | Type | Required | Description |
diff --git a/doc/api/protected_tags.md b/doc/api/protected_tags.md
index aa750e467f8..3adca61a108 100644
--- a/doc/api/protected_tags.md
+++ b/doc/api/protected_tags.md
@@ -25,7 +25,7 @@ GET /projects/:id/protected_tags
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_tags'
+curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_tags'
```
Example response:
@@ -60,7 +60,7 @@ GET /projects/:id/protected_tags/:name
| `name` | string | yes | The name of the tag or wildcard |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_tags/release-1-0'
+curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_tags/release-1-0'
```
Example response:
@@ -87,7 +87,7 @@ POST /projects/:id/protected_tags
```
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_tags?name=*-stable&create_access_level=30'
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_tags?name=*-stable&create_access_level=30'
```
| Attribute | Type | Required | Description |
@@ -119,7 +119,7 @@ DELETE /projects/:id/protected_tags/:name
```
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/5/protected_tags/*-stable'
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_tags/*-stable'
```
| Attribute | Type | Required | Description |
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
new file mode 100644
index 00000000000..943109a3ea9
--- /dev/null
+++ b/doc/api/releases/index.md
@@ -0,0 +1,482 @@
+# Releases API
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
+> - Using this API you can manipulate GitLab's [Release](../../user/project/releases/index.md) entries.
+> - For manipulating links as a release asset, see [Release Links API](links.md)
+
+## List Releases
+
+Paginated list of Releases, sorted by `created_at`.
+
+```
+GET /projects/:id/releases
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases"
+```
+
+Example response:
+
+```json
+[
+ {
+ "tag_name":"v0.2",
+ "description":"## CHANGELOG\r\n\r\n- Escape label and milestone titles to prevent XSS in GFM autocomplete. !2740\r\n- Prevent private snippets from being embeddable.\r\n- Add subresources removal to member destroy service.",
+ "name":"Awesome app v0.2 beta",
+ "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eEscape label and milestone titles to prevent XSS in GFM autocomplete. !2740\u003c/li\u003e\n\u003cli\u003ePrevent private snippets from being embeddable.\u003c/li\u003e\n\u003cli\u003eAdd subresources removal to member destroy service.\u003c/li\u003e\n\u003c/ul\u003e",
+ "created_at":"2019-01-03T01:56:19.539Z",
+ "author":{
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url":"http://localhost:3000/root"
+ },
+ "commit":{
+ "id":"079e90101242458910cccd35eab0e211dfc359c0",
+ "short_id":"079e9010",
+ "title":"Update README.md",
+ "created_at":"2019-01-03T01:55:38.000Z",
+ "parent_ids":[
+ "f8d3d94cbd347e924aa7b715845e439d00e80ca4"
+ ],
+ "message":"Update README.md",
+ "author_name":"Administrator",
+ "author_email":"admin@example.com",
+ "authored_date":"2019-01-03T01:55:38.000Z",
+ "committer_name":"Administrator",
+ "committer_email":"admin@example.com",
+ "committed_date":"2019-01-03T01:55:38.000Z"
+ },
+ "assets":{
+ "count":6,
+ "sources":[
+ {
+ "format":"zip",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.zip"
+ },
+ {
+ "format":"tar.gz",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar.gz"
+ },
+ {
+ "format":"tar.bz2",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar.bz2"
+ },
+ {
+ "format":"tar",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar"
+ }
+ ],
+ "links":[
+ {
+ "id":2,
+ "name":"awesome-v0.2.msi",
+ "url":"http://192.168.10.15:3000/msi",
+ "external":true
+ },
+ {
+ "id":1,
+ "name":"awesome-v0.2.dmg",
+ "url":"http://192.168.10.15:3000",
+ "external":true
+ }
+ ]
+ }
+ },
+ {
+ "tag_name":"v0.1",
+ "description":"## CHANGELOG\r\n\r\n-Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
+ "name":"Awesome app v0.1 alpha",
+ "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
+ "created_at":"2019-01-03T01:55:18.203Z",
+ "author":{
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url":"http://localhost:3000/root"
+ },
+ "commit":{
+ "id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
+ "short_id":"f8d3d94c",
+ "title":"Initial commit",
+ "created_at":"2019-01-03T01:53:28.000Z",
+ "parent_ids":[
+
+ ],
+ "message":"Initial commit",
+ "author_name":"Administrator",
+ "author_email":"admin@example.com",
+ "authored_date":"2019-01-03T01:53:28.000Z",
+ "committer_name":"Administrator",
+ "committer_email":"admin@example.com",
+ "committed_date":"2019-01-03T01:53:28.000Z"
+ },
+ "assets":{
+ "count":4,
+ "sources":[
+ {
+ "format":"zip",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
+ },
+ {
+ "format":"tar.gz",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
+ },
+ {
+ "format":"tar.bz2",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
+ },
+ {
+ "format":"tar",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
+ }
+ ],
+ "links":[
+
+ ]
+ }
+ }
+]
+```
+
+## Get a Release by a tag name
+
+Get a Release for the given tag.
+
+```
+GET /projects/:id/releases/:tag_name
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `tag_name` | string | yes | The tag where the release will be created from. |
+
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1"
+```
+
+Example response:
+
+```json
+{
+ "tag_name":"v0.1",
+ "description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
+ "name":"Awesome app v0.1 alpha",
+ "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
+ "created_at":"2019-01-03T01:55:18.203Z",
+ "author":{
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url":"http://localhost:3000/root"
+ },
+ "commit":{
+ "id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
+ "short_id":"f8d3d94c",
+ "title":"Initial commit",
+ "created_at":"2019-01-03T01:53:28.000Z",
+ "parent_ids":[
+
+ ],
+ "message":"Initial commit",
+ "author_name":"Administrator",
+ "author_email":"admin@example.com",
+ "authored_date":"2019-01-03T01:53:28.000Z",
+ "committer_name":"Administrator",
+ "committer_email":"admin@example.com",
+ "committed_date":"2019-01-03T01:53:28.000Z"
+ },
+ "assets":{
+ "count":4,
+ "sources":[
+ {
+ "format":"zip",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
+ },
+ {
+ "format":"tar.gz",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
+ },
+ {
+ "format":"tar.bz2",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
+ },
+ {
+ "format":"tar",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
+ }
+ ],
+ "links":[
+
+ ]
+ }
+}
+```
+
+## Create a release
+
+Create a Release. You need push access to the repository to create a Release.
+
+```
+POST /projects/:id/releases
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `name` | string | yes | The release name. |
+| `tag_name` | string | yes | The tag where the release will be created from. |
+| `description` | string | yes | The description of the release. You can use [markdown](../user/markdown.md). |
+| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. |
+| `assets:links`| array of hash | no | An array of assets links. |
+| `assets:links:name`| string | no (if `assets:links` specified, it's required) | The name of the link. |
+| `assets:links:url`| string | no (if `assets:links` specified, it's required) | The url of the link. |
+
+Example request:
+
+```sh
+curl --header 'Content-Type: application/json' --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" \
+ --data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "assets": { "links": [{ "name": "hoge", "url": "https://google.com" }] } }' \
+ --request POST http://localhost:3000/api/v4/projects/24/releases
+```
+
+Example response:
+
+```json
+{
+ "tag_name":"v0.3",
+ "description":"Super nice release",
+ "name":"New release",
+ "description_html":"\u003cp dir=\"auto\"\u003eSuper nice release\u003c/p\u003e",
+ "created_at":"2019-01-03T02:22:45.118Z",
+ "author":{
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url":"http://localhost:3000/root"
+ },
+ "commit":{
+ "id":"079e90101242458910cccd35eab0e211dfc359c0",
+ "short_id":"079e9010",
+ "title":"Update README.md",
+ "created_at":"2019-01-03T01:55:38.000Z",
+ "parent_ids":[
+ "f8d3d94cbd347e924aa7b715845e439d00e80ca4"
+ ],
+ "message":"Update README.md",
+ "author_name":"Administrator",
+ "author_email":"admin@example.com",
+ "authored_date":"2019-01-03T01:55:38.000Z",
+ "committer_name":"Administrator",
+ "committer_email":"admin@example.com",
+ "committed_date":"2019-01-03T01:55:38.000Z"
+ },
+ "assets":{
+ "count":5,
+ "sources":[
+ {
+ "format":"zip",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.zip"
+ },
+ {
+ "format":"tar.gz",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar.gz"
+ },
+ {
+ "format":"tar.bz2",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar.bz2"
+ },
+ {
+ "format":"tar",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar"
+ }
+ ],
+ "links":[
+ {
+ "id":3,
+ "name":"hoge",
+ "url":"https://google.com",
+ "external":true
+ }
+ ]
+ }
+}
+```
+
+## Update a release
+
+Update a Release.
+
+```
+PUT /projects/:id/releases/:tag_name
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `tag_name` | string | yes | The tag where the release will be created from. |
+| `name` | string | no | The release name. |
+| `description` | string | no | The description of the release. You can use [markdown](../user/markdown.md). |
+
+Example request:
+
+```sh
+curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1"
+```
+
+Example response:
+
+```json
+{
+ "tag_name":"v0.1",
+ "description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
+ "name":"new name",
+ "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
+ "created_at":"2019-01-03T01:55:18.203Z",
+ "author":{
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url":"http://localhost:3000/root"
+ },
+ "commit":{
+ "id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
+ "short_id":"f8d3d94c",
+ "title":"Initial commit",
+ "created_at":"2019-01-03T01:53:28.000Z",
+ "parent_ids":[
+
+ ],
+ "message":"Initial commit",
+ "author_name":"Administrator",
+ "author_email":"admin@example.com",
+ "authored_date":"2019-01-03T01:53:28.000Z",
+ "committer_name":"Administrator",
+ "committer_email":"admin@example.com",
+ "committed_date":"2019-01-03T01:53:28.000Z"
+ },
+ "assets":{
+ "count":4,
+ "sources":[
+ {
+ "format":"zip",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
+ },
+ {
+ "format":"tar.gz",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
+ },
+ {
+ "format":"tar.bz2",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
+ },
+ {
+ "format":"tar",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
+ }
+ ],
+ "links":[
+
+ ]
+ }
+}
+```
+
+## Delete a Release
+
+Delete a Release. Deleting a Release will not delete the associated tag.
+
+```
+DELETE /projects/:id/releases/:tag_name
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `tag_name` | string | yes | The tag where the release will be created from. |
+
+Example request:
+
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1"
+```
+
+Example response:
+
+```json
+{
+ "tag_name":"v0.1",
+ "description":"## CHANGELOG\r\n\r\n- Remove limit of 100 when searching repository code. !8671\r\n- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\r\n- Fix a bug where internal email pattern wasn't respected. !22516",
+ "name":"new name",
+ "description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
+ "created_at":"2019-01-03T01:55:18.203Z",
+ "author":{
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url":"http://localhost:3000/root"
+ },
+ "commit":{
+ "id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
+ "short_id":"f8d3d94c",
+ "title":"Initial commit",
+ "created_at":"2019-01-03T01:53:28.000Z",
+ "parent_ids":[
+
+ ],
+ "message":"Initial commit",
+ "author_name":"Administrator",
+ "author_email":"admin@example.com",
+ "authored_date":"2019-01-03T01:53:28.000Z",
+ "committer_name":"Administrator",
+ "committer_email":"admin@example.com",
+ "committed_date":"2019-01-03T01:53:28.000Z"
+ },
+ "assets":{
+ "count":4,
+ "sources":[
+ {
+ "format":"zip",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
+ },
+ {
+ "format":"tar.gz",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
+ },
+ {
+ "format":"tar.bz2",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
+ },
+ {
+ "format":"tar",
+ "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
+ }
+ ],
+ "links":[
+
+ ]
+ }
+}
+```
diff --git a/doc/api/releases/links.md b/doc/api/releases/links.md
new file mode 100644
index 00000000000..ae99f3bd8b6
--- /dev/null
+++ b/doc/api/releases/links.md
@@ -0,0 +1,177 @@
+# Release links API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
+
+Using this API you can manipulate GitLab's [Release](../../user/project/releases/index.md) links. For manipulating other Release assets, see [Release API](index.md).
+
+## Get links
+
+Get assets as links from a Release.
+
+```
+GET /projects/:id/releases/:tag_name/assets/links
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `tag_name` | string | yes | The tag associated with the Release. |
+
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id":2,
+ "name":"awesome-v0.2.msi",
+ "url":"http://192.168.10.15:3000/msi",
+ "external":true
+ },
+ {
+ "id":1,
+ "name":"awesome-v0.2.dmg",
+ "url":"http://192.168.10.15:3000",
+ "external":true
+ }
+]
+```
+
+## Get a link
+
+Get an asset as a link from a Release.
+
+```
+GET /projects/:id/releases/:tag_name/assets/links/:link_id
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `tag_name` | string | yes | The tag associated with the Release. |
+| `link_id` | integer | yes | The id of the link. |
+
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links/1"
+```
+
+Example response:
+
+```json
+{
+ "id":1,
+ "name":"awesome-v0.2.dmg",
+ "url":"http://192.168.10.15:3000",
+ "external":true
+}
+```
+
+## Create a link
+
+Create an asset as a link from a Release.
+
+```
+POST /projects/:id/releases/:tag_name/assets/links
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `tag_name` | string | yes | The tag associated with the Release. |
+| `name` | string | yes | The name of the link. |
+| `url` | string | yes | The URL of the link. |
+
+Example request:
+
+```sh
+curl --request POST \
+ --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" \
+ --data name="awesome-v0.2.dmg" \
+ --data url="http://192.168.10.15:3000" \
+ "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links"
+```
+
+Example response:
+
+```json
+{
+ "id":1,
+ "name":"awesome-v0.2.dmg",
+ "url":"http://192.168.10.15:3000",
+ "external":true
+}
+```
+
+## Update a link
+
+Update an asset as a link from a Release.
+
+```
+PUT /projects/:id/releases/:tag_name/assets/links/:link_id
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `tag_name` | string | yes | The tag associated with the Release. |
+| `link_id` | integer | yes | The id of the link. |
+| `name` | string | no | The name of the link. |
+| `url` | string | no | The URL of the link. |
+
+NOTE: **NOTE**
+You have to specify at least one of `name` or `url`
+
+Example request:
+
+```sh
+curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links/1"
+```
+
+Example response:
+
+```json
+{
+ "id":1,
+ "name":"new name",
+ "url":"http://192.168.10.15:3000",
+ "external":true
+}
+```
+
+## Delete a link
+
+Delete an asset as a link from a Release.
+
+```
+DELETE /projects/:id/releases/:tag_name/assets/links/:link_id
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `tag_name` | string | yes | The tag associated with the Release. |
+| `link_id` | integer | yes | The id of the link. |
+
+Example request:
+
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links/1"
+```
+
+Example response:
+
+```json
+{
+ "id":1,
+ "name":"new name",
+ "url":"http://192.168.10.15:3000",
+ "external":true
+}
+```
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 55f5a4cc3b2..9f552a10589 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -212,7 +212,7 @@ Response:
## Merge Base
-Get the common ancestor for 2 refs (commit SHAs, branch names or tags).
+Get the common ancestor for 2 or more refs (commit SHAs, branch names or tags).
```
GET /projects/:id/repository/merge_base
@@ -224,7 +224,7 @@ GET /projects/:id/repository/merge_base
| `refs` | array | yes | The refs to find the common ancestor of, multiple refs can be passed |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/merge_base?refs[]=304d257dcb821665ab5110318fc58a007bd104ed&refs[]=0031876facac3f2b2702a0e53a26e89939a42209"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/merge_base?refs[]=304d257dcb821665ab5110318fc58a007bd104ed&refs[]=0031876facac3f2b2702a0e53a26e89939a42209"
```
Example response:
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 5f587f480b6..8c1d982f394 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -25,7 +25,7 @@ GET /projects/:id/repository/files/:file_path
```
```bash
-curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb?ref=master'
+curl --request GET --header 'PRIVATE-TOKEN: <your_access_token>' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb?ref=master'
```
Example response:
@@ -60,7 +60,7 @@ HEAD /projects/:id/repository/files/:file_path
```
```bash
-curl --head --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb?ref=master'
+curl --head --header 'PRIVATE-TOKEN: <your_access_token>' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb?ref=master'
```
Example response:
@@ -87,7 +87,7 @@ GET /projects/:id/repository/files/:file_path/raw
```
```bash
-curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb/raw?ref=master'
+curl --request GET --header 'PRIVATE-TOKEN: <your_access_token>' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fmodels%2Fkey%2Erb/raw?ref=master'
```
Parameters:
@@ -107,7 +107,7 @@ POST /projects/:id/repository/files/:file_path
```
```bash
-curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' --header "Content-Type: application/json" \
+curl --request POST --header 'PRIVATE-TOKEN: <your_access_token>' --header "Content-Type: application/json" \
--data '{"branch": "master", "author_email": "author@example.com", "author_name": "Firstname Lastname", \
"content": "some content", "commit_message": "create a new file"}' \
'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb'
@@ -142,7 +142,7 @@ PUT /projects/:id/repository/files/:file_path
```
```bash
-curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' --header "Content-Type: application/json" \
+curl --request PUT --header 'PRIVATE-TOKEN: <your_access_token>' --header "Content-Type: application/json" \
--data '{"branch": "master", "author_email": "author@example.com", "author_name": "Firstname Lastname", \
"content": "some content", "commit_message": "update file"}' \
'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb'
@@ -187,7 +187,7 @@ DELETE /projects/:id/repository/files/:file_path
```
```bash
-curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' --header "Content-Type: application/json" \
+curl --request DELETE --header 'PRIVATE-TOKEN: <your_access_token>' --header "Content-Type: application/json" \
--data '{"branch": "master", "author_email": "author@example.com", "author_name": "Firstname Lastname", \
"commit_message": "delete file"}' \
'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb'
diff --git a/doc/api/repository_submodules.md b/doc/api/repository_submodules.md
index 11b04c81172..2c44c4abc93 100644
--- a/doc/api/repository_submodules.md
+++ b/doc/api/repository_submodules.md
@@ -22,7 +22,7 @@ PUT /projects/:id/repository/submodules/:submodule
| `commit_message` | string | no | Commit message. If no message is provided, a default one will be set |
```sh
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/submodules/lib%2Fmodules%2Fexample"
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/submodules/lib%2Fmodules%2Fexample"
--data "branch=master&commit_sha=3ddec28ea23acc5caa5d8331a6ecb2a65fc03e88&commit_message=Update submodule reference"
```
diff --git a/doc/api/resource_label_events.md b/doc/api/resource_label_events.md
index 33e4821ccf4..e1f9ffa9472 100644
--- a/doc/api/resource_label_events.md
+++ b/doc/api/resource_label_events.md
@@ -65,7 +65,7 @@ GET /projects/:id/issues/:issue_iid/resource_label_events
```
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/resource_label_events
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/resource_label_events
```
### Get single issue label event
@@ -85,7 +85,7 @@ Parameters:
| `resource_label_event_id` | integer | yes | The ID of a label event |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/11/resource_label_events/1
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/resource_label_events/1
```
## Merge requests
@@ -151,7 +151,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/resource_label_events
```
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/resource_label_events
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/resource_label_events
```
### Get single merge request label event
@@ -171,5 +171,5 @@ Parameters:
| `resource_label_event_id` | integer | yes | The ID of a label event |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/resource_label_events/120
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/resource_label_events/120
```
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 071c13f41cb..4aa0e4543e5 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -22,7 +22,7 @@ GET /runners?status=active
| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners"
```
Example response:
@@ -71,7 +71,7 @@ GET /runners/all?status=active
| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/all"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/all"
```
Example response:
@@ -134,7 +134,7 @@ GET /runners/:id
| `id` | integer | yes | The ID of a runner |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6"
```
Example response:
@@ -193,7 +193,7 @@ PUT /runners/:id
| `maximum_timeout` | integer | no | Maximum timeout set when this Runner will handle the job |
```
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
```
Example response:
@@ -247,7 +247,7 @@ DELETE /runners/:id
| `id` | integer | yes | The ID of a runner |
```
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6"
```
## List runner's jobs
@@ -264,7 +264,7 @@ GET /runners/:id/jobs
| `status` | string | no | Status of the job; one of: `running`, `success`, `failed`, `canceled` |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/1/jobs?status=running"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/1/jobs?status=running"
```
Example response:
@@ -357,7 +357,7 @@ GET /projects/:id/runners?status=active
| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/9/runners"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners"
```
Example response:
@@ -401,7 +401,7 @@ POST /projects/:id/runners
| `runner_id` | integer | yes | The ID of a runner |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/9/runners" --form "runner_id=9"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners" --form "runner_id=9"
```
Example response:
@@ -435,7 +435,7 @@ DELETE /projects/:id/runners/:runner_id
| `runner_id` | integer | yes | The ID of a runner |
```
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/9/runners/9"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners/9"
```
## Register a new Runner
diff --git a/doc/api/search.md b/doc/api/search.md
index 7e3ae7404a3..aaaed7d9956 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -24,7 +24,7 @@ The response depends on the requested scope.
### Scope: projects
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=projects&search=flight
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=projects&search=flight
```
Example response:
@@ -55,7 +55,7 @@ Example response:
### Scope: issues
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=issues&search=file
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=issues&search=file
```
Example response:
@@ -120,7 +120,7 @@ Example response:
### Scope: merge_requests
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=merge_requests&search=file
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=merge_requests&search=file
```
Example response:
@@ -197,7 +197,7 @@ Example response:
### Scope: milestones
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=milestones&search=release
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=milestones&search=release
```
Example response:
@@ -222,7 +222,7 @@ Example response:
### Scope: snippet_titles
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=snippet_titles&search=sample
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=snippet_titles&search=sample
```
Example response:
@@ -253,7 +253,7 @@ Example response:
### Scope: snippet_blobs
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/search?scope=snippet_blos&search=test
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=snippet_blos&search=test
```
Example response:
@@ -305,7 +305,7 @@ The response depends on the requested scope.
### Scope: projects
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=projects&search=flight
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/3/search?scope=projects&search=flight
```
Example response:
@@ -336,7 +336,7 @@ Example response:
### Scope: issues
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=issues&search=file
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/3/search?scope=issues&search=file
```
Example response:
@@ -401,7 +401,7 @@ Example response:
### Scope: merge_requests
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=merge_requests&search=file
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/3/search?scope=merge_requests&search=file
```
Example response:
@@ -478,7 +478,7 @@ Example response:
### Scope: milestones
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/3/search?scope=milestones&search=release
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/3/search?scope=milestones&search=release
```
Example response:
@@ -524,7 +524,7 @@ The response depends on the requested scope.
### Scope: issues
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/search?scope=issues&search=file
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/12/search?scope=issues&search=file
```
Example response:
@@ -589,7 +589,7 @@ Example response:
### Scope: merge_requests
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=merge_requests&search=file
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=merge_requests&search=file
```
Example response:
@@ -666,7 +666,7 @@ Example response:
### Scope: milestones
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/12/search?scope=milestones&search=release
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/12/search?scope=milestones&search=release
```
Example response:
@@ -691,7 +691,7 @@ Example response:
### Scope: notes
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=notes&search=maxime
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=notes&search=maxime
```
Example response:
@@ -740,7 +740,7 @@ results:
times in the content.
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=wiki_blobs&search=bye
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=wiki_blobs&search=bye
```
Example response:
@@ -763,7 +763,7 @@ Example response:
### Scope: commits
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=commits&search=bye
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=commits&search=bye
```
Example response:
@@ -810,7 +810,7 @@ Blobs searches are performed on both filenames and contents. Search results:
times in the content.
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation
```
Example response:
diff --git a/doc/api/services.md b/doc/api/services.md
index c4edaa17815..868bcdd07fc 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -639,7 +639,7 @@ Example response:
"job_events": true,
"pipeline_events": true,
"properties": {
- "token": "9koXpg98eAheJpvBs5tK"
+ "token": "<your_access_token>"
}
}
```
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 9b38e3a4eb7..c329e3cdf24 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -19,7 +19,7 @@ GET /application/settings
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/application/settings
```
Example response:
@@ -75,7 +75,7 @@ PUT /application/settings
```
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal
```
Example response:
@@ -208,7 +208,7 @@ are listed in the descriptions of the relevant settings.
| `rsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded RSA key. Default is `0` (no restriction). `-1` disables RSA keys. |
| `send_user_confirmation_email` | boolean | no | Send confirmation email on sign-up. |
| `sentry_dsn` | string | required by: `sentry_enabled` | Sentry Data Source Name. |
-| `sentry_enabled` | boolean | no | (**If enabled, requires:** `sentry_dsn`) Sentry is an error reporting and logging tool which is currently not shipped with GitLab, available at https://getsentry.com. |
+| `sentry_enabled` | boolean | no | (**If enabled, requires:** `sentry_dsn`) Sentry is an error reporting and logging tool which is currently not shipped with GitLab, available at <https://sentry.io>. |
| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
| `shared_runners_enabled` | boolean | no | (**If enabled, requires:** `shared_runners_text`) Enable shared runners for new projects. |
| `shared_runners_text` | string | required by: `shared_runners_enabled` | Shared runners text. |
diff --git a/doc/api/sidekiq_metrics.md b/doc/api/sidekiq_metrics.md
index b9500916cf2..95dcf2d5277 100644
--- a/doc/api/sidekiq_metrics.md
+++ b/doc/api/sidekiq_metrics.md
@@ -15,7 +15,7 @@ GET /sidekiq/queue_metrics
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/sidekiq/queue_metrics
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/sidekiq/queue_metrics
```
Example response:
@@ -40,7 +40,7 @@ GET /sidekiq/process_metrics
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/sidekiq/process_metrics
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/sidekiq/process_metrics
```
Example response:
@@ -82,7 +82,7 @@ GET /sidekiq/job_stats
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/sidekiq/job_stats
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/sidekiq/job_stats
```
Example response:
@@ -106,7 +106,7 @@ GET /sidekiq/compound_metrics
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/sidekiq/compound_metrics
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/sidekiq/compound_metrics
```
Example response:
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index e840e640377..2cbd041d132 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -38,7 +38,7 @@ Parameters:
| `id` | Integer | yes | The ID of a snippet |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/snippets/1
```
Example response:
@@ -80,7 +80,7 @@ Parameters:
| `id` | Integer | yes | The ID of a snippet |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets/1/raw
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/snippets/1/raw
```
Example response:
@@ -208,7 +208,7 @@ Parameters:
```
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/snippets/1"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/snippets/1"
```
upon successful delete a `204 No content` HTTP code shall be expected, with no data,
@@ -226,7 +226,7 @@ GET /snippets/public
| `page` | Integer | no | the page to retrieve |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets/public?per_page=2&page=1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/snippets/public?per_page=2&page=1
```
Example response:
@@ -288,7 +288,7 @@ GET /snippets/:id/user_agent_detail
| `id` | Integer | yes | The ID of a snippet |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets/1/user_agent_detail
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/snippets/1/user_agent_detail
```
Example response:
diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md
index 9d76ef0c4bf..e88d536282a 100644
--- a/doc/api/suggestions.md
+++ b/doc/api/suggestions.md
@@ -16,7 +16,7 @@ PUT /suggestions/:id/apply
| `id` | integer/string | yes | The ID of a suggestion |
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/suggestions/5/apply
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/suggestions/5/apply
```
Example response:
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index 7b8db6cfa8f..f8563e819db 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -20,7 +20,7 @@ GET /hooks
Example request:
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/hooks
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/hooks
```
Example response:
@@ -63,7 +63,7 @@ POST /hooks
Example request:
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/hooks?url=https://gitlab.example.com/hook"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/hooks?url=https://gitlab.example.com/hook"
```
Example response:
@@ -96,7 +96,7 @@ GET /hooks/:id
Example request:
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/hooks/2
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/hooks/2
```
Example response:
@@ -129,5 +129,5 @@ DELETE /hooks/:id
Example request:
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/hooks/2
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/hooks/2
``` \ No newline at end of file
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 826900ca518..23dbf2d9ff7 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -17,6 +17,9 @@ Parameters:
| `id` | integer/string| yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user|
| `order_by` | string | no | Return tags ordered by `name` or `updated` fields. Default is `updated` |
| `sort` | string | no | Return tags sorted in `asc` or `desc` order. Default is `desc` |
+| `search` | string | no | Return list of tags matching the search criteria |
+
+> Support for `search` was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/54401) in GitLab 11.8.
```json
[
@@ -65,7 +68,7 @@ Parameters:
| `tag_name` | string | yes | The name of the tag |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/tags/v1.0.0
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/tags/v1.0.0
```
Example Response:
diff --git a/doc/api/templates/licenses.md b/doc/api/templates/licenses.md
index 8d1006e08c5..5feb1e498bd 100644
--- a/doc/api/templates/licenses.md
+++ b/doc/api/templates/licenses.md
@@ -116,7 +116,7 @@ If you omit the `fullname` parameter but authenticate your request, the name of
the authenticated user will be used to replace the copyright holder placeholder.
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/templates/licenses/mit?project=My+Cool+Project
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/templates/licenses/mit?project=My+Cool+Project
```
Example response:
diff --git a/doc/api/todos.md b/doc/api/todos.md
index 0843e4eedc6..c54c90d9f06 100644
--- a/doc/api/todos.md
+++ b/doc/api/todos.md
@@ -23,7 +23,7 @@ Parameters:
| `type` | string | no | The type of a todo. Can be either `Issue` or `MergeRequest` |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/todos
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/todos
```
Example Response:
@@ -195,7 +195,7 @@ Parameters:
| `id` | integer | yes | The ID of a todo |
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/todos/130/mark_as_done
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/todos/130/mark_as_done
```
Example Response:
@@ -285,8 +285,7 @@ POST /todos/mark_as_done
```
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/todos/donmark_as_donee
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/todos/mark_as_done
```
-
[ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188
diff --git a/doc/api/users.md b/doc/api/users.md
index 1cf4444319c..6000b9b900f 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -458,7 +458,7 @@ GET /user/status
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/user/status"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/user/status"
```
Example response:
@@ -513,7 +513,7 @@ PUT /user/status
When both parameters `emoji` and `message` are empty, the status will be cleared.
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "emoji=coffee" --data "message=I crave coffee" https://gitlab.example.com/api/v4/user/status
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --data "emoji=coffee" --data "message=I crave coffee" https://gitlab.example.com/api/v4/user/status
```
Example responses
@@ -679,7 +679,7 @@ GET /user/gpg_keys
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/user/gpg_keys
```
Example response:
@@ -709,7 +709,7 @@ Parameters:
| `key_id` | integer | yes | The ID of the GPG key |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/user/gpg_keys/1
```
Example response:
@@ -737,7 +737,7 @@ Parameters:
| key | string | yes | The new GPG key |
```bash
-curl --data "key=-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFV..." --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys
+curl --data "key=-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFV..." --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/user/gpg_keys
```
Example response:
@@ -767,7 +767,7 @@ Parameters:
| `key_id` | integer | yes | The ID of the GPG key |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/gpg_keys/1
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/user/gpg_keys/1
```
Returns `204 No Content` on success, or `404 Not found` if the key cannot be found.
@@ -787,7 +787,7 @@ Parameters:
| `id` | integer | yes | The ID of the user |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/2/gpg_keys
```
Example response:
@@ -818,7 +818,7 @@ Parameters:
| `key_id` | integer | yes | The ID of the GPG key |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/2/gpg_keys/1
```
Example response:
@@ -847,7 +847,7 @@ Parameters:
| `key_id` | integer | yes | The ID of the GPG key |
```bash
-curl --data "key=-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFV..." --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys
+curl --data "key=-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n\r\nxsBNBFV..." --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/2/gpg_keys
```
Example response:
@@ -878,7 +878,7 @@ Parameters:
| `key_id` | integer | yes | The ID of the GPG key |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/2/gpg_keys/1
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/2/gpg_keys/1
```
## List emails
@@ -1063,7 +1063,7 @@ Parameters:
| `state` | string | no | filter tokens based on state (`all`, `active`, `inactive`) |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/impersonation_tokens
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/42/impersonation_tokens
```
Example response:
@@ -1115,7 +1115,7 @@ Parameters:
| `impersonation_token_id` | integer | yes | The ID of the impersonation token |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/impersonation_tokens/2
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/42/impersonation_tokens/2
```
Example response:
@@ -1160,7 +1160,7 @@ Parameters:
| `scopes` | array | yes | The array of scopes of the impersonation token (`api`, `read_user`) |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "name=mytoken" --data "expires_at=2017-04-04" --data "scopes[]=api" https://gitlab.example.com/api/v4/users/42/impersonation_tokens
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "name=mytoken" --data "expires_at=2017-04-04" --data "scopes[]=api" https://gitlab.example.com/api/v4/users/42/impersonation_tokens
```
Example response:
@@ -1192,7 +1192,7 @@ DELETE /users/:user_id/impersonation_tokens/:impersonation_token_id
```
```
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/users/42/impersonation_tokens/1
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/42/impersonation_tokens/1
```
Parameters:
@@ -1227,7 +1227,7 @@ Parameters:
| `from` | string | no | Date string in the format YEAR-MONTH-DAY, e.g. `2016-03-11`. Defaults to 6 months ago. |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/user/activities
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/user/activities
```
Example response:
diff --git a/doc/api/version.md b/doc/api/version.md
index 8b2a5b51bc5..ac19178b7ad 100644
--- a/doc/api/version.md
+++ b/doc/api/version.md
@@ -10,7 +10,7 @@ GET /version
```
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/version
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/version
```
Example response:
diff --git a/doc/api/wikis.md b/doc/api/wikis.md
index df3b54e8f89..436d06cfd3a 100644
--- a/doc/api/wikis.md
+++ b/doc/api/wikis.md
@@ -18,7 +18,7 @@ GET /projects/:id/wikis
| `with_content` | boolean | no | Include pages' content |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/wikis?with_content=1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/wikis?with_content=1
```
Example response:
@@ -59,7 +59,7 @@ GET /projects/:id/wikis/:slug
| `slug` | string | yes | The slug (a unique string) of the wiki page |
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/wikis/home
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/wikis/home
```
Example response:
@@ -89,7 +89,7 @@ POST /projects/:id/wikis
| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, and `asciidoc` |
```bash
-curl --data "format=rdoc&title=Hello&content=Hello world" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis"
+curl --data "format=rdoc&title=Hello&content=Hello world" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/wikis"
```
Example response:
@@ -121,7 +121,7 @@ PUT /projects/:id/wikis/:slug
```bash
-curl --request PUT --data "format=rdoc&content=documentation&title=Docs" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
+curl --request PUT --data "format=rdoc&content=documentation&title=Docs" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
```
Example response:
@@ -149,7 +149,7 @@ DELETE /projects/:id/wikis/:slug
| `slug` | string | yes | The slug (a unique string) of the wiki page |
```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
```
On success the HTTP status code is `204` and no JSON response is expected.
@@ -177,7 +177,7 @@ The `file=` parameter must point to a file on your filesystem and be preceded
by `@`. For example:
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "file=@dk.png" https://gitlab.example.com/api/v4/projects/1/wikis/attachments
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "file=@dk.png" https://gitlab.example.com/api/v4/projects/1/wikis/attachments
```
Example response:
diff --git a/doc/ci/autodeploy/img/auto_deploy_btn.png b/doc/ci/autodeploy/img/auto_deploy_btn.png
deleted file mode 100644
index ee88e5ce8c0..00000000000
--- a/doc/ci/autodeploy/img/auto_deploy_btn.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/autodeploy/img/auto_deploy_button.png b/doc/ci/autodeploy/img/auto_deploy_button.png
deleted file mode 100644
index 0e84d9c57a1..00000000000
--- a/doc/ci/autodeploy/img/auto_deploy_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/autodeploy/img/auto_deploy_dropdown.png b/doc/ci/autodeploy/img/auto_deploy_dropdown.png
deleted file mode 100644
index 4094f8ebb4e..00000000000
--- a/doc/ci/autodeploy/img/auto_deploy_dropdown.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/autodeploy/img/auto_monitoring.png b/doc/ci/autodeploy/img/auto_monitoring.png
deleted file mode 100644
index 5a11923d199..00000000000
--- a/doc/ci/autodeploy/img/auto_monitoring.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/autodeploy/img/guide_connect_cluster.png b/doc/ci/autodeploy/img/guide_connect_cluster.png
deleted file mode 100644
index 703d536f37a..00000000000
--- a/doc/ci/autodeploy/img/guide_connect_cluster.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/autodeploy/img/guide_integration.png b/doc/ci/autodeploy/img/guide_integration.png
deleted file mode 100644
index ab72de2bba3..00000000000
--- a/doc/ci/autodeploy/img/guide_integration.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/autodeploy/img/guide_secret.png b/doc/ci/autodeploy/img/guide_secret.png
deleted file mode 100644
index 8469bee48b7..00000000000
--- a/doc/ci/autodeploy/img/guide_secret.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/autodeploy/quick_start_guide.md b/doc/ci/autodeploy/quick_start_guide.md
index 1473703542d..985ec4b972c 100644
--- a/doc/ci/autodeploy/quick_start_guide.md
+++ b/doc/ci/autodeploy/quick_start_guide.md
@@ -1,95 +1 @@
-# Auto Deploy: quick start guide
-
-This is a step-by-step guide to deploying a project hosted on GitLab.com to Google Cloud, using Auto Deploy.
-
-We made a minimal [Ruby application](https://gitlab.com/gitlab-examples/minimal-ruby-app) to use as an example for this guide. It contains two files:
-
-* `server.rb` - our application. It will start an HTTP server on port 5000 and render “Hello, world!”
-* `Dockerfile` - to build our app into a container image. It will use a ruby base image and run `server.rb`
-
-## Fork sample project on GitLab.com
-
-Let’s start by forking our sample application. Go to [the project page](https://gitlab.com/gitlab-examples/minimal-ruby-app) and press the `Fork` button. Soon you should have a project under your namespace with the necessary files.
-
-## Set up your own cluster on Google Kubernetes Engine
-
-If you do not already have a Google Cloud account, create one at https://console.cloud.google.com.
-
-Visit the [`Kubernetes Engine`](https://console.cloud.google.com/kubernetes/list) tab and create a new cluster. You can change the name and leave the rest of the default settings. Once you have your cluster running, you need to connect to the cluster by following the Google interface.
-
-## Connect to Kubernetes cluster
-
-You need to have the Google Cloud SDK installed. e.g.
-On OSX, install [homebrew](https://brew.sh):
-
-1. Install Brew Caskroom: `brew install caskroom/cask/brew-cask`
-1. Install Google Cloud SDK: `brew cask install google-cloud-sdk`
-1. Add `kubectl`: `gcloud components install kubectl`
-1. Log in: `gcloud auth login`
-
-Now go back to the Google interface, find your cluster, and follow the instructions under `Connect to the cluster` and open the Kubernetes Dashboard. It will look something like `gcloud container clusters get-credentials ruby-autodeploy \ --zone europe-west2-c --project api-project-XXXXXXX` and then `kubectl proxy`.
-
-![connect to cluster](img/guide_connect_cluster.png)
-
-## Copy credentials to GitLab.com project
-
-Once you have the Kubernetes Dashboard interface running, you should visit `Secrets` under the `Config` section. There you should find the settings we need for GitLab integration: ca.crt and token.
-
-![connect to cluster](img/guide_secret.png)
-
-You need to copy-paste the ca.crt and token into your project on GitLab.com in the Kubernetes integration page under project `Settings` > `Integrations` > `Project services` > `Kubernetes`. Don't actually copy the namespace though. Each project should have a unique namespace, and by leaving it blank, GitLab will create one for you.
-
-![connect to cluster](img/guide_integration.png)
-
-For API URL, you should use the `Endpoint` IP from your cluster page on Google Cloud Platform.
-
-## Expose the application to the internet
-
-In order to be able to visit your application, you need to install an NGINX ingress controller and point your domain name to its external IP address.
-
-### Set up Ingress controller
-
-You’ll need to make sure you have an ingress controller. If you don’t have one, do:
-
-```sh
-brew install kubernetes-helm
-helm init
-helm install --name ruby-app stable/nginx-ingress
-```
-
-This should create several services including `ruby-app-nginx-ingress-controller`. You can list your services by running `kubectl get svc` to confirm that.
-
-### Point DNS at Cluster IP
-
-Find out the external IP address of the `ruby-app-nginx-ingress-controller` by running:
-
-```sh
-kubectl get svc ruby-app-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
-```
-
-Use this IP address to configure your DNS. This part heavily depends on your preferences and domain provider. But in case you are not sure, just create an A record with a wildcard host like `*.<your-domain>` pointing to the external IP address you found above.
-
-Use `nslookup minimal-ruby-app-staging.<yourdomain>` to confirm that domain is assigned to the cluster IP.
-
-## Set up Auto Deploy
-
-Visit the home page of your GitLab.com project and press "Set up Auto Deploy" button.
-
-![auto deploy button](img/auto_deploy_btn.png)
-
-You will be redirected to the "New file" page where you can apply one of the Auto Deploy templates. Select "Kubernetes" to apply the template, then in the file, replace `domain.example.com` with your domain name and make any other adjustments you need.
-
-![auto deploy template](img/auto_deploy_dropdown.png)
-
-Change the target branch to `master`, and submit your changes. This should create
-a new pipeline with several jobs. If you made only the domain name change, the
-pipeline will have three jobs: `build`, `staging`, and `production`.
-
-The `build` job will create a Docker image with your new change and push it to
-the GitLab Container Registry. The `staging` job will deploy this image on your
-cluster. Once the deploy job succeeds you should be able to see your application by
-visiting the Kubernetes dashboard. Select the namespace of your project, which
-will look like `ruby-autodeploy-23`, but with a unique ID for your project, and
-your app will be listed as "staging" under the "Deployment" tab.
-
-Once its ready - just visit http://minimal-ruby-app-staging.yourdomain.com to see “Hello, world!”
+This document was moved to [another location](../../topics/autodevops/index.md#auto-deploy).
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index fef367051bf..a462c75f2f5 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -194,7 +194,7 @@ not without its own challenges:
- docker run -v "$MOUNT_POINT:/mnt" my-docker-image
```
-An example project using this approach can be found here: https://gitlab.com/gitlab-examples/docker.
+An example project using this approach can be found here: <https://gitlab.com/gitlab-examples/docker>.
### Use Docker socket binding
@@ -521,11 +521,11 @@ stages:
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
- CONTAINER_TEST_IMAGE: registry.example.com/my-group/my-project/my-image:$CI_COMMIT_REF_SLUG
- CONTAINER_RELEASE_IMAGE: registry.example.com/my-group/my-project/my-image:latest
+ CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+ CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.example.com
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
build:
stage: build
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 315d0c5e7ef..b9b5ceab7fb 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -416,81 +416,15 @@ and/or `production`) you can see this information in the merge request itself.
### Go directly from source files to public pages on the environment
-> Introduced in GitLab 8.17. In GitLab 11.5 the file links
-are surfaced to the merge request widget.
-
-You can specify a Route Map to get GitLab to show "View on <environment URL>"
-buttons to go directly from a file to that file's representation on the
-[deployed website via Review Apps](review_apps/index.md).
-
-To get this to work, you need to tell GitLab how the paths of files in your repository map to paths of pages on your website, using a Route Map.
-
-A Route Map is a file inside the repository at `.gitlab/route-map.yml`, which contains a YAML array that maps `source` paths (in the repository) to `public` paths (on the website).
-Below is an example of a route map for [Middleman](https://middlemanapp.com) static websites
-like <https://gitlab.com/gitlab-com/www-gitlab-com>:
-
-```yaml
-# Team data
-- source: 'data/team.yml' # data/team.yml
- public: 'team/' # team/
-
-# Blogposts
-- source: /source\/posts\/([0-9]{4})-([0-9]{2})-([0-9]{2})-(.+?)\..*/ # source/posts/2017-01-30-around-the-world-in-6-releases.html.md.erb
- public: '\1/\2/\3/\4/' # 2017/01/30/around-the-world-in-6-releases/
-
-# HTML files
-- source: /source\/(.+?\.html).*/ # source/index.html.haml
- public: '\1' # index.html
-
-# Other files
-- source: /source\/(.*)/ # source/images/blogimages/around-the-world-in-6-releases-cover.png
- public: '\1' # images/blogimages/around-the-world-in-6-releases-cover.png
-```
-
-Mappings are defined as entries in the root YAML array, and are identified by a `-` prefix. Within an entry, we have a hash map with two keys:
-
-- `source`
- - a string, starting and ending with `'`, for an exact match
- - a regular expression, starting and ending with `/`, for a pattern match
- - The regular expression needs to match the entire source path - `^` and `$` anchors are implied.
- - Can include capture groups denoted by `()` that can be referred to in the `public` path.
- - Slashes (`/`) can, but don't have to, be escaped as `\/`.
- - Literal periods (`.`) should be escaped as `\.`.
-- `public`
- - a string, starting and ending with `'`.
- - Can include `\N` expressions to refer to capture groups in the `source` regular expression in order of their occurrence, starting with `\1`.
-
-The public path for a source path is determined by finding the first `source` expression that matches it, and returning the corresponding `public` path, replacing the `\N` expressions with the values of the `()` capture groups if appropriate.
-
-In the example above, the fact that mappings are evaluated in order of their definition is used to ensure that `source/index.html.haml` will match `/source\/(.+?\.html).*/` instead of `/source\/(.*)/`, and will result in a public path of `index.html`, instead of `index.html.haml`.
-
----
-
-Once you have the route mapping set up, it will be exposed in a few places:
-
-- In the merge request widget. The **View app** button will take you to the
- environment URL you have set up in `.gitlab-ci.yml`. The dropdown will render
- the first 5 matched items from the route map, but you can filter them if more
- than 5 are available.
-
- ![View app file list in merge request widget](img/view_on_mr_widget.png)
-
-- In the diff for a merge request, comparison, or commit.
-
- !["View on env" button in merge request diff](img/view_on_env_mr.png)
-
-- In the blob file view.
-
- !["View on env" button in file view](img/view_on_env_blob.png) |
-
----
-
-We now have a full development cycle, where our app is tested, built, deployed
-as a Review app, deployed to a staging server once the merge request is merged,
-and finally manually deployed to the production server. What we just described
-is a single workflow, but imagine tens of developers working on a project
-at the same time. They each push to their branches, and dynamic environments are
-created all the time. In that case, we probably need to do some clean up. Read
+With GitLab's [Route Maps](review_apps/index.md#route-maps) you can go directly
+from source files to public pages on the environment set for Review Apps.
+
+From then on, you have a full development cycle, where your app is tested, built, deployed
+as a Review App, deployed to a staging server once the merge request is merged,
+and finally manually deployed to the production server. This is a simple workflow,
+but when you have multiple developers working on a project
+at the same time, each of them pushing to their own branches, dynamic environments are
+created all the time. In which case, you probably want to do some clean up. Read
next how environments can be stopped.
## Stopping an environment
diff --git a/doc/ci/examples/artifactory_and_gitlab/index.md b/doc/ci/examples/artifactory_and_gitlab/index.md
index 6aa0edd87b4..04b48938e1a 100644
--- a/doc/ci/examples/artifactory_and_gitlab/index.md
+++ b/doc/ci/examples/artifactory_and_gitlab/index.md
@@ -16,8 +16,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 (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>)
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/features/gitlab-ci-cd/).
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/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index 40ceef3d554..8873a1596f7 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
@@ -138,5 +138,5 @@ buildpack: client-certificate-mapper=1.2.0_RELEASE container-security-provider=1
```
You can then visit your deployed application (for this example,
-https://gitlab-hello-world-undissembling-hotchpot.cfapps.io/) and you should
+`https://gitlab-hello-world-undissembling-hotchpot.cfapps.io/`) and you should
see the "Spring is here!" message.
diff --git a/doc/ci/examples/deployment/README.md b/doc/ci/examples/deployment/README.md
index 46effb76d71..010ba6b66a2 100644
--- a/doc/ci/examples/deployment/README.md
+++ b/doc/ci/examples/deployment/README.md
@@ -6,7 +6,7 @@ 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.
+<https://github.com/travis-ci/dpl#supported-providers>.
## Requirements
@@ -34,7 +34,7 @@ The Dpl provides support for vast number of services, including: Heroku, Cloud F
To use it simply define provider and any additional parameters required by the provider.
For example if you want to use it to deploy your application to heroku, you need to specify `heroku` as provider, specify `api-key` and `app`.
-There's more and all possible parameters can be found here: https://github.com/travis-ci/dpl#heroku
+There's more and all possible parameters can be found here: <https://github.com/travis-ci/dpl#heroku>.
```yaml
staging:
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index d299e28d2e6..0cf9daed22f 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -3,7 +3,10 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/50144) in GitLab 11.3.
Interactive web terminals give the user access to a terminal in GitLab for
-running one-off commands for their CI pipeline.
+running one-off commands for their CI pipeline. Since this is giving the user
+shell access to the environment where [GitLab Runner](https://docs.gitlab.com/runner/)
+is deployed, some [security precautions](../../administration/integration/terminal.md#security) were
+taken to protect the users.
NOTE: **Note:**
GitLab.com does not support interactive web terminal at the moment – neither
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index 91a0ae327bf..cf18c6d9660 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -69,7 +69,7 @@ collects the JUnit test report from each job. After each job is executed, the
XML reports are stored in GitLab as artifacts and their results are shown in the
merge request widget.
-NOTE: **Note:**
+NOTE: **Note:**
If you also want the ability to browse JUnit output files, include the
[`artifacts:paths`](yaml/README.md#artifactspaths) keyword.
@@ -151,7 +151,7 @@ There are a few tools that can produce JUnit reports in C/C++.
#### GoogleTest
In the following example, `gtest` is used to generate the test reports.
-If there are multiple gtest executables created for different architectures (`x86`, `x64` or `arm`),
+If there are multiple gtest executables created for different architectures (`x86`, `x64` or `arm`),
you will be required to run each test providing a unique filename. The results
will then be aggregated together.
@@ -171,4 +171,4 @@ Currently, the following tools might not work because their XML formats are unsu
|Case|Tool|Issue|
|---|---|---|
-|`<testcase>` does not have `classname` attribute|ESlint, sass-lint|https://gitlab.com/gitlab-org/gitlab-ce/issues/50964|
+|`<testcase>` does not have `classname` attribute|ESlint, sass-lint|<https://gitlab.com/gitlab-org/gitlab-ce/issues/50964>|
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index f8cf9de9aff..d2a00b9218d 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -241,9 +241,9 @@ So each job would be represented as a `Period`, which consists of
`Period#first` as when the job started and `Period#last` as when the
job was finished. A simple example here would be:
-* A (1, 3)
-* B (2, 4)
-* C (6, 7)
+- A (1, 3)
+- B (2, 4)
+- C (6, 7)
Here A begins from 1, and ends to 3. B begins from 2, and ends to 4.
C begins from 6, and ends to 7. Visually it could be viewed as:
diff --git a/doc/ci/img/view_on_env_blob.png b/doc/ci/review_apps/img/view_on_env_blob.png
index acc457fbb38..acc457fbb38 100644
--- a/doc/ci/img/view_on_env_blob.png
+++ b/doc/ci/review_apps/img/view_on_env_blob.png
Binary files differ
diff --git a/doc/ci/img/view_on_env_mr.png b/doc/ci/review_apps/img/view_on_env_mr.png
index 2c0bd25a4f2..2c0bd25a4f2 100644
--- a/doc/ci/img/view_on_env_mr.png
+++ b/doc/ci/review_apps/img/view_on_env_mr.png
Binary files differ
diff --git a/doc/ci/img/view_on_mr_widget.png b/doc/ci/review_apps/img/view_on_mr_widget.png
index efe023b07b5..efe023b07b5 100644
--- a/doc/ci/img/view_on_mr_widget.png
+++ b/doc/ci/review_apps/img/view_on_mr_widget.png
Binary files differ
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index 64be011008e..8b3a7b63e62 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -102,3 +102,88 @@ The following are example projects that use Review Apps with:
- [OpenShift](https://gitlab.com/gitlab-examples/review-apps-openshift).
See also the video [Demo: Cloud Native Development with GitLab](https://www.youtube.com/watch?v=jfIyQEwrocw), which includes a Review Apps example.
+
+## Route Maps
+
+> Introduced in GitLab 8.17. In GitLab 11.5 the file links
+are surfaced to the merge request widget.
+
+Route Maps allows you to go directly from source files
+to public pages on the [environment](../environments.md) defined for
+Review Apps. Once set up, the review app link in the merge request
+widget can take you directly to the pages changed, making it easier
+and faster to preview proposed modifications.
+
+All you need to do is to tell GitLab how the paths of files
+in your repository map to paths of pages on your website using a Route Map.
+Once set, GitLab will display **View on ...** buttons, which will take you
+to the pages changed directly from merge requests.
+
+To set up a route map, add a a file inside the repository at `.gitlab/route-map.yml`,
+which contains a YAML array that maps `source` paths (in the repository) to `public`
+paths (on the website).
+
+### Route Maps example
+
+Below there's an example of a route map for [Middleman](https://middlemanapp.com),
+a static site generator (SSG) used to build [GitLab's website](https://about.gitlab.com),
+deployed from its [project on GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com):
+
+```yaml
+# Team data
+- source: 'data/team.yml' # data/team.yml
+ public: 'team/' # team/
+
+# Blogposts
+- source: /source\/posts\/([0-9]{4})-([0-9]{2})-([0-9]{2})-(.+?)\..*/ # source/posts/2017-01-30-around-the-world-in-6-releases.html.md.erb
+ public: '\1/\2/\3/\4/' # 2017/01/30/around-the-world-in-6-releases/
+
+# HTML files
+- source: /source\/(.+?\.html).*/ # source/index.html.haml
+ public: '\1' # index.html
+
+# Other files
+- source: /source\/(.*)/ # source/images/blogimages/around-the-world-in-6-releases-cover.png
+ public: '\1' # images/blogimages/around-the-world-in-6-releases-cover.png
+```
+
+Mappings are defined as entries in the root YAML array, and are identified by a `-` prefix. Within an entry, we have a hash map with two keys:
+
+- `source`
+ - a string, starting and ending with `'`, for an exact match
+ - a regular expression, starting and ending with `/`, for a pattern match
+ - The regular expression needs to match the entire source path - `^` and `$` anchors are implied.
+ - Can include capture groups denoted by `()` that can be referred to in the `public` path.
+ - Slashes (`/`) can, but don't have to, be escaped as `\/`.
+ - Literal periods (`.`) should be escaped as `\.`.
+- `public`
+ - a string, starting and ending with `'`.
+ - Can include `\N` expressions to refer to capture groups in the `source` regular expression in order of their occurrence, starting with `\1`.
+
+The public path for a source path is determined by finding the first
+`source` expression that matches it, and returning the corresponding
+`public` path, replacing the `\N` expressions with the values of the
+`()` capture groups if appropriate.
+
+In the example above, the fact that mappings are evaluated in order
+of their definition is used to ensure that `source/index.html.haml`
+will match `/source\/(.+?\.html).*/` instead of `/source\/(.*)/`,
+and will result in a public path of `index.html`, instead of
+`index.html.haml`.
+
+Once you have the route mapping set up, it will be exposed in a few places:
+
+- In the merge request widget. The **View app** button will take you to the
+ environment URL you have set up in `.gitlab-ci.yml`. The dropdown will render
+ the first 5 matched items from the route map, but you can filter them if more
+ than 5 are available.
+
+ ![View app file list in merge request widget](img/view_on_mr_widget.png)
+
+- In the diff for a merge request, comparison, or commit.
+
+ !["View on env" button in merge request diff](img/view_on_env_mr.png)
+
+- In the blob file view.
+
+ !["View on env" button in file view](img/view_on_env_blob.png)
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 9c9ea651678..c742dc61368 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -135,6 +135,14 @@ Consider that if you don't lock your specific Runner to a specific project, any
user with Maintainer role in you project can assign your Runner to another arbitrary
project without requiring your authorization, so use it with caution.
+CAUTION: **Caution:**
+Never add a private Runner that you're using in your private projects to a
+project that you share with other people.
+
+CAUTION: **Caution:**
+Never use a Runner from a project which has multiple maintainers in your
+private project.
+
An admin can enable/disable a specific Runner for projects:
1. Navigate to **Admin > Runners**
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 209a2c15d90..466c5ee5914 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -1,3 +1,7 @@
+---
+table_display_block: true
+---
+
# GitLab CI/CD Variables
When receiving a job from GitLab CI, the [Runner] prepares the build environment.
@@ -80,7 +84,8 @@ future GitLab releases.**
| **CI_MERGE_REQUEST_TARGET_BRANCH_NAME** | 11.6 | all | The target branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
| **CI_NODE_INDEX** | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. |
| **CI_NODE_TOTAL** | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. |
-| **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
+| **CI_API_V4_URL** | 11.7 | all | The GitLab API v4 root URL |
+| **CI_PIPELINE_ID** | 8.10 | all | The unique id of the current pipeline that GitLab CI uses internally |
| **CI_PIPELINE_IID** | 11.0 | all | The unique id of the current pipeline scoped to project |
| **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
@@ -249,6 +254,23 @@ the project services that you are using to learn which variables they define.
An example project service that defines deployment variables is the
[Kubernetes integration](../../user/project/clusters/index.md#deployment-variables).
+## Auto DevOps application variables
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/49056) in GitLab 11.7.
+
+You can configure [Auto DevOps](../../topics/autodevops/index.md) to
+pass CI variables to the running application by prefixing the key of the
+variable with `K8S_SECRET_`.
+
+These [prefixed
+variables](../../topics/autodevops/index.md#application-secret-variables) will
+then be available as environment variables on the running application
+container.
+
+CAUTION: **Caution:**
+Variables with multiline values are not currently supported due to
+limitations with the current Auto DevOps scripting environment.
+
## Debug tracing
> Introduced in GitLab Runner 1.7.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 5d87a5c0a1f..fb69d888b94 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -57,6 +57,7 @@ A job is defined by a list of parameters that define the job behavior.
|---------------|----------|-------------|
| [script](#script) | yes | Defines a shell script which is executed by Runner |
| [extends](#extends) | no | Defines a configuration entry that this job is going to inherit from |
+| [include](#include) | no | Defines a configuration entry that allows this job to include external YAML files |
| [image](#image-and-services) | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
| [services](#image-and-services) | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
| [stage](#stage) | no | Defines a job stage (default: `test`) |
@@ -77,118 +78,6 @@ A job is defined by a list of parameters that define the job behavior.
| [retry](#retry) | no | Define when and how many times a job can be auto-retried in case of a failure |
| [parallel](#parallel) | no | Defines how many instances of a job should be run in parallel |
-### `extends`
-
-> Introduced in GitLab 11.3.
-
-`extends` defines an entry name that a job that uses `extends` is going to
-inherit from.
-
-It is an alternative to using [YAML anchors](#anchors) and is a little
-more flexible and readable:
-
-```yaml
-.tests:
- script: rake test
- stage: test
- only:
- refs:
- - branches
-
-rspec:
- extends: .tests
- script: rake rspec
- only:
- variables:
- - $RSPEC
-```
-
-In the example above, the `rspec` job inherits from the `.tests` template job.
-GitLab will perform a reverse deep merge based on the keys. GitLab will:
-
-- Merge the `rspec` contents into `.tests` recursively.
-- Not merge the values of the keys.
-
-This results in the following `rspec` job:
-
-```yaml
-rspec:
- script: rake rspec
- stage: test
- only:
- refs:
- - branches
- variables:
- - $RSPEC
-```
-
-NOTE: **Note:**
-Note that `script: rake test` has been overwritten by `script: rake rspec`.
-
-If you do want to include the `rake test`, have a look at [before_script-and-after_script](#before_script-and-after_script).
-
-`.tests` in this example is a [hidden key](#hidden-keys-jobs), but it's
-possible to inherit from regular jobs as well.
-
-`extends` supports multi-level inheritance, however it is not recommended to
-use more than three levels. The maximum nesting level that is supported is 10.
-The following example has two levels of inheritance:
-
-```yaml
-.tests:
- only:
- - pushes
-
-.rspec:
- extends: .tests
- script: rake rspec
-
-rspec 1:
- variables:
- RSPEC_SUITE: '1'
- extends: .rspec
-
-rspec 2:
- variables:
- RSPEC_SUITE: '2'
- extends: .rspec
-
-spinach:
- extends: .tests
- script: rake spinach
-```
-
-`extends` works across configuration files combined with [`include`](#include).
-
-### `pages`
-
-`pages` is a special job that is used to upload static content to GitLab that
-can be used to serve your website. It has a special syntax, so the two
-requirements below must be met:
-
-1. Any static content must be placed under a `public/` directory
-1. `artifacts` with a path to the `public/` directory must be defined
-
-The example below simply moves all files from the root of the project to the
-`public/` directory. The `.public` workaround is so `cp` doesn't also copy
-`public/` to itself in an infinite loop:
-
-```yaml
-pages:
- stage: deploy
- script:
- - mkdir .public
- - cp -r * .public
- - mv .public public
- artifacts:
- paths:
- - public
- only:
- - master
-```
-
-Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
-
## `image` and `services`
This allows to specify a custom Docker image and a list of services that can be
@@ -259,7 +148,7 @@ There are also two edge cases worth mentioning:
1. If no `stages` are defined in `.gitlab-ci.yml`, then the `build`,
`test` and `deploy` are allowed to be used as job's stage by default.
-2. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
+1. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
## `stage`
@@ -327,15 +216,15 @@ a "key: value" pair. Be careful when using special characters:
jobs are created:
1. `only` defines the names of branches and tags for which the job will run.
-2. `except` defines the names of branches and tags for which the job will
+1. `except` defines the names of branches and tags for which the job will
**not** run.
There are a few rules that apply to the usage of job policy:
-* `only` and `except` are inclusive. If both `only` and `except` are defined
+- `only` and `except` are inclusive. If both `only` and `except` are defined
in a job specification, the ref is filtered by `only` and `except`.
-* `only` and `except` allow the use of regular expressions (using [Ruby regexp syntax](https://ruby-doc.org/core/Regexp.html)).
-* `only` and `except` allow to specify a repository path to filter jobs for
+- `only` and `except` allow the use of regular expressions (using [Ruby regexp syntax](https://ruby-doc.org/core/Regexp.html)).
+- `only` and `except` allow to specify a repository path to filter jobs for
forks.
In addition, `only` and `except` allow the use of special keywords:
@@ -392,8 +281,8 @@ job:
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except master.
-If a job does not have neither `only` nor `except` rule,
-`only: ['branches', 'tags']` is set by default.
+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.
For example,
@@ -676,9 +565,9 @@ cleanup_job:
The above script will:
1. Execute `cleanup_build_job` only when `build_job` fails.
-2. Always execute `cleanup_job` as the last step in pipeline regardless of
+1. Always execute `cleanup_job` as the last step in pipeline regardless of
success or failure.
-3. Allow you to manually execute `deploy_job` from GitLab's UI.
+1. Allow you to manually execute `deploy_job` from GitLab's UI.
### `when:manual`
@@ -1621,7 +1510,6 @@ Possible values for `when` are:
- `missing_dependency_failure`: Retry if a dependency was missing.
- `runner_unsupported`: Retry if the runner was unsupported.
-
## `parallel`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22631) in GitLab 11.5.
@@ -1644,113 +1532,213 @@ test:
## `include`
-> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.5.
-> Available for Starter, Premium and Ultimate since 10.6.
-> Behaviour expanded in GitLab 10.8 to allow more flexible overriding.
-> [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21603)
-to GitLab Core in 11.4
+> - Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.5.
+> - Available for Starter, Premium and Ultimate since 10.6.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21603) to GitLab Core in 11.4.
Using the `include` keyword, you can allow the inclusion of external YAML files.
+`include` requires the external YAML file to have the extensions `.yml` or `.yaml`,
+otherwise the external file will not be included.
-In the following example, the content of `.before-script-template.yml` will be
-automatically fetched and evaluated along with the content of `.gitlab-ci.yml`:
+The files defined in `include` are:
+
+- Deep merged with those in `.gitlab-ci.yml`.
+- Always evaluated first and merged with the content of `.gitlab-ci.yml`,
+ regardless of the position of the `include` keyword.
+
+TIP: **Tip:**
+Use merging to customize and override included CI/CD configurations with local
+definitions.
+
+Recursive includes are not supported. Your external files should not use the
+`include` keyword as it will be ignored.
+
+NOTE: **Note:**
+Using YAML aliases across different YAML files sourced by `include` is not
+supported. You must only refer to aliases in the same file. Instead
+of using YAML anchors, you can use the [`extends` keyword](#extends).
+
+`include` supports four include methods:
+
+- [`local`](#includelocal)
+- [`file`](#includefile)
+- [`template`](#includetemplate)
+- [`remote`](#includeremote)
+
+See [usage examples](#include-examples).
+
+### `include:local`
+
+`include:local` includes a file from the same repository as `.gitlab-ci.yml`.
+It's referenced using full paths relative to the root directory (`/`).
+
+You can only use files that are currently tracked by Git on the same branch
+your configuration file is on. In other words, when using a `include:local`, make
+sure that both `.gitlab-ci.yml` and the local file are on the same branch.
+
+NOTE: **Note:**
+Including local files through Git submodules paths is not supported.
+
+Example:
```yaml
-# Content of https://gitlab.com/awesome-project/raw/master/.before-script-template.yml
+include:
+ - local: '/templates/.gitlab-ci-template.yml'
+```
-before_script:
- - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
- - gem install bundler --no-document
- - bundle install --jobs $(nproc) "${FLAGS[@]}"
+### `include:file`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53903) in GitLab 11.7.
+
+To include files from another private project under the same GitLab instance,
+use `include:file`. This file is referenced using full paths relative to the
+root directory (`/`). For example:
+
+```yaml
+include:
+ - project: 'my-group/my-project'
+ file: '/templates/.gitlab-ci-template.yml'
```
+You can also specify `ref`, with the default being the `HEAD` of the project:
+
```yaml
-# Content of .gitlab-ci.yml
+include:
+ - project: 'my-group/my-project'
+ ref: master
+ file: '/templates/.gitlab-ci-template.yml'
-include: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - project: 'my-group/my-project'
+ ref: v1.0.0
+ file: '/templates/.gitlab-ci-template.yml'
-rspec:
- script:
- - bundle exec rspec
+ - project: 'my-group/my-project'
+ ref: 787123b47f14b552955ca2786bc9542ae66fee5b # Git SHA
+ file: '/templates/.gitlab-ci-template.yml'
```
-NOTE: **Note:**
-`include` requires the external YAML files to have the extensions `.yml` or `.yaml`.
-The external file will not be included if the extension is missing.
+### `include:template`
-You can define it either as a single string, or, in case you want to include
-more than one files, an array of different values . The following examples
-are both valid cases:
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53445) in GitLab 11.7.
+
+`include:template` can be used to include `.gitlab-ci.yml` templates that are
+[shipped with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates).
+
+For example:
```yaml
-# Single string
+# File sourced from GitLab's template collection
+include:
+ - template: Auto-DevOps.gitlab-ci.yml
+```
-include: '/templates/.after-script-template.yml'
+### `include:remote`
+
+`include:remote` can be used to include a file from a different location,
+using HTTP/HTTPS, referenced by using the full URL. The remote file must be
+publicly accessible through a simple GET request as authentication schemas
+in the remote URL is not supported. For example:
+
+```yaml
+include:
+ - remote: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
```
+NOTE: **Note for GitLab admins:**
+In order to include files from another repository inside your local network,
+you may need to enable the **Allow requests to the local network from hooks and services** checkbox
+located in the **Admin area > Settings > Network > Outbound requests** section.
+
+### `include` examples
+
+Here are a few more `include` examples.
+
+#### Single string or array of multiple values
+
+You can include your extra YAML file(s) either as a single string or
+an array of multiple values. The following examples are all valid.
+
+Single string with the `include:local` method implied:
+
```yaml
-# Array
+include: '/templates/.after-script-template.yml'
+```
+Array with `include` method implied:
+
+```yaml
include:
- 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- '/templates/.after-script-template.yml'
```
----
+Single string with `include` method specified explicitly:
-`include` supports two types of files:
+```yaml
+include:
+ remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+```
-- **local** to the same repository, referenced by using full paths in the same
- repository, with `/` being the root directory. For example:
+Array with `include:remote` being the single item:
- ```yaml
- # Within the repository
- include: '/templates/.gitlab-ci-template.yml'
- ```
+```yaml
+include:
+ - remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+```
- NOTE: **Note:**
- You can only use files that are currently tracked by Git on the same branch
- your configuration file is. In other words, when using a **local file**, make
- sure that both `.gitlab-ci.yml` and the local file are on the same branch.
+Array with multiple `include` methods specified explicitly:
- NOTE: **Note:**
- We don't support the inclusion of local files through Git submodules paths.
+```yaml
+include:
+ - remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - local: '/templates/.after-script-template.yml'
+ - template: Auto-DevOps.gitlab-ci.yml
+```
-- **remote** in a different location, accessed using HTTP/HTTPS, referenced
- using the full URL. For example:
+Array mixed syntax:
- ```yaml
- include: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
- ```
+```yaml
+include:
+ - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - '/templates/.after-script-template.yml'
+ - template: Auto-DevOps.gitlab-ci.yml
+ - project: 'my-group/my-project'
+ ref: master
+ file: '/templates/.gitlab-ci-template.yml'
+```
- NOTE: **Note:**
- The remote file must be publicly accessible through a simple GET request, as we don't support authentication schemas in the remote URL.
+#### Re-using a `before_script` template
- NOTE: **Note:**
- In order to include files from another repository inside your local network,
- you may need to enable the **Allow requests to the local network from hooks and services** checkbox
- located in the **Settings > Network > Outbound requests** section within the **Admin area**.
+In the following example, the content of `.before-script-template.yml` will be
+automatically fetched and evaluated along with the content of `.gitlab-ci.yml`.
----
+Content of `https://gitlab.com/awesome-project/raw/master/.before-script-template.yml`:
+```yaml
+before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - gem install bundler --no-document
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+```
-Since GitLab 10.8 we are now deep merging the files defined in `include`
-with those in `.gitlab-ci.yml`. Files defined by `include` are always
-evaluated first and merged with the content of `.gitlab-ci.yml`, no
-matter the position of the `include` keyword. You can take advantage of
-merging to customize and override details in included CI
-configurations with local definitions.
+Content of `.gitlab-ci.yml`:
-NOTE: **Note:**
-The recursive includes are not supported, meaning your external files
-should not use the `include` keyword, as it will be ignored.
+```yaml
+include: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+
+rspec:
+ script:
+ - bundle exec rspec
+```
+
+#### Overriding external template values
The following example shows specific YAML-defined variables and details of the
`production` job from an include file being customized in `.gitlab-ci.yml`.
-```yaml
-# Content of https://company.com/autodevops-template.yml
+Content of `https://company.com/autodevops-template.yml`:
+```yaml
variables:
POSTGRES_USER: user
POSTGRES_PASSWORD: testing_password
@@ -1768,9 +1756,9 @@ production:
- master
```
-```yaml
-# Content of .gitlab-ci.yml
+Content of `.gitlab-ci.yml`:
+```yaml
include: 'https://company.com/autodevops-template.yml'
image: alpine:latest
@@ -1797,11 +1785,11 @@ with the environment url of the `production` job defined in
The merging lets you extend and override dictionary mappings, but
you cannot add or modify items to an included array. For example, to add
an additional item to the production job script, you must repeat the
-existing script items.
+existing script items:
-```yaml
-# Content of https://company.com/autodevops-template.yml
+Content of `https://company.com/autodevops-template.yml`:
+```yaml
production:
stage: production
script:
@@ -1809,9 +1797,9 @@ production:
- deploy
```
-```yaml
-# Content of .gitlab-ci.yml
+Content of `.gitlab-ci.yml`:
+```yaml
include: 'https://company.com/autodevops-template.yml'
stages:
@@ -1828,10 +1816,140 @@ In this case, if `install_dependencies` and `deploy` were not repeated in
`.gitlab-ci.yml`, they would not be part of the script for the `production`
job in the combined CI configuration.
+## `extends`
+
+> Introduced in GitLab 11.3.
+
+`extends` defines an entry name that a job that uses `extends` is going to
+inherit from.
+
+It is an alternative to using [YAML anchors](#anchors) and is a little
+more flexible and readable:
+
+```yaml
+.tests:
+ script: rake test
+ stage: test
+ only:
+ refs:
+ - branches
+
+rspec:
+ extends: .tests
+ script: rake rspec
+ only:
+ variables:
+ - $RSPEC
+```
+
+In the example above, the `rspec` job inherits from the `.tests` template job.
+GitLab will perform a reverse deep merge based on the keys. GitLab will:
+
+- Merge the `rspec` contents into `.tests` recursively.
+- Not merge the values of the keys.
+
+This results in the following `rspec` job:
+
+```yaml
+rspec:
+ script: rake rspec
+ stage: test
+ only:
+ refs:
+ - branches
+ variables:
+ - $RSPEC
+```
+
NOTE: **Note:**
-We currently do not support using YAML aliases across different YAML files
-sourced by `include`. You must only refer to aliases in the same file. Instead
-of using YAML anchors you can use [`extends` keyword](#extends).
+Note that `script: rake test` has been overwritten by `script: rake rspec`.
+
+If you do want to include the `rake test`, see [`before_script` and `after_script`](#before_script-and-after_script).
+
+`.tests` in this example is a [hidden key](#hidden-keys-jobs), but it's
+possible to inherit from regular jobs as well.
+
+`extends` supports multi-level inheritance, however it is not recommended to
+use more than three levels. The maximum nesting level that is supported is 10.
+The following example has two levels of inheritance:
+
+```yaml
+.tests:
+ only:
+ - pushes
+
+.rspec:
+ extends: .tests
+ script: rake rspec
+
+rspec 1:
+ variables:
+ RSPEC_SUITE: '1'
+ extends: .rspec
+
+rspec 2:
+ variables:
+ RSPEC_SUITE: '2'
+ extends: .rspec
+
+spinach:
+ extends: .tests
+ script: rake spinach
+```
+
+## Using `extends` and `include` together
+
+`extends` works across configuration files combined with `include`.
+
+For example, if you have a local `included.yml` file:
+
+```yaml
+.template:
+ script:
+ - echo Hello!
+```
+
+Then, in `.gitlab-ci.yml` you can use it like this:
+
+```yaml
+include: included.yml
+
+useTemplate:
+ image: alpine
+ extends: .template
+```
+
+This will run a job called `useTemplate` that runs `echo Hello!` as defined in
+the `.template` job, and uses the `alpine` Docker image as defined in the local job.
+
+## `pages`
+
+`pages` is a special job that is used to upload static content to GitLab that
+can be used to serve your website. It has a special syntax, so the two
+requirements below must be met:
+
+- Any static content must be placed under a `public/` directory.
+- `artifacts` with a path to the `public/` directory must be defined.
+
+The example below simply moves all files from the root of the project to the
+`public/` directory. The `.public` workaround is so `cp` doesn't also copy
+`public/` to itself in an infinite loop:
+
+```yaml
+pages:
+ stage: deploy
+ script:
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+```
+
+Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
## `variables`
@@ -1870,9 +1988,9 @@ which can be set in GitLab's UI.
### Git strategy
-> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
- completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner
- v1.7+.
+> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
+> completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner
+> v1.7+.
You can set the `GIT_STRATEGY` used for getting recent application code, either
globally or per-job in the [`variables`](#variables) section. If left
@@ -1908,6 +2026,11 @@ variables:
GIT_STRATEGY: none
```
+NOTE: **Note:** `GIT_STRATEGY` is not supported for
+[Kubernetes executor](https://docs.gitlab.com/runner/executors/kubernetes.html),
+but may be in the future. See the [support Git strategy with Kubernetes executor feature proposal](https://gitlab.com/gitlab-org/gitlab-runner/issues/3847)
+for updates.
+
### Git submodule strategy
> Requires GitLab Runner v1.10+.
@@ -2202,8 +2325,9 @@ capitalization, the commit will be created but the pipeline will be skipped.
Alternatively, one can pass the `ci.skip` [Git push option][push-option] if
using Git 2.10 or newer:
-```
-$ git push -o ci.skip
+
+```sh
+git push -o ci.skip
```
## Validate the .gitlab-ci.yml
diff --git a/doc/development/README.md b/doc/development/README.md
index f22dde32de9..05715274a81 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -47,6 +47,7 @@ description: 'Learn how to contribute to GitLab.'
- [Avoid modules with instance variables](module_with_instance_variables.md) if possible
- [How to dump production data to staging](db_dump.md)
- [Working with the GitHub importer](github_importer.md)
+- [Import/Export development documentation](import_export.md)
- [Working with Merge Request diffs](diffs.md)
- [Permissions](permissions.md)
- [Prometheus metrics](prometheus_metrics.md)
diff --git a/doc/development/adding_database_indexes.md b/doc/development/adding_database_indexes.md
index d1d2b8c4907..c47ce0f1182 100644
--- a/doc/development/adding_database_indexes.md
+++ b/doc/development/adding_database_indexes.md
@@ -113,9 +113,9 @@ the meaning of the various columns can be found at
Because the output of this query relies on the actual usage of your database it
may be affected by factors such as (but not limited to):
-* Certain queries never being executed, thus not being able to use certain
+- Certain queries never being executed, thus not being able to use certain
indexes.
-* Certain tables having little data, resulting in PostgreSQL using sequence
+- Certain tables having little data, resulting in PostgreSQL using sequence
scans instead of index scans.
In other words, this data is only reliable for a frequently used database with
diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md
index ce444ebdde4..4fc38a460f8 100644
--- a/doc/development/api_styleguide.md
+++ b/doc/development/api_styleguide.md
@@ -14,7 +14,7 @@ Always use an [Entity] to present the endpoint's payload.
## Methods and parameters description
Every method must be described using the [Grape DSL](https://github.com/ruby-grape/grape#describing-methods)
-(see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/api/environments.rb
+(see <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/api/environments.rb>
for a good example):
- `desc` for the method summary. You should pass it a block for additional
@@ -49,14 +49,14 @@ end
`params` block. It filters out the params that have been passed, but are not
allowed.
-– https://github.com/ruby-grape/grape#declared
+– <https://github.com/ruby-grape/grape#declared>
### Exclude params from parent namespaces!
> By default `declared(params) `includes parameters that were defined in all
parent namespaces.
-– https://github.com/ruby-grape/grape#include-parent-namespaces
+– <https://github.com/ruby-grape/grape#include-parent-namespaces>
In most cases you will want to exclude params from the parent namespaces:
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index 0cc083cefc0..fed772b9240 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -148,7 +148,7 @@ merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`:
- To cherry-pick multiple commits, such as B and D in a range [A > B > C > D], use:
```shell
- git cherry-pick commmit-B-SHA commit-D-SHA
+ git cherry-pick commit-B-SHA commit-D-SHA
```
For example, suppose commit B SHA = `4f5e4018c09ed797fdf446b3752f82e46f5af502`,
@@ -213,7 +213,7 @@ being able to deploy.
No, not if there is an EE merge request for every CE merge request that causes
conflicts _and_ that EE merge request is merged first. In the past we may have
been a bit more relaxed when it comes to enforcing EE merge requests, but to
-enable automatic merging have to start requiring such merge requests even for
+enable automatic merging we have to start requiring such merge requests even for
the smallest conflicts.
### Some files I work with often conflict, how can I best deal with this?
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index dd4a9e058d7..642dac614c7 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -25,9 +25,9 @@ should only be used for data migrations.
Some examples where background migrations can be useful:
-* Migrating events from one table to multiple separate tables.
-* Populating one column based on JSON stored in another column.
-* Migrating data that depends on the output of external services (e.g. an API).
+- Migrating events from one table to multiple separate tables.
+- Populating one column based on JSON stored in another column.
+- Migrating data that depends on the output of external services (e.g. an API).
## Isolation
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index 9dffca5b99c..7ac846e4c4d 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -97,28 +97,28 @@ When your code contains more than 500 changes, any major breaking changes, or an
This [documentation](issue_workflow.md) outlines the current issue process.
-* [Type labels](issue_workflow.md#type-labels)
-* [Subject labels](issue_workflow.md#subject-labels)
-* [Team labels](issue_workflow.md#team-labels)
-* [Release Scoping labels](issue_workflow.md#release-scoping-labels)
-* [Priority labels](issue_workflow.md#priority-labels)
-* [Severity labels](issue_workflow.md#severity-labels)
-* [Label for community contributors](issue_workflow.md#label-for-community-contributors)
-* [Issue triaging](issue_workflow.md#issue-triaging)
-* [Feature proposals](issue_workflow.md#feature-proposals)
-* [Issue tracker guidelines](issue_workflow.md#issue-tracker-guidelines)
-* [Issue weight](issue_workflow.md#issue-weight)
-* [Regression issues](issue_workflow.md#regression-issues)
-* [Technical and UX debt](issue_workflow.md#technical-and-ux-debt)
-* [Stewardship](issue_workflow.md#stewardship)
+- [Type labels](issue_workflow.md#type-labels)
+- [Subject labels](issue_workflow.md#subject-labels)
+- [Team labels](issue_workflow.md#team-labels)
+- [Release Scoping labels](issue_workflow.md#release-scoping-labels)
+- [Priority labels](issue_workflow.md#priority-labels)
+- [Severity labels](issue_workflow.md#severity-labels)
+- [Label for community contributors](issue_workflow.md#label-for-community-contributors)
+- [Issue triaging](issue_workflow.md#issue-triaging)
+- [Feature proposals](issue_workflow.md#feature-proposals)
+- [Issue tracker guidelines](issue_workflow.md#issue-tracker-guidelines)
+- [Issue weight](issue_workflow.md#issue-weight)
+- [Regression issues](issue_workflow.md#regression-issues)
+- [Technical and UX debt](issue_workflow.md#technical-and-ux-debt)
+- [Stewardship](issue_workflow.md#stewardship)
## Merge requests
This [documentation](merge_request_workflow.md) outlines the current merge request process.
-* [Merge request guidelines](merge_request_workflow.md#merge-request-guidelines)
-* [Contribution acceptance criteria](merge_request_workflow.md#contribution-acceptance-criteria)
-* [Definition of done](merge_request_workflow.md#definition-of-done)
+- [Merge request guidelines](merge_request_workflow.md#merge-request-guidelines)
+- [Contribution acceptance criteria](merge_request_workflow.md#contribution-acceptance-criteria)
+- [Definition of done](merge_request_workflow.md#definition-of-done)
## Style guides
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 7c7da50a149..24feb1378a1 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -6,7 +6,7 @@ scheduling into milestones. Labelling is a task for everyone.
Most issues will have labels for at least one of the following:
-- Type: ~"feature proposal", ~bug, ~customer, etc.
+- Type: ~feature, ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~Plan, ~Manage, ~Quality, etc.
- Stage: ~"devops:plan", ~"devops:create", etc.
@@ -27,8 +27,8 @@ labels, you can _always_ add the team and type, and often also the subject.
Type labels are very important. They define what kind of issue this is. Every
issue should have one or more.
-Examples of type labels are ~"feature proposal", ~bug, ~customer, ~security,
-and ~"direction".
+Examples of type labels are ~feature, ~bug, ~customer, ~security,
+and ~direction.
A number of type labels have a priority assigned to them, which automatically
makes them float to the top, depending on their importance.
@@ -67,7 +67,7 @@ The current team labels are:
- ~Geo
- ~Gitaly
- ~Manage
-- ~Monitoring
+- ~Monitor
- ~Plan
- ~Quality
- ~Release
@@ -200,7 +200,7 @@ We add the ~"Accepting merge requests" label to:
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
solve in the ~"Next Patch Release")
-- Small ~"feature proposal"
+- Small ~feature
- Small ~"technical debt" issues
After adding the ~"Accepting merge requests" label, we try to estimate the
@@ -259,10 +259,10 @@ For feature proposals for EE, open an issue on the
[issue tracker of EE][ee-tracker].
In order to help track the feature proposals, we have created a
-[`feature proposal`][fpl] label. For the time being, users that are not members
+[`feature`][fl] label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team]
-members to add the label ~"feature proposal" to the issue or add the following
-code snippet right after your description in a new line: `~"feature proposal"`.
+members to add the label ~feature to the issue or add the following
+code snippet right after your description in a new line: `~feature`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
@@ -276,7 +276,7 @@ need to ask one of the [core team] members to add the label, if you do not have
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.
-[fpl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature+proposal
+[fl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature
## Issue tracker guidelines
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 6bcee74a3dd..9bef0635e3f 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -171,21 +171,21 @@ the feature you contribute through all of these steps.
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/), if relevant
1. Community questions answered
1. Answers to questions radiated (in docs/wiki/support etc.)
-1. [Black-box tests/end-to-end tests](../testing_guide/testing_levels.md#black-box-tests-or-end-to-end-tests) added if required. Please contact [the quality team](https://about.gitlab.com/handbook/engineering/quality/#teams) with any questions
+1. [Black-box tests/end-to-end tests](../testing_guide/testing_levels.md#black-box-tests-or-end-to-end-tests) added if required. Please contact [the quality team](https://about.gitlab.com/handbook/engineering/quality/#teams) with any questions
If you add a dependency in GitLab (such as an operating system package) please
consider updating the following and note the applicability of each in your
merge request:
-1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
-1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
-1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
-1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
-1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
-1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
+1. Note the addition in the release blog post (create one if it doesn't exist yet) <https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/>
+1. Upgrade guide, for example <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md>
+1. Installation guide <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies>
+1. GitLab Development Kit <https://gitlab.com/gitlab-org/gitlab-development-kit>
+1. Test suite <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh>
+1. Omnibus package creator <https://gitlab.com/gitlab-org/omnibus-gitlab>
[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
-[testing]: ../testing_guide/index.md
+[testing]: ../testing_guide/index.md
---
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 256e0476c2f..828f9bfeec6 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -4,7 +4,7 @@ description: Learn how to contribute to GitLab Documentation.
# GitLab Documentation guidelines
-GitLab's documentation is [intended as the single source of truth (SSOT)](https://about.gitab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions covering every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, as well as the use of GitLab with other applications.
+GitLab's documentation is [intended as the single source of truth (SSOT)](https://about.gitlab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions covering every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, as well as the use of GitLab with other applications.
In addition to this page, the following resources to help craft and contribute documentation are available:
@@ -14,7 +14,7 @@ In addition to this page, the following resources to help craft and contribute d
- [Feature-change documentation workflow](feature-change-workflow.md) - Adding required documentation when developing a GitLab feature.
- [Documentation improvement worflow](improvement-workflow.md) - New content not associated with a new feature.
- [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) - A reference for the markdown implementation used by GitLab's documentation site and about.gitlab.com.
-- [Site architecture](/doc/development/documentation/site_architecture/index.md) - How docs.gitlab.com is built.
+- [Site architecture](site_architecture/index.md) - How docs.gitlab.com is built.
## Source and rendered locations
diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md
index 9d4d2d3a28b..0ce5825fd61 100644
--- a/doc/development/documentation/site_architecture/index.md
+++ b/doc/development/documentation/site_architecture/index.md
@@ -11,7 +11,7 @@ and deploy it to <https://docs.gitlab.com>.
While the source of the documentation content is stored in GitLab's respective product
repositories, the source that is used to build the documentation site _from that content_
-is located at https://gitlab.com/gitlab-com/gitlab-docs. See the README there for
+is located at <https://gitlab.com/gitlab-com/gitlab-docs>. See the README there for
detailed information.
## Assets
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 8760cd194d4..cda66447c2c 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -15,7 +15,7 @@ For programmatic help adhering to the guidelines, see [linting](index.md#linting
## Files
- [Directory structure](index.md#location-and-naming-documents): place the docs
-in the correct location.
+ in the correct location.
- [Documentation files](index.md#documentation-files): name the files accordingly.
DANGER: **Attention:**
@@ -29,12 +29,12 @@ a test that will fail if it spots a new `README.md` file.
### Markdown
The [documentation website](https://docs.gitlab.com) had its markdown engine migrated from [Redcarpet to GitLab Kramdown](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/108)
-in October, 2018.
+in October 2018.
The [`gitlab-kramdown`](https://gitlab.com/gitlab-org/gitlab_kramdown)
gem will support all [GFM markup](../../user/markdown.md) in the future. For now,
use regular markdown markup, following the rules on this style guide. For a complete
-Kramdown reference, check the [GiLab Markdown Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/).
+Kramdown reference, check the [GitLab Markdown Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/).
Use Kramdown markup wisely: do not overuse its specific markup (e.g., `{:.class}`) as it will not render properly in
[`/help`](#gitlab-help).
@@ -45,38 +45,38 @@ yield a useful result, and ensuring content is helpful and easy to consume.
- What to include:
- Any and all helpful information, processes, and tips for implementing,
-using, and troubleshooting GitLab features. [The documentation is the single source of truth](https://about.gitlab.com/handbook/documentation/#documentation-as-single-source-of-truth-ssot)
-for this information.
+ using, and troubleshooting GitLab features. [The documentation is the single source of truth](https://about.gitlab.com/handbook/documentation/#documentation-as-single-source-of-truth-ssot)
+ for this information.
- 'Risky' or niche problem-solving steps. There is no reason to withhold these or
-store them elsewhere; simply include them along with the rest of the docs including all necessary
-detail, such as specific warnings and caveats about potential ramifications.
+ store them elsewhere; simply include them along with the rest of the docs including all necessary
+ detail, such as specific warnings and caveats about potential ramifications.
- Any content types/sources, if relevant to users or admins. You can freely
-include presentations, videos, etc.; no matter who it was originally written for,
-if it is helpful to any of our audiences, we can include it. If an outside source
-that's under copyright, rephrase, or summarize and link out; do not copy and paste.
+ include presentations, videos, etc.; no matter who it was originally written for,
+ if it is helpful to any of our audiences, we can include it. If an outside source
+ that's under copyright, rephrase, or summarize and link out; do not copy and paste.
- All applicable subsections as described on the [structure and template](structure.md) page,
-with files organized in the [correct directory](index.md#documentation-directory-structure).
+ with files organized in the [correct directory](index.md#documentation-directory-structure).
- To ensure discoverability, link to each doc from its higher-level index page and other related pages.
- When referencing other GitLab products and features, link to their
respective docs; when referencing third-party products or technologies,
link out to their external sites, documentation, and resources.
- Do not duplicate information.
- Structure content in alphabetical order in tables, lists, etc., unless there is
-a logical reason not to (for example, when mirroring the UI or an ordered sequence).
+ a logical reason not to (for example, when mirroring the UI or an ordered sequence).
## Language
- Use inclusive language and avoid jargon, as well as uncommon
-words. The docs should be clear and easy to understand.
+ words. The docs should be clear and easy to understand.
- Write in the 3rd person (use "we", "you", "us", "one", instead of "I" or "me").
- Be clear, concise, and stick to the goal of the doc.
- Write in US English.
- Capitalize "G" and "L" in GitLab.
- Use title case when referring to [features](https://about.gitlab.com/features/) or
-[products](https://about.gitlab.com/pricing/) (e.g., GitLab Runner, Geo,
-Issue Boards, GitLab Core, Git, Prometheus, Kubernetes, etc), and methods or methodologies
-(e.g., Continuous Integration, Continuous Deployment, Scrum, Agile, etc). Note that
-some features are also objects (e.g. "GitLab's Merge Requests support X." and "Create a new merge request for Z.").
+ [products](https://about.gitlab.com/pricing/) (e.g., GitLab Runner, Geo,
+ Issue Boards, GitLab Core, Git, Prometheus, Kubernetes, etc), and methods or methodologies
+ (e.g., Continuous Integration, Continuous Deployment, Scrum, Agile, etc). Note that
+ some features are also objects (e.g. "GitLab's Merge Requests support X." and "Create a new merge request for Z.").
## Text
@@ -95,6 +95,20 @@ some features are also objects (e.g. "GitLab's Merge Requests support X." and "C
- List item 2
```
+### Tables overlapping the ToC
+
+By default, all tables have a width of 100% on docs.gitlab.com.
+In a few cases, the table will overlap the table of contents (ToC).
+For these cases, add an entry to the document's frontmatter to
+render them displaying block. This will make sure the table
+is displayed behind the ToC, scrolling horizontally:
+
+```md
+---
+table_display_block: true
+---
+```
+
## Emphasis
- Use double asterisks (`**`) to mark a word or text in bold (`**bold**`).
@@ -127,9 +141,9 @@ Check specific punctuation rules for [list items](#list-items) below.
**Markup:**
-- Use dashes (`- `) for unordered lists instead of asterisks (`* `).
+- Use dashes (`-`) for unordered lists instead of asterisks (`*`).
- Use the number one (`1`) for each item in an ordered list.
-When rendered, the list items will appear with sequential numbering.
+ When rendered, the list items will appear with sequential numbering.
**Punctuation:**
@@ -210,7 +224,7 @@ For other punctuation rules, please refer to the
- Use inline link markdown markup `[Text](https://example.com)`.
It's easier to read, review, and maintain. **Do not** use `[Text][identifier]`.
- To link to internal documentation, use relative links, not full URLs. Use `../` to
- navigate tp high-level directories, and always add the file name `file.md` at the
+ navigate to high-level directories, and always add the file name `file.md` at the
end of the link with the `.md` extension, not `.html`.
Example: instead of `[text](../../merge_requests/)`, use
`[text](../../merge_requests/index.md)` or, `[text](../../ci/README.md)`, or,
@@ -222,16 +236,24 @@ For other punctuation rules, please refer to the
E.g., instead of writing something like `Read more about GitLab Issue Boards [here](LINK)`,
write `Read more about [GitLab Issue Boards](LINK)`.
+### Unlinking emails
+
+By default, all email addresses will render in an email tag on docs.gitlab.com.
+To escape the code block and unlink email addresses, use two backticks:
+
+```md
+`` example@email.com ``
+```
+
## Navigation
To indicate the steps of navigation through the UI:
-
- Use the exact word as shown in the UI, including any capital letters as-is.
- Use bold text for navigation items and the char "greater than" (`>`) as separator
-(e.g., `Navigate to your project's **Settings > CI/CD**` ).
+ (e.g., `Navigate to your project's **Settings > CI/CD**` ).
- If there are any expandable menus, make sure to mention that the user
-needs to expand the tab to find the settings you're referring to (e.g., `Navigate to your project's **Settings > CI/CD** and expand **General pipelines**`).
+ needs to expand the tab to find the settings you're referring to (e.g., `Navigate to your project's **Settings > CI/CD** and expand **General pipelines**`).
## Images
@@ -246,7 +268,7 @@ needs to expand the tab to find the settings you're referring to (e.g., `Navigat
- Compress all images with <https://tinypng.com/> or similar tool.
- Compress gifs with <https://ezgif.com/optimize> or similar tool.
- Images should be used (only when necessary) to _illustrate_ the description
-of a process, not to _replace_ it.
+ of a process, not to _replace_ it.
- Max image size: 100KB (gifs included).
- The GitLab docs do not support videos yet.
@@ -263,30 +285,46 @@ Inside the document:
- If a heading is placed right after an image, always add three dashes (`---`)
between the image and the heading.
+### Remove image shadow
+
+All images displayed on docs.gitlab.com have a box shadow by default.
+To remove the box shadow, use the image class `.image-noshadow` applied
+directly to an HTML `img` tag:
+
+```html
+<img src="path/to/image.jpg" alt="Alt text (required)" class="image-noshadow">
+```
+
## Code blocks
- Always wrap code added to a sentence in inline code blocks (``` ` ```).
-E.g., `.gitlab-ci.yml`, `git add .`, `CODEOWNERS`, `only: master`.
-File names, commands, entries, and anything that refers to code should be added to code blocks.
-To make things easier for the user, always add a full code block for things that can be
-useful to copy and paste, as they can easily do it with the button on code blocks.
+ E.g., `.gitlab-ci.yml`, `git add .`, `CODEOWNERS`, `only: master`.
+ File names, commands, entries, and anything that refers to code should be added to code blocks.
+ To make things easier for the user, always add a full code block for things that can be
+ useful to copy and paste, as they can easily do it with the button on code blocks.
- For regular code blocks, always use a highlighting class corresponding to the
-language for better readability. Examples:
+ language for better readability. Examples:
- ```md
- ```ruby
- Ruby code
- ```
+ ````md
+ ```ruby
+ Ruby code
+ ```
- ```js
- JavaScript code
- ```
+ ```js
+ JavaScript code
+ ```
- ```md
- Markdown code
- ```
- ```
+ ```md
+ Markdown code
+ ```
+ ```text
+ Code for which no specific highlighting class is available.
+ ```
+ ````
+
+- To display raw markdown instead of rendered markdown, use four backticks on their own lines around the
+ markdown to display. See [example](https://gitlab.com/gitlab-org/gitlab-ce/blob/8c1991b9bb7e3b8d606481fdea316d633cfa5eb7/doc/development/documentation/styleguide.md#L275-287).
- For a complete reference on code blocks, check the [Kramdown guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/#code-blocks).
## Alert boxes
@@ -384,12 +422,36 @@ Which renders to:
> ### This is an `h3`
>{:.no_toc}
-## Specific sections and terms
+## Terms
+
+To maintain consistency through GitLab documentation, the following guides documentation authors
+on agreed styles and usage of terms.
-To mention and/or reference specific terms in GitLab, please follow the styles
-below.
+### Describing UI elements
-### GitLab versions and tiers
+The following are styles to follow when describing UI elements on a screen:
+
+- For elements with a visible label, use that label in bold with matching case. For example, `the **Cancel** button`.
+- For elements with a tooltip or hover label, use that label in bold with matching case. For example, `the **Add status emoji** button`.
+
+### Verbs for UI elements
+
+The following are recommended verbs for specific uses.
+
+| Recommended | Used for | Alternatives |
+|:------------|:---------------------------|:---------------------------|
+| "click" | buttons, links, menu items | "hit", "press", "select" |
+| "check" | checkboxes | "enable", "click", "press" |
+| "select" | dropdowns | "pick" |
+| "expand" | expandable sections | "open" |
+
+### Other Verbs
+
+| Recommended | Used for | Alternatives |
+|:------------|:--------------------------------|:-------------------|
+| "go" | making a browser go to location | "navigate", "open" |
+
+## GitLab versions and tiers
- Every piece of documentation that comes with a new feature should declare the
GitLab version that feature got introduced. Right below the heading add a
@@ -414,7 +476,7 @@ below.
> [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
```
-#### Early versions of EE
+### Early versions of EE
If the feature was created before GitLab 9.2 (before [different EE tiers were introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1851)):
@@ -427,7 +489,7 @@ For example:
> [Introduced](<link-to-issue>) in GitLab Enterprise Edition 9.0. Available in [GitLab Premium](https://about.gitlab.com/pricing/).
```
-### Product badges
+## Product badges
When a feature is available in EE-only tiers, add the corresponding tier according to the
feature availability:
@@ -448,18 +510,26 @@ keyword "only":
The tier should be ideally added to headers, so that the full badge will be displayed.
However, it can be also mentioned from paragraphs, list items, and table cells. For these cases,
the tier mention will be represented by an orange question mark that will show the tiers on hover.
-E.g., `**[STARTER]**` renders **[STARTER]**, `**[STARTER ONLY]**` renders **[STARTER ONLY]**.
+
+For example:
+
+- `**[STARTER]**` renders as **[STARTER]**
+- `**[STARTER ONLY]**` renders as **[STARTER ONLY]**
The absence of tiers' mentions mean that the feature is available in GitLab Core,
GitLab.com Free, and all higher tiers.
-#### How it works
+### How it works
Introduced by [!244](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/244),
the special markup `**[STARTER]**` will generate a `span` element to trigger the
badges and tooltips (`<span class="badge-trigger starter">`). When the keyword
"only" is added, the corresponding GitLab.com badge will not be displayed.
+## Specific sections
+
+Certain styles should be applied to specific sections. Styles for specific sections are outlined below.
+
### GitLab Restart
There are many cases that a restart/reconfigure of GitLab is required. To
@@ -536,13 +606,63 @@ the style below as a guide:
In this case:
-- Before each step list the installation method is declared in bold
+- Before each step list the installation method is declared in bold.
- Three dashes (`---`) are used to create a horizontal line and separate the
- two methods
+ two methods.
- The code blocks are indented one or more spaces under the list item to render
- correctly
-- Different highlighting languages are used for each config in the code block
-- The [references](#references) guide is used for reconfigure/restart
+ correctly.
+- Different highlighting languages are used for each config in the code block.
+- The [references](#references) guide is used for reconfigure/restart.
+
+## API
+
+Here is a list of must-have items. Use them in the exact order that appears
+on this document. Further explanation is given below.
+
+- Every method must have the REST API request. For example:
+
+ ```
+ GET /projects/:id/repository/branches
+ ```
+
+- Every method must have a detailed
+ [description of the parameters](#method-description).
+- Every method must have a cURL example.
+- Every method must have a response body (in JSON format).
+
+### API topic template
+
+The following can be used as a template to get started:
+
+````md
+## Descriptive title
+
+One or two sentence description of what endpoint does.
+
+```text
+METHOD /endpoint
+```
+
+| Attribute | Type | Required | Description |
+|:------------|:---------|:---------|:----------------------|
+| `attribute` | datatype | yes/no | Detailed description. |
+| `attribute` | datatype | yes/no | Detailed description. |
+
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/endpoint?parameters'
+```
+
+Example response:
+
+```json
+[
+ {
+ }
+]
+```
+````
### Fake tokens
@@ -553,9 +673,9 @@ low.
You can use the following fake tokens as examples.
-| **Token type** | **Token value** |
+| Token type | Token value |
|:----------------------|:-------------------------------------------------------------------|
-| Private user token | `9koXpg98eAheJpvBs5tK` |
+| Private user token | `<your_access_token>` |
| Personal access token | `n671WNGecHugsdEDPsyo` |
| Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` |
| Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` |
@@ -567,23 +687,7 @@ You can use the following fake tokens as examples.
| Health check token | `Tu7BgjR9qeZTEyRzGG2P` |
| Request profile token | `7VgpS4Ax5utVD2esNstz` |
-### API
-
-Here is a list of must-have items. Use them in the exact order that appears
-on this document. Further explanation is given below.
-
-- Every method must have the REST API request. For example:
-
- ```
- GET /projects/:id/repository/branches
- ```
-
-- Every method must have a detailed
- [description of the parameters](#method-description).
-- Every method must have a cURL example.
-- Every method must have a response body (in JSON format).
-
-#### Method description
+### Method description
Use the following table headers to describe the methods. Attributes should
always be in code blocks using backticks (``` ` ```).
@@ -599,10 +703,10 @@ Rendered example:
|:----------|:-------|:---------|:--------------------|
| `user` | string | yes | The GitLab username |
-#### cURL commands
+### cURL commands
- Use `https://gitlab.example.com/api/v4/` as an endpoint.
-- Wherever needed use this personal access token: `9koXpg98eAheJpvBs5tK`.
+- Wherever needed use this personal access token: `<your_access_token>`.
- Always put the request first. `GET` is the default so you don't have to
include it.
- Use double quotes to the URL when it includes additional parameters.
@@ -611,63 +715,63 @@ Rendered example:
| Methods | Description |
|:-------------------------------------------|:------------------------------------------------------|
-| `-H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"` | Use this method as is, whenever authentication needed |
+| `-H "PRIVATE-TOKEN: <your_access_token>"` | Use this method as is, whenever authentication needed |
| `-X POST` | Use this method when creating new objects |
| `-X PUT` | Use this method when updating existing objects |
| `-X DELETE` | Use this method when removing existing objects |
-#### cURL Examples
+### cURL Examples
Below is a set of [cURL][] examples that you can use in the API documentation.
-##### Simple cURL command
+#### Simple cURL command
Get the details of a group:
```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/gitlab-org
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/gitlab-org
```
-##### cURL example with parameters passed in the URL
+#### cURL example with parameters passed in the URL
Create a new project under the authenticated user's namespace:
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects?name=foo"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects?name=foo"
```
-##### Post data using cURL's --data
+#### Post data using cURL's --data
Instead of using `-X POST` and appending the parameters to the URI, you can use
cURL's `--data` option. The example below will create a new project `foo` under
the authenticated user's namespace.
```bash
-curl --data "name=foo" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects"
+curl --data "name=foo" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects"
```
-##### Post data using JSON content
+#### Post data using JSON content
> **Note:** In this example we create a new group. Watch carefully the single
and double quotes.
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v4/groups
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v4/groups
```
-##### Post data using form-data
+#### Post data using form-data
Instead of using JSON or urlencode you can use multipart/form-data which
properly handles data encoding:
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "title=ssh-key" --form "key=ssh-rsa AAAAB3NzaC1yc2EA..." https://gitlab.example.com/api/v4/users/25/keys
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "title=ssh-key" --form "key=ssh-rsa AAAAB3NzaC1yc2EA..." https://gitlab.example.com/api/v4/users/25/keys
```
The above example is run by and administrator and will add an SSH public key
titled ssh-key to user's account which has an id of 25.
-##### Escape special characters
+#### Escape special characters
Spaces or slashes (`/`) may sometimes result to errors, thus it is recommended
to escape them when possible. In the example below we create a new issue which
@@ -675,19 +779,19 @@ contains spaces in its title. Observe how spaces are escaped using the `%20`
ASCII code.
```bash
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/42/issues?title=Hello%20Dude"
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/42/issues?title=Hello%20Dude"
```
Use `%2F` for slashes (`/`).
-##### Pass arrays to API calls
+#### Pass arrays to API calls
The GitLab API sometimes accepts arrays of strings or integers. For example, to
restrict the sign-up e-mail domains of a GitLab instance to `*.example.com` and
`example.net`, you would do something like this:
```bash
-curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain_whitelist[]=*.example.com" --data "domain_whitelist[]=example.net" https://gitlab.example.com/api/v4/application/settings
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --data "domain_whitelist[]=*.example.com" --data "domain_whitelist[]=example.net" https://gitlab.example.com/api/v4/application/settings
```
[cURL]: http://curl.haxx.se/ "cURL website"
diff --git a/doc/development/emails.md b/doc/development/emails.md
index 35ada35babe..8baf343b133 100644
--- a/doc/development/emails.md
+++ b/doc/development/emails.md
@@ -76,14 +76,32 @@ See the [Rails guides] for more info.
## Email namespace
-If you need to implement a new feature which requires a new email handler, follow these rules:
+As of GitLab 11.7, we support a new format for email handler addresses. This was done to
+support catch-all mailboxes.
- - You must choose a namespace. The namespace cannot contain `/` or `+`, and cannot match `\h{16}`.
- - If your feature is related to a project, you will append the namespace **after** the project path, separated by a `+`
- - If you have different actions in the namespace, you add the actions **after** the namespace separated by a `+`. The action name cannot contain `/` or `+`, , and cannot match `\h{16}`.
- - You will register your handlers in `lib/gitlab/email/handler.rb`
+If you need to implement a feature which requires a new email handler, follow these rules
+for the format of the email key:
-Therefore, these are the only valid formats for an email handler:
+- Actions are always at the end, separated by `-`. For example `-issue` or `-merge-request`
+- If your feature is related to a project, the key begins with the project identifiers (project path slug
+ and project id), separated by `-`. For example, `gitlab-org-gitlab-ce-20`
+- Additional information, such as an author's token, can be added between the project identifiers and
+ the action, separated by `-`. For example, `gitlab-org-gitlab-ce-20-Author_Token12345678-issue`
+- You register your handlers in `lib/gitlab/email/handler.rb`
+
+Examples of valid email keys:
+
+ - `gitlab-org-gitlab-ce-20-Author_Token12345678-issue` (create a new issue)
+ - `gitlab-org-gitlab-ce-20-Author_Token12345678-merge-request` (create a new merge request)
+ - `1234567890abcdef1234567890abcdef-unsubscribe` (unsubscribe from a conversation)
+ - `1234567890abcdef1234567890abcdef` (reply to a conversation)
+
+Please note that the action `-issue-` is used in GitLab Premium as the handler for the Service Desk feature.
+
+### Legacy format
+
+Although we continue to support the older legacy format, no new features should use a legacy format.
+These are the only valid legacy formats for an email handler:
- `path/to/project+namespace`
- `path/to/project+namespace+action`
diff --git a/doc/development/fe_guide/components.md b/doc/development/fe_guide/components.md
index 0e9126ee667..52462a4bec9 100644
--- a/doc/development/fe_guide/components.md
+++ b/doc/development/fe_guide/components.md
@@ -1,18 +1,19 @@
# Components
## Contents
-* [Dropdowns](#dropdowns)
-* [Modals](#modals)
+
+- [Dropdowns](#dropdowns)
+- [Modals](#modals)
## Dropdowns
See also the [corresponding UX guide](https://design.gitlab.com/#/components/dropdowns).
### How to style a bootstrap dropdown
+
1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
1. Add a specific class to the top level `.dropdown` element
-
```Haml
.dropdown.my-dropdown
%button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
diff --git a/doc/development/fe_guide/droplab/droplab.md b/doc/development/fe_guide/droplab/droplab.md
index ce96a9fc8ae..e6aa0be671f 100644
--- a/doc/development/fe_guide/droplab/droplab.md
+++ b/doc/development/fe_guide/droplab/droplab.md
@@ -177,28 +177,28 @@ droplab.init().addData('trigger', [{
DropLab adds some CSS classes to help lower the barrier to integration.
-For example,
+For example:
-* The `droplab-item-selected` css class is added to items that have been selected
-either by a mouse click or by enter key selection.
-* The `droplab-item-active` css class is added to items that have been selected
-using arrow key navigation.
-* You can add the `droplab-item-ignore` css class to any item that you do not want to be selectable. For example,
-an `<li class="divider"></li>` list divider element that should not be interactive.
+- The `droplab-item-selected` css class is added to items that have been selected
+ either by a mouse click or by enter key selection.
+- The `droplab-item-active` css class is added to items that have been selected
+ using arrow key navigation.
+- You can add the `droplab-item-ignore` css class to any item that you do not want to be selectable. For example,
+ an `<li class="divider"></li>` list divider element that should not be interactive.
## Internal events
DropLab uses some custom events to help lower the barrier to integration.
-For example,
+For example:
-* The `click.dl` event is fired when an `li` list item has been clicked. It is also
-fired when a list item has been selected with the keyboard. It is also fired when a
-`HookButton` button is clicked (a registered `button` tag or `a` tag trigger).
-* The `input.dl` event is fired when a `HookInput` (a registered `input` tag trigger) triggers an `input` event.
-* The `mousedown.dl` event is fired when a `HookInput` triggers a `mousedown` event.
-* The `keyup.dl` event is fired when a `HookInput` triggers a `keyup` event.
-* The `keydown.dl` event is fired when a `HookInput` triggers a `keydown` event.
+- The `click.dl` event is fired when an `li` list item has been clicked. It is also
+ fired when a list item has been selected with the keyboard. It is also fired when a
+ `HookButton` button is clicked (a registered `button` tag or `a` tag trigger).
+- The `input.dl` event is fired when a `HookInput` (a registered `input` tag trigger) triggers an `input` event.
+- The `mousedown.dl` event is fired when a `HookInput` triggers a `mousedown` event.
+- The `keyup.dl` event is fired when a `HookInput` triggers a `keyup` event.
+- The `keydown.dl` event is fired when a `HookInput` triggers a `keydown` event.
These custom events add a `detail` object to the vanilla `Event` object that provides some potentially useful data.
@@ -233,9 +233,9 @@ droplab.init(trigger, list, [droplabAjax], {
### Documentation
-* [Ajax plugin](plugins/ajax.md)
-* [Filter plugin](plugins/filter.md)
-* [InputSetter plugin](plugins/input_setter.md)
+- [Ajax plugin](plugins/ajax.md)
+- [Filter plugin](plugins/filter.md)
+- [InputSetter plugin](plugins/input_setter.md)
### Development
diff --git a/doc/development/fe_guide/droplab/plugins/ajax.md b/doc/development/fe_guide/droplab/plugins/ajax.md
index 9c7e56fa448..b6a883ce6c4 100644
--- a/doc/development/fe_guide/droplab/plugins/ajax.md
+++ b/doc/development/fe_guide/droplab/plugins/ajax.md
@@ -8,10 +8,10 @@ Add the `Ajax` object to the plugins array of a `DropLab.prototype.init` or `Dro
`Ajax` requires 2 config values, the `endpoint` and `method`.
-* `endpoint` should be a URL to the request endpoint.
-* `method` should be `setData` or `addData`.
-* `setData` completely replaces the dropdown with the response data.
-* `addData` appends the response data to the current dropdown list.
+- `endpoint` should be a URL to the request endpoint.
+- `method` should be `setData` or `addData`.
+- `setData` completely replaces the dropdown with the response data.
+- `addData` appends the response data to the current dropdown list.
```html
<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a>
diff --git a/doc/development/fe_guide/droplab/plugins/filter.md b/doc/development/fe_guide/droplab/plugins/filter.md
index 0853ea4d320..ddc6a3386c7 100644
--- a/doc/development/fe_guide/droplab/plugins/filter.md
+++ b/doc/development/fe_guide/droplab/plugins/filter.md
@@ -7,8 +7,8 @@ to the dropdown using a simple fuzzy string search of an input value.
Add the `Filter` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call.
-* `Filter` requires a config value for `template`.
-* `template` should be the key of the objects within your data array that you want to compare
+- `Filter` requires a config value for `template`.
+- `template` should be the key of the objects within your data array that you want to compare
to the user input string, for filtering.
```html
diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md
index 94f3c8254a8..8e28a41f32e 100644
--- a/doc/development/fe_guide/droplab/plugins/input_setter.md
+++ b/doc/development/fe_guide/droplab/plugins/input_setter.md
@@ -6,9 +6,9 @@
Add the `InputSetter` object to the plugins array of a `DropLab.prototype.init` or `DropLab.prototype.addHook` call.
-* `InputSetter` requires a config value for `input` and `valueAttribute`.
-* `input` should be the DOM element that you want to manipulate.
-* `valueAttribute` should be a string that is the name of an attribute on your list items that is used to get the value
+- `InputSetter` requires a config value for `input` and `valueAttribute`.
+- `input` should be the DOM element that you want to manipulate.
+- `valueAttribute` should be a string that is the name of an attribute on your list items that is used to get the value
to update the `input` element with.
You can also set the `InputSetter` config to an array of objects, which will allow you to update multiple elements.
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index cca3ad6fae6..3a3cb77f592 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -21,8 +21,8 @@ We also use [Axios][axios] to handle all of our network requests.
We also utilize [webpack][webpack] to handle the bundling, minification, and
compression of our assets.
-Working with our frontend assets requires Node (v6.0 or greater) and Yarn
-(v1.2 or greater). You can find information on how to install these on our
+Working with our frontend assets requires Node (v8.10.0 or greater) and Yarn
+(v1.10.0 or greater). You can find information on how to install these on our
[installation guide][install].
### Browser Support
@@ -118,7 +118,7 @@ The [externalization part of the guide](../i18n/externalization.md) explains the
## [DropLab](droplab/droplab.md)
Our internal `DropLab` dropdown library.
-* [DropLab](droplab/droplab.md)
-* [Ajax plugin](droplab/plugins/ajax.md)
-* [Filter plugin](droplab/plugins/filter.md)
-* [InputSetter plugin](droplab/plugins/input_setter.md)
+- [DropLab](droplab/droplab.md)
+- [Ajax plugin](droplab/plugins/ajax.md)
+- [Filter plugin](droplab/plugins/filter.md)
+- [InputSetter plugin](droplab/plugins/input_setter.md)
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index ef0eed786d2..e5a383c25f5 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -29,8 +29,8 @@ To improve the time to first render we are using lazy loading for images. This w
the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded,
the value of `data-src` will be moved to `src` automatically if the image is in the current viewport.
-* Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`
-* If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
+- Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`.
+- If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
If you are asynchronously adding content which contains lazy images then you need to call the function
`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed.
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index ccfd465531a..9c614e3468a 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -6,9 +6,9 @@ To get started with Vue, read through [their documentation][vue-docs].
What is described in the following sections can be found in these examples:
-- web ide: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/ide/stores
-- security products: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports
-- registry: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/registry/stores
+- web ide: <https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/ide/stores>
+- security products: <https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports>
+- registry: <https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/registry/stores>
## Vue architecture
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index 65963b959f7..4b60ec80cb8 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -120,8 +120,8 @@ create:
1. An action `receiveSomethingError`, to handle the error callback
1. An action `fetchSomething` to make the request.
1. In case your application does more than a `GET` request you can use these as examples:
- - `PUT`: `createSomething`
- - `POST`: `updateSomething`
+ - `POST`: `createSomething`
+ - `PUT`: `updateSomething`
- `DELETE`: `deleteSomething`
The component MUST only dispatch the `fetchNamespace` action. Actions namespaced with `request` or `receive` should not be called from the component
diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md
index 6e014e8c751..b90dc90e424 100644
--- a/doc/development/file_storage.md
+++ b/doc/development/file_storage.md
@@ -4,15 +4,15 @@ We use the [CarrierWave] gem to handle file upload, store and retrieval.
There are many places where file uploading is used, according to contexts:
-* System
+- System
- Instance Logo (logo visible in sign in/sign up pages)
- Header Logo (one displayed in the navigation bar)
-* Group
+- Group
- Group avatars
-* User
+- User
- User avatars
- User snippet attachments
-* Project
+- Project
- Project avatars
- Issues/MR/Notes Markdown attachments
- Issues/MR/Notes Legacy Markdown attachments
@@ -52,7 +52,7 @@ hash of the project ID instead, if project migrates to the new approach (introdu
### Path segments
-Files are stored at multiple locations and use different path schemes.
+Files are stored at multiple locations and use different path schemes.
All the `GitlabUploader` derived classes should comply with this path segment schema:
```
@@ -61,7 +61,7 @@ All the `GitlabUploader` derived classes should comply with this path segment sc
| `<gitlab_root>/public/` | `uploads/-/system/` | `user/avatar/:id/` | `:filename` |
| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
| `CarrierWave.root` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` |
-| | `CarrierWave::Uploader#store_dir` | |
+| | `CarrierWave::Uploader#store_dir` | |
| FileUploader
| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
@@ -69,7 +69,7 @@ All the `GitlabUploader` derived classes should comply with this path segment sc
| `<gitlab_root>/shared/` | `snippets/` | `:secret/` | `:filename` |
| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- |
| `CarrierWave.root` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` |
-| | `CarrierWave::Uploader#store_dir` | |
+| | `CarrierWave::Uploader#store_dir` | |
| | | `FileUploader#upload_path |
| ObjectStore::Concern (store = remote)
@@ -77,7 +77,7 @@ All the `GitlabUploader` derived classes should comply with this path segment sc
| `<bucket_name>` | <ignored> | `user/avatar/:id/` | `:filename` |
| ----------------------- + ------------------------- + ----------------------------------- + -------------------------------- |
| `#fog_dir` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` |
-| | | `ObjectStorage::Concern#store_dir` | |
+| | | `ObjectStorage::Concern#store_dir` | |
| | | `ObjectStorage::Concern#upload_path |
```
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index 32beafad307..d5fc403bf8b 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -128,7 +128,26 @@ to manually run `make` again.
Note that CI tests will not use your locally modified version of
Gitaly. To use a custom Gitaly version in CI you need to update
GITALY_SERVER_VERSION. You can use the format `=revision` to use a
-non-tagged commit from https://gitlab.com/gitlab-org/gitaly in CI.
+non-tagged commit from <https://gitlab.com/gitlab-org/gitaly> in CI.
+
+To use a different Gitaly repository, e.g., if your changes are present
+on a fork, you can specify a `GITALY_REPO_URL` environment variable when
+running tests:
+
+```shell
+GITALY_REPO_URL=https://gitlab.com/nick.thomas/gitaly bundle exec rspec spec/lib/gitlab/git/repository_spec.rb
+```
+
+If your fork of Gitaly is private, you can generate a [Deploy Token](../user/project/deploy_tokens/index.md)
+and specify it in the URL:
+
+```shell
+GITALY_REPO_URL=https://gitlab+deploy-token-1000:token-here@gitlab.com/nick.thomas/gitaly bundle exec rspec spec/lib/gitlab/git/repository_spec.rb
+```
+
+To use a custom Gitaly repository in CI, for instance if you want your
+GitLab fork to always use your own Gitaly fork, set `GITALY_REPO_URL`
+as a [CI environment variable](../ci/variables/README.md#variables).
---
diff --git a/doc/development/github_importer.md b/doc/development/github_importer.md
index 863ac049db6..5445f36b9fa 100644
--- a/doc/development/github_importer.md
+++ b/doc/development/github_importer.md
@@ -13,20 +13,20 @@ or Rake tasks. The parallel importer on the other hand uses Sidekiq.
## Requirements
-* GitLab CE 10.2.0 or newer.
-* Sidekiq workers that process the `github_importer` and
+- GitLab CE 10.2.0 or newer.
+- Sidekiq workers that process the `github_importer` and
`github_importer_advance_stage` queues (this is enabled by default).
-* Octokit (used for interacting with the GitHub API)
+- Octokit (used for interacting with the GitHub API).
## Code structure
The importer's codebase is broken up into the following directories:
-* `lib/gitlab/github_import`: this directory contains most of the code such as
+- `lib/gitlab/github_import`: this directory contains most of the code such as
the classes used for importing resources.
-* `app/workers/gitlab/github_import`: this directory contains the Sidekiq
+- `app/workers/gitlab/github_import`: this directory contains the Sidekiq
workers.
-* `app/workers/concerns/gitlab/github_import`: this directory contains a few
+- `app/workers/concerns/gitlab/github_import`: this directory contains a few
modules reused by the various Sidekiq workers.
## Architecture overview
@@ -188,8 +188,8 @@ projects get imported the fewer GitHub API calls will be needed.
The code for this resides in:
-* `lib/gitlab/github_import/user_finder.rb`
-* `lib/gitlab/github_import/caching.rb`
+- `lib/gitlab/github_import/user_finder.rb`
+- `lib/gitlab/github_import/caching.rb`
## Mapping labels and milestones
@@ -204,6 +204,6 @@ project that is being imported.
The code for this resides in:
-* `lib/gitlab/github_import/label_finder.rb`
-* `lib/gitlab/github_import/milestone_finder.rb`
-* `lib/gitlab/github_import/caching.rb`
+- `lib/gitlab/github_import/label_finder.rb`
+- `lib/gitlab/github_import/milestone_finder.rb`
+- `lib/gitlab/github_import/caching.rb`
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index a5a34d82bec..1b9ebb50c29 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -96,8 +96,8 @@ end
### Why
-* Because it is not isolated therefore it might be broken at times.
-* Because it doesn't work whenever the method we want to stub was defined
+- Because it is not isolated therefore it might be broken at times.
+- Because it doesn't work whenever the method we want to stub was defined
in a prepended module, which is very likely the case in EE. We could see
error like this:
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
new file mode 100644
index 00000000000..71db1abb201
--- /dev/null
+++ b/doc/development/import_export.md
@@ -0,0 +1,352 @@
+# Import/Export development documentation
+
+Troubleshooing and general development guidelines and tips for the [Import/Export feature](../user/project/settings/import_export.md).
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> This document is originally based on the [Import/Export 201 presentation available on YouTube](https://www.youtube.com/watch?v=V3i1OfExotE).
+
+## Troubleshooting commands
+
+Finds information about the status of the import and further logs using the JID:
+
+```ruby
+# Rails console
+Project.find_by_full_path('group/project').import_state.slice(:jid, :status, :last_error)
+> {"jid"=>"414dec93f941a593ea1a6894", "status"=>"finished", "last_error"=>nil}
+```
+
+```bash
+# Logs
+grep JID /var/log/gitlab/sidekiq/current
+grep "Import/Export error" /var/log/gitlab/sidekiq/current
+grep "Import/Export backtrace" /var/log/gitlab/sidekiq/current
+```
+
+## Troubleshooting performance issues
+
+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):
+
+```bash
+SIDEKIQ_MEMORY_KILLER_MAX_RSS = 2GB in GitLab.com
+```
+
+An import status `started`, and the following sidekiq logs will signal a memory issue:
+
+```bash
+WARN: Work still in progress <struct with JID>
+```
+
+### Timeouts
+
+Timeout errors occur due to the `StuckImportJobsWorker` marking the process as failed:
+
+```ruby
+class StuckImportJobsWorker
+ include ApplicationWorker
+ include CronjobQueue
+
+ IMPORT_JOBS_EXPIRATION = 15.hours.to_i
+
+ def perform
+ import_state_without_jid_count = mark_import_states_without_jid_as_failed!
+ import_state_with_jid_count = mark_import_states_with_jid_as_failed!
+ ...
+```
+
+```bash
+Marked stuck import jobs as failed. JIDs: xyz
+```
+
+```
+ +-----------+ +-----------------------------------+
+ |Export Job |--->| Calls ActiveRecord `as_json` and |
+ +-----------+ | `to_json` on all project models |
+ +-----------------------------------+
+
+ +-----------+ +-----------------------------------+
+ |Import Job |--->| Loads all JSON in memory, then |
+ +-----------+ | inserts into the DB in batches |
+ +-----------------------------------+
+```
+
+### Problems and solutions
+
+| Problem | Possible solutions |
+| -------- | -------- |
+| [Slow JSON](https://gitlab.com/gitlab-org/gitlab-ce/issues/54084) loading/dumping models from the database | [split the worker](https://gitlab.com/gitlab-org/gitlab-ce/issues/54085) |
+| | Batch export
+| | Optimize SQL
+| | Move away from `ActiveRecord` callbacks (difficult)
+| High memory usage (see also some [analysis](https://gitlab.com/gitlab-org/gitlab-ce/issues/35389) | DB Commit sweet spot that uses less memory |
+| | [Netflix Fast JSON API](https://github.com/Netflix/fast_jsonapi) may help |
+| | Batch reading/writing to disk and any SQL
+
+### Temporary solutions
+
+While the performance problems are not tackled, there is a process to workaround
+importing big projects, using a foreground import:
+
+[Foreground import](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5384) of big projects for customers.
+(Using the import template in the [infrastructure tracker](https://gitlab.com/gitlab-com/gl-infra/infrastructure/))
+
+## Security
+
+The Import/Export feature is constantly updated (adding new things to export), however
+the code hasn't been refactored in a long time. We should perform a [code audit](https://gitlab.com/gitlab-org/gitlab-ce/issues/42135)
+to make sure its dynamic nature does not increase the number of security concerns.
+
+### Security in the code
+
+Some of these classes provide a layer of security to the Import/Export.
+
+The `AttributeCleaner` removes any prohibited keys:
+
+```ruby
+# AttributeCleaner
+# Removes all `_ids` and other prohibited keys
+ class AttributeCleaner
+ ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
+
+ def clean
+ @relation_hash.reject do |key, _value|
+ prohibited_key?(key) || !@relation_class.attribute_method?(key) || excluded_key?(key)
+ end.except('id')
+ end
+
+ ...
+
+```
+
+The `AttributeConfigurationSpec` checks and confirms the addition of new columns:
+
+```ruby
+# AttributeConfigurationSpec
+<<-MSG
+ It looks like #{relation_class}, which is exported using the project Import/Export, has new attributes:
+
+ Please add the attribute(s) to SAFE_MODEL_ATTRIBUTES if you consider this can be exported.
+ Otherwise, please blacklist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its correspondent
+ model in the +excluded_attributes+ section.
+
+ SAFE_MODEL_ATTRIBUTES: #{File.expand_path(safe_attributes_file)}
+ IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+MSG
+```
+
+The `ModelConfigurationSpec` checks and confirms the addition of new models:
+
+```ruby
+# ModelConfigurationSpec
+<<-MSG
+ New model(s) <#{new_models.join(',')}> have been added, related to #{parent_model_name}, which is exported by
+ the Import/Export feature.
+
+ If you think this model should be included in the export, please add it to `#{Gitlab::ImportExport.config_file}`.
+
+ Definitely add it to `#{File.expand_path(ce_models_yml)}`
+ #{"or `#{File.expand_path(ee_models_yml)}` if the model/associations are EE-specific\n" if ee_models_hash.any?}
+ to signal that you've handled this error and to prevent it from showing up in the future.
+MSG
+```
+
+The `ExportFileSpec` detects encrypted or sensitive columns:
+
+```ruby
+# ExportFileSpec
+<<-MSG
+ Found a new sensitive word <#{key_found}>, which is part of the hash #{parent.inspect}
+ If you think this information shouldn't get exported, please exclude the model or attribute in
+ IMPORT_EXPORT_CONFIG.
+
+ Otherwise, please add the exception to +safe_list+ in CURRENT_SPEC using #{sensitive_word} as the
+ key and the correspondent hash or model as the value.
+
+ Also, if the attribute is a generated unique token, please add it to RelationFactory::TOKEN_RESET_MODELS
+ if it needs to be reset (to prevent duplicate column problems while importing to the same instance).
+
+ IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+ CURRENT_SPEC: #{__FILE__}
+MSG
+```
+
+## Versioning
+
+Import/Export does not use strict SemVer, since it has frequent constant changes
+during a single GitLab release. It does require an update when there is a breaking change.
+
+```ruby
+# ImportExport
+module Gitlab
+ module ImportExport
+ extend self
+
+ # For every version update, the version history in import_export.md has to be kept up to date.
+ VERSION = '0.2.4'
+```
+
+## Version history
+
+The [current version history](../user/project/settings/import_export.md) also displays the equivalent GitLab version
+and it is useful for knowing which versions won't be compatible between them.
+
+| GitLab version | Import/Export version |
+| ---------------- | --------------------- |
+| 11.1 to current | 0.2.4 |
+| 10.8 | 0.2.3 |
+| 10.4 | 0.2.2 |
+| ... | ... |
+| 8.10.3 | 0.1.3 |
+| 8.10.0 | 0.1.2 |
+| 8.9.5 | 0.1.1 |
+| 8.9.0 | 0.1.0 |
+
+### When to bump the version up
+
+We will have to bump the verision if we rename model/columns or perform any format
+modifications in the JSON structure or the file structure of the archive file.
+
+We do not need to bump the version up in any of the following cases:
+
+- Add a new column or a model
+- Remove a column or model (unless there is a DB constraint)
+- Export new things (such as a new type of upload)
+
+
+Every time we bump the version, the integration specs will fail and can be fixed with:
+
+```bash
+bundle exec rake gitlab:import_export:bump_version
+```
+
+### Renaming columns or models
+
+This is a relatively common occurence that will require a version bump.
+
+There is also the _RC problem_ - GitLab.com runs an RC, prior to any customers,
+meaning that we want to bump the version up in the next version (or patch release).
+
+For example:
+
+1. Add rename to `RelationRenameService` in X.Y
+2. Remove it from `RelationRenameService` in X.Y + 1
+3. Bump Import/Export version in X.Y + 1
+
+```ruby
+module Gitlab
+ module ImportExport
+ class RelationRenameService
+ RENAMES = {
+ 'pipelines' => 'ci_pipelines' # Added in 11.6, remove in 11.7
+ }.freeze
+```
+
+## A quick dive into the code
+
+### Import/Export configuration (`import_export.yml`)
+
+The main configuration `import_export.yml` defines what models can be exported/imported.
+
+Model relationships to be included in the project import/export:
+
+```yaml
+project_tree:
+ - labels:
+ :priorities
+ - milestones:
+ - events:
+ - :push_event_payload
+ - issues:
+ - events:
+ - ...
+```
+
+Only include the following attributes for the models specified:
+
+```yaml
+included_attributes:
+ user:
+ - :id
+ - :email
+ ...
+
+```
+
+Do not include the following attributes for the models specified:
+
+```yaml
+excluded_attributes:
+ project:
+ - :name
+ - :path
+ - ...
+```
+
+Extra methods to be called by the export:
+
+```yaml
+# Methods
+methods:
+ labels:
+ - :type
+ label:
+ - :type
+```
+
+### Import
+
+The import job status moves from `none` to `finished` or `failed` into different states:
+
+_import\_status_: none -> scheduled -> started -> finished/failed
+
+While the status is `started` the `Importer` code processes each step required for the import.
+
+```ruby
+# ImportExport::Importer
+module Gitlab
+ module ImportExport
+ class Importer
+ def execute
+ if import_file && check_version! && restorers.all?(&:restore) && overwrite_project
+ project_tree.restored_project
+ else
+ raise Projects::ImportService::Error.new(@shared.errors.join(', '))
+ end
+ rescue => e
+ raise Projects::ImportService::Error.new(e.message)
+ ensure
+ remove_import_file
+ end
+
+ def restorers
+ [repo_restorer, wiki_restorer, project_tree, avatar_restorer,
+ uploads_restorer, lfs_restorer, statistics_restorer]
+ end
+```
+
+The export service, is similar to the `Importer`, restoring data instead of saving it.
+
+### Export
+
+```ruby
+# ImportExport::ExportService
+module Projects
+ module ImportExport
+ class ExportService < BaseService
+
+ def save_all!
+ if save_services
+ Gitlab::ImportExport::Saver.save(project: project, shared: @shared)
+ notify_success
+ else
+ cleanup_and_notify_error!
+ end
+ end
+
+ def save_services
+ [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver,
+ wiki_repo_saver, lfs_saver].all?(&:save)
+ end
+```
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index a36dc6424a7..5f95cf3707c 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -11,12 +11,12 @@ Instrumenting methods is done by using the `Gitlab::Metrics::Instrumentation`
module. This module offers a few different methods that can be used to
instrument code:
-* `instrument_method`: instruments a single class method.
-* `instrument_instance_method`: instruments a single instance method.
-* `instrument_class_hierarchy`: given a Class this method will recursively
+- `instrument_method`: instruments a single class method.
+- `instrument_instance_method`: instruments a single instance method.
+- `instrument_class_hierarchy`: given a Class this method will recursively
instrument all sub-classes (both class and instance methods).
-* `instrument_methods`: instruments all public and private class methods of a Module.
-* `instrument_instance_methods`: instruments all public and private instance methods of a
+- `instrument_methods`: instruments all public and private class methods of a Module.
+- `instrument_instance_methods`: instruments all public and private instance methods of a
Module.
To remove the need for typing the full `Gitlab::Metrics::Instrumentation`
diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md
index ee01c89e0ed..6f1baad3a2d 100644
--- a/doc/development/merge_request_performance_guidelines.md
+++ b/doc/development/merge_request_performance_guidelines.md
@@ -9,8 +9,8 @@ To measure the impact of a merge request you can use
[Sherlock](profiling.md#sherlock). It's also highly recommended that you read
the following guides:
-* [Performance Guidelines](performance.md)
-* [What requires downtime?](what_requires_downtime.md)
+- [Performance Guidelines](performance.md)
+- [What requires downtime?](what_requires_downtime.md)
## Impact Analysis
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index d0a054c3290..98b54684d39 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -40,7 +40,7 @@ migrations _unless_ schema changes are absolutely required to solve a problem.
## What Requires Downtime?
The document ["What Requires Downtime?"](what_requires_downtime.md) specifies
-various database operations, such as
+various database operations, such as
- [adding, dropping, and renaming columns](what_requires_downtime.md#adding-columns)
- [changing column constraints and types](what_requires_downtime.md#changing-column-constraints)
@@ -59,9 +59,9 @@ the migrations that _do_ require downtime.
To tag a migration, add the following two constants to the migration class'
body:
-* `DOWNTIME`: a boolean that when set to `true` indicates the migration requires
+- `DOWNTIME`: a boolean that when set to `true` indicates the migration requires
downtime.
-* `DOWNTIME_REASON`: a String containing the reason for the migration requiring
+- `DOWNTIME_REASON`: a String containing the reason for the migration requiring
downtime. This constant **must** be set when `DOWNTIME` is set to `true`.
For example:
@@ -318,13 +318,38 @@ end
Instead of using these methods one should use the following methods to store timestamps with timezones:
-* `add_timestamps_with_timezone`
-* `timestamps_with_timezone`
+- `add_timestamps_with_timezone`
+- `timestamps_with_timezone`
This ensures all timestamps have a time zone specified. This in turn means existing timestamps won't
suddenly use a different timezone when the system's timezone changes. It also makes it very clear which
timezone was used in the first place.
+## Storing JSON in database
+
+The Rails 5 natively supports `JSONB` (binary JSON) column type.
+Example migration adding this column:
+
+```ruby
+class AddOptionsToBuildMetadata < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :ci_builds_metadata, :config_options, :jsonb
+ end
+end
+```
+
+On MySQL the `JSON` and `JSONB` is translated to `TEXT 1MB`, as `JSONB` is PostgreSQL only feature.
+
+For above reason you have to use a serializer to provide a translation layer
+in order to support PostgreSQL and MySQL seamlessly:
+
+```ruby
+class BuildMetadata
+ serialize :config_options, Serializers::JSON # rubocop:disable Cop/ActiveRecordSerialize
+end
+```
## Testing
diff --git a/doc/development/new_fe_guide/development/components.md b/doc/development/new_fe_guide/development/components.md
index 899efb398cd..8ae58d30c35 100644
--- a/doc/development/new_fe_guide/development/components.md
+++ b/doc/development/new_fe_guide/development/components.md
@@ -6,16 +6,16 @@ We have a lot of graphing libraries in our codebase to render graphs. In an effo
We chose D3 as our library going forward because of the following features:
-* [Tree shaking webpack capabilities.](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40)
-* [Compatible with vue.js as well as vanilla javascript.](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40)
+- [Tree shaking webpack capabilities](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40).
+- [Compatible with vue.js as well as vanilla javascript](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-40).
D3 is very popular across many projects outside of GitLab:
-* [The New York Times](https://archive.nytimes.com/www.nytimes.com/interactive/2012/02/13/us/politics/2013-budget-proposal-graphic.html)
-* [plot.ly](https://plot.ly/)
-* [Droptask](https://www.droptask.com/)
+- [The New York Times](https://archive.nytimes.com/www.nytimes.com/interactive/2012/02/13/us/politics/2013-budget-proposal-graphic.html)
+- [plot.ly](https://plot.ly/)
+- [Droptask](https://www.droptask.com/)
Within GitLab, D3 has been used for the following notable features
-* [Prometheus graphs](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html)
-* Contribution calendars
+- [Prometheus graphs](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html)
+- Contribution calendars
diff --git a/doc/development/new_fe_guide/development/performance.md b/doc/development/new_fe_guide/development/performance.md
index 5ccd5357314..640a8d64176 100644
--- a/doc/development/new_fe_guide/development/performance.md
+++ b/doc/development/new_fe_guide/development/performance.md
@@ -7,10 +7,10 @@ We have a performance dashboard available in one of our [grafana instances](http
These pages can be found inside a text file in the gitlab-build-images [repository](https://gitlab.com/gitlab-org/gitlab-build-images) called [gitlab.txt](https://gitlab.com/gitlab-org/gitlab-build-images/blob/master/scripts/gitlab.txt)
Any frontend engineer can contribute to this dashboard. They can contribute by adding or removing urls of pages from this text file. Please have a [frontend monitoring expert](https://about.gitlab.com/team) review your changes before assigning to a maintainer of the `gitlab-build-images` project. The changes will go live on the next scheduled run after the changes are merged into `master`.
-There are 3 recommended high impact metrics to review on each page
+There are 3 recommended high impact metrics to review on each page:
-* [First visual change](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint)
-* [Speed Index](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
-* [Visual Complete 95%](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
+- [First visual change](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint)
+- [Speed Index](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
+- [Visual Complete 95%](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index)
For these metrics, lower numbers are better as it means that the website is more performant.
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
index 0d98180add0..d74f141f08f 100644
--- a/doc/development/new_fe_guide/development/testing.md
+++ b/doc/development/new_fe_guide/development/testing.md
@@ -1,29 +1,27 @@
# Overview of Frontend Testing
-Tests relevant for frontend development can be found at two places:
+Tests relevant for frontend development can be found at the following places:
-- `spec/javascripts/` which are run by Karma and contain
+- `spec/javascripts/` which are run by Karma (command: `yarn karma`) and contain
- [frontend unit tests](#frontend-unit-tests)
- [frontend component tests](#frontend-component-tests)
- [frontend integration tests](#frontend-integration-tests)
-- `spec/frontend/` which are run by Jest and contain
+- `spec/frontend/` which are run by Jest (command: `yarn jest`) and contain
- [frontend unit tests](#frontend-unit-tests)
- [frontend component tests](#frontend-component-tests)
- [frontend integration tests](#frontend-integration-tests)
- `spec/features/` which are run by RSpec and contain
- [feature tests](#feature-tests)
-All tests in `spec/javascripts/` will eventually be migrated to `spec/frontend/` (see also [#53757]).
+All tests in `spec/javascripts/` will eventually be migrated to `spec/frontend/` (see also [#52483](https://gitlab.com/gitlab-org/gitlab-ce/issues/52483)).
In addition there were feature tests in `features/` run by Spinach in the past.
These have been removed from our codebase in May 2018 ([#23036](https://gitlab.com/gitlab-org/gitlab-ce/issues/23036)).
See also:
-- [old testing guide](../../testing_guide/frontend_testing.html)
-- [notes on testing Vue components](../../fe_guide/vue.html#testing-vue-components)
-
-[#53757]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53757
+- [Old testing guide](../../testing_guide/frontend_testing.html).
+- [Notes on testing Vue components](../../fe_guide/vue.html#testing-vue-components).
## Frontend unit tests
@@ -246,6 +244,8 @@ Their abstraction level is comparable to how a user would interact with the UI.
In contrast to [frontend integration tests](#frontend-integration-tests), feature tests make requests against the real backend instead of using fixtures.
This also implies that database queries are executed which makes this category significantly slower.
+See also the [RSpec testing guidelines](../../testing_guide/best_practices.md#rspec).
+
### When to use feature tests
- use cases that require a backend and cannot be tested using fixtures
@@ -302,8 +302,8 @@ Check an example in [spec/javascripts/ide/stores/actions_spec.jsspec/javascripts
To make mounting a Vue component easier and more readable, we have a few helpers available in `spec/helpers/vue_mount_component_helper`.
-* `createComponentWithStore`
-* `mountComponentWithStore`
+- `createComponentWithStore`
+- `mountComponentWithStore`
Examples of usage:
@@ -350,7 +350,7 @@ You can find the credentials on 1Password, under `frontendteam@gitlab.com`.
#### macOS
-You can download any older version of Firefox from the releases FTP server, https://ftp.mozilla.org/pub/firefox/releases/
+You can download any older version of Firefox from the releases FTP server, <https://ftp.mozilla.org/pub/firefox/releases/>
1. From the website, select a version, in this case `50.0.1`.
1. Go to the mac folder.
diff --git a/doc/development/new_fe_guide/modules/index.md b/doc/development/new_fe_guide/modules/index.md
index 0a7f2dbd819..a7820442df0 100644
--- a/doc/development/new_fe_guide/modules/index.md
+++ b/doc/development/new_fe_guide/modules/index.md
@@ -1,5 +1,5 @@
# Modules
-* [DirtySubmit](dirty_submit.md)
+- [DirtySubmit](dirty_submit.md)
- Disable form submits until there are unsaved changes. \ No newline at end of file
+ Disable form submits until there are unsaved changes.
diff --git a/doc/development/ordering_table_columns.md b/doc/development/ordering_table_columns.md
index e9c6481635b..3e49a65f5ab 100644
--- a/doc/development/ordering_table_columns.md
+++ b/doc/development/ordering_table_columns.md
@@ -30,7 +30,7 @@ ideal column order would be the following:
- `user_id` (integer, 4 bytes)
- `name` (text, variable)
-or
+or
- `name` (text, variable)
- `id` (integer, 4 bytes)
@@ -47,8 +47,7 @@ type size in descending order with variable sizes (`text`, `varchar`, arrays,
## Type Sizes
-While the PostgreSQL documentation
-(https://www.postgresql.org/docs/current/static/datatype.html) contains plenty
+While the [PostgreSQL documentation](https://www.postgresql.org/docs/current/datatype.html) contains plenty
of information we will list the sizes of common types here so it's easier to
look them up. Here "word" refers to the word size, which is 4 bytes for a 32
bits platform and 8 bytes for a 64 bits platform.
@@ -138,7 +137,7 @@ This would produce the following chunks:
| variable | data |
Here we only need 40 bytes per row excluding the variable sized data and 24-byte
-tuple header. 8 bytes being saved may not sound like much, but for tables as
+tuple header. 8 bytes being saved may not sound like much, but for tables as
large as the `events` table it does begin to matter. For example, when storing
80 000 000 rows this translates to a space saving of at least 610 MB, all by
just changing the order of a few columns.
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 4cc2fdc9a58..972c93be817 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -23,9 +23,9 @@ The process of solving performance problems is roughly as follows:
When providing timings make sure to provide:
-* The 95th percentile
-* The 99th percentile
-* The mean
+- The 95th percentile
+- The 99th percentile
+- The mean
When providing screenshots of graphs, make sure that both the X and Y axes and
the legend are clearly visible. If you happen to have access to GitLab.com's own
@@ -36,12 +36,12 @@ graphs/dashboards.
GitLab provides built-in tools to help improve performance and availability:
-* [Profiling](profiling.md)
- * [Sherlock](profiling.md#sherlock)
-* [GitLab Performance Monitoring](../administration/monitoring/performance/index.md)
-* [Request Profiling](../administration/monitoring/performance/request_profiling.md)
-* [QueryRecoder](query_recorder.md) for preventing `N+1` regressions
-* [Chaos endpoints](chaos_endpoints.md) for testing failure scenarios. Intended mainly for testing availability.
+- [Profiling](profiling.md).
+ - [Sherlock](profiling.md#sherlock).
+- [GitLab Performance Monitoring](../administration/monitoring/performance/index.md).
+- [Request Profiling](../administration/monitoring/performance/request_profiling.md).
+- [QueryRecoder](query_recorder.md) for preventing `N+1` regressions.
+- [Chaos endpoints](chaos_endpoints.md) for testing failure scenarios. Intended mainly for testing availability.
GitLab employees can use GitLab.com's performance monitoring systems located at
<https://dashboards.gitlab.net>, this requires you to log in using your
@@ -269,11 +269,11 @@ piece of code is worth optimizing. The only two things you can do are:
Some examples of changes that aren't really important/worth the effort:
-* Replacing double quotes with single quotes.
-* Replacing usage of Array with Set when the list of values is very small.
-* Replacing library A with library B when both only take up 0.1% of the total
+- Replacing double quotes with single quotes.
+- Replacing usage of Array with Set when the list of values is very small.
+- Replacing library A with library B when both only take up 0.1% of the total
execution time.
-* Calling `freeze` on every string (see [String Freezing](#string-freezing)).
+- Calling `freeze` on every string (see [String Freezing](#string-freezing)).
## Slow Operations & Sidekiq
diff --git a/doc/development/policies.md b/doc/development/policies.md
index 62141356f59..97424d90fb5 100644
--- a/doc/development/policies.md
+++ b/doc/development/policies.md
@@ -13,7 +13,7 @@ Permissions are broken into two parts: `conditions` and `rules`. Conditions are
Conditions are defined by the `condition` method, and are given a name and a block. The block will be executed in the context of the policy object - so it can access `@user` and `@subject`, as well as call any methods defined on the policy. Note that `@user` may be nil (in the anonymous case), but `@subject` is guaranteed to be a real instance of the subject class.
-``` ruby
+```ruby
class FooPolicy < BasePolicy
condition(:is_public) do
# @subject guaranteed to be an instance of Foo
@@ -37,7 +37,7 @@ Conditions are cached according to their scope. Scope and ordering will be cover
A `rule` is a logical combination of conditions and other rules, that are configured to enable or prevent certain abilities. It is important to note that the rule configuration is static - a rule's logic cannot touch the database or know about `@user` or `@subject`. This allows us to cache only at the condition level. Rules are specified through the `rule` method, which takes a block of DSL configuration, and returns an object that responds to `#enable` or `#prevent`:
-``` ruby
+```ruby
class FooPolicy < BasePolicy
# ...
@@ -57,10 +57,10 @@ end
Within the rule DSL, you can use:
-* A regular word mentions a condition by name - a rule that is in effect when that condition is truthy.
-* `~` indicates negation
-* `&` and `|` are logical combinations, also available as `all?(...)` and `any?(...)`
-* `can?(:other_ability)` delegates to the rules that apply to `:other_ability`. Note that this is distinct from the instance method `can?`, which can check dynamically - this only configures a delegation to another ability.
+- A regular word mentions a condition by name - a rule that is in effect when that condition is truthy.
+- `~` indicates negation.
+- `&` and `|` are logical combinations, also available as `all?(...)` and `any?(...)`.
+- `can?(:other_ability)` delegates to the rules that apply to `:other_ability`. Note that this is distinct from the instance method `can?`, which can check dynamically - this only configures a delegation to another ability.
## Scores, Order, Performance
@@ -72,7 +72,7 @@ When a policy is asked whether a particular ability is allowed (`policy.allowed?
Sometimes, a condition will only use data from `@user` or only from `@subject`. In this case, we want to change the scope of the caching, so that we don't recalculate conditions unnecessarily. For example, given:
-``` ruby
+```ruby
class FooPolicy < BasePolicy
condition(:expensive_condition) { @subject.expensive_query? }
@@ -82,7 +82,7 @@ end
Naively, if we call `Ability.can?(user1, :some_ability, foo)` and `Ability.can?(user2, :some_ability, foo)`, we would have to calculate the condition twice - since they are for different users. But if we use the `scope: :subject` option:
-``` ruby
+```ruby
condition(:expensive_condition, scope: :subject) { @subject.expensive_query? }
```
@@ -93,7 +93,7 @@ both user and subject (including a simple anonymous check!) your result will be
Sometimes we are checking permissions for a lot of users for one subject, or a lot of subjects for one user. In this case, we want to set a *preferred scope* - i.e. tell the system that we prefer rules that can be cached on the repeated parameter. For example, in `Ability.users_that_can_read_project`:
-``` ruby
+```ruby
def users_that_can_read_project(users, project)
DeclarativePolicy.subject_scope do
users.select { |u| allowed?(u, :read_project, project) }
@@ -105,9 +105,9 @@ This will, for example, prefer checking `project.public?` to checking `user.admi
## Delegation
-Delegation is the inclusion of rules from another policy, on a different subject. For example,
+Delegation is the inclusion of rules from another policy, on a different subject. For example:
-``` ruby
+```ruby
class FooPolicy < BasePolicy
delegate { @subject.project }
end
diff --git a/doc/development/polymorphic_associations.md b/doc/development/polymorphic_associations.md
index d63b9fb115f..5d69c8add38 100644
--- a/doc/development/polymorphic_associations.md
+++ b/doc/development/polymorphic_associations.md
@@ -7,9 +7,9 @@ usually works by adding two columns to a table: a target type column, and a
target id. For example, at the time of writing we have such a setup for
`members` with the following columns:
-* `source_type`: a string defining the model to use, can be either `Project` or
+- `source_type`: a string defining the model to use, can be either `Project` or
`Namespace`.
-* `source_id`: the ID of the row to retrieve based on `source_type`. For
+- `source_id`: the ID of the row to retrieve based on `source_type`. For
example, when `source_type` is `Project` then `source_id` will contain a
project ID.
@@ -92,10 +92,10 @@ AND source_id = 4
Instead such a table should be broken up into separate tables. For example, you
may end up with 4 tables in this case:
-* project_members
-* group_members
-* pending_project_members
-* pending_group_members
+- project_members
+- group_members
+- pending_project_members
+- pending_group_members
This makes querying data trivial. For example, to get the members of a group
you'd run:
diff --git a/doc/development/post_deployment_migrations.md b/doc/development/post_deployment_migrations.md
index 5986efa9974..a41096aa3eb 100644
--- a/doc/development/post_deployment_migrations.md
+++ b/doc/development/post_deployment_migrations.md
@@ -70,6 +70,6 @@ version (which doesn't depend on the column anymore) has been deployed.
Some other examples where these migrations are useful:
-* Cleaning up data generated due to a bug in GitLab
-* Removing tables
-* Migrating jobs from one Sidekiq queue to another
+- Cleaning up data generated due to a bug in GitLab
+- Removing tables
+- Migrating jobs from one Sidekiq queue to another
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 2ad748d4802..ae9bf863419 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -38,6 +38,14 @@ Note that since you can't see the questions from stdout, you might just want
to `echo 'yes'` to keep it running. It would still print the errors on stderr
so no worries about missing errors.
+### Extra Project seed options
+
+There are a few environment flags you can pass to change how projects are seeded
+
+- `SIZE`: defaults to `8`, max: `32`. Amount of projects to create.
+- `LARGE_PROJECTS`: defaults to false. If set will clone 6 large projects to help with testing.
+- `FORK`: defaults to false. If set to `true` will fork `torvalds/linux` five times. Can also be set to an existing project full_path and it will fork that instead.
+
### Notes for MySQL
Since the seeds would contain various UTF-8 characters, such as emojis or so,
diff --git a/doc/development/reusing_abstractions.md b/doc/development/reusing_abstractions.md
index 83d7d42bd1f..01cedf734fb 100644
--- a/doc/development/reusing_abstractions.md
+++ b/doc/development/reusing_abstractions.md
@@ -149,11 +149,11 @@ typically in JSON.
These are class methods defined by _GitLab itself_, including the following
methods provided by Active Record:
-* `find`
-* `find_by_id`
-* `delete_all`
-* `destroy`
-* `destroy_all`
+- `find`
+- `find_by_id`
+- `delete_all`
+- `destroy`
+- `destroy_all`
Any other methods such as `find_by(some_column: X)` are not included, and
instead fall under the "Active Record" abstraction.
@@ -163,10 +163,10 @@ instead fall under the "Active Record" abstraction.
Instance methods defined on Active Record models by _GitLab itself_. Methods
provided by Active Record are not included, except for the following methods:
-* `save`
-* `update`
-* `destroy`
-* `delete`
+- `save`
+- `update`
+- `destroy`
+- `delete`
### Active Record
diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md
index b65fbc9d958..ef1aba95712 100644
--- a/doc/development/rolling_out_changes_using_feature_flags.md
+++ b/doc/development/rolling_out_changes_using_feature_flags.md
@@ -10,12 +10,12 @@ disable those changes, without having to revert an entire release.
Starting with GitLab 11.4, developers are required to use feature flags for
non-trivial changes. Such changes include:
-* New features (e.g. a new merge request widget, epics, etc).
-* Complex performance improvements that may require additional testing in
+- New features (e.g. a new merge request widget, epics, etc).
+- Complex performance improvements that may require additional testing in
production, such as rewriting complex queries.
-* Invasive changes to the user interface, such as a new navigation bar or the
+- Invasive changes to the user interface, such as a new navigation bar or the
removal of a sidebar.
-* Adding support for importing projects from a third-party service.
+- Adding support for importing projects from a third-party service.
In all cases, those working on the changes can best decide if a feature flag is
necessary. For example, changing the color of a button doesn't need a feature
diff --git a/doc/development/sidekiq_debugging.md b/doc/development/sidekiq_debugging.md
index 84b61bd7e61..2b3a9481b93 100644
--- a/doc/development/sidekiq_debugging.md
+++ b/doc/development/sidekiq_debugging.md
@@ -11,6 +11,11 @@ Example:
gitlab_rails['env'] = {"SIDEKIQ_LOG_ARGUMENTS" => "1"}
```
-Please note: It is not recommend to enable this setting in production because some
+Please note: It is not recommend to enable this setting in production because some
Sidekiq jobs (such as sending a password reset email) take secret arguments (for
-example the password reset token). \ No newline at end of file
+example the password reset token).
+
+When using [Sidekiq JSON logging](../administration/logs.md#sidekiqlog),
+arguments logs are limited to a maximum size of 10 kilobytes of text;
+any arguments after this limit will be discarded and replaced with a
+single argument containing the string `"..."`.
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 24f4d457d45..4cc3812b0f0 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -121,7 +121,7 @@ failure. In CI you can download these files as job artifacts.
Also, you can manually take screenshots at any point in a test by adding the
methods below. Be sure to remove them when they are no longer needed! See
-https://github.com/mattheworiordan/capybara-screenshot#manual-screenshots for
+<https://github.com/mattheworiordan/capybara-screenshot#manual-screenshots> for
more.
Add `screenshot_and_save_page` in a `:js` spec to screenshot what Capybara
@@ -302,7 +302,7 @@ path, they will use the same repository on disk and lead to test environment
pollution.
Other files must be managed manually by the spec. If you run code that creates a
-`tmp/test-file.csv` file, for instance, the spec must ensure that the file is
+`tmp/test-file.csv` file, for instance, the spec must ensure that the file is
removed as part of cleanup.
#### Persistent in-memory application state
diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md
index bbb2313ea7b..3d568c37fba 100644
--- a/doc/development/testing_guide/flaky_tests.md
+++ b/doc/development/testing_guide/flaky_tests.md
@@ -5,6 +5,39 @@
It's a test that sometimes fails, but if you retry it enough times, it passes,
eventually.
+## Quarantined tests
+
+When a test frequently fails in `master`,
+[a ~"broken master" issue](https://about.gitlab.com/handbook/engineering/workflow/#broken-master)
+should be created.
+If the test cannot be fixed in a timely fashion, there is an impact on the
+productivity of all the developers, so it should be placed in quarantine by
+assigning the `:quarantine` metadata.
+
+This means it will be skipped unless run with `--tag quarantine`:
+
+```shell
+bin/rspec --tag quarantine
+```
+
+**Before putting a test in quarantine, you should make sure that a
+~"broken master" issue exists for it so it won't stay in quarantine forever.**
+
+Once a test is in quarantine, there are 3 choices:
+
+- Should the test be fixed (i.e. get rid of its flakiness)?
+- Should the test be moved to a lower level of testing?
+- Should the test be removed entirely (e.g. because there's already a
+ lower-level test, or it's duplicating another same-level test, or it's testing
+ too much etc.)?
+
+### Quarantine tests on the CI
+
+Quarantined tests are run on the CI in dedicated jobs that are allowed to fail:
+
+- `rspec-pg-quarantine` and `rspec-mysql-quarantine` (CE & EE)
+- `rspec-pg-quarantine-ee` and `rspec-mysql-quarantine-ee` (EE only)
+
## Automatic retries and flaky tests detection
On our CI, we use [rspec-retry] to automatically retry a failing example a few
@@ -15,51 +48,51 @@ examples in a JSON report file on `master` (`retrieve-tests-metadata` and `updat
is detected in any other branch (`flaky-examples-check` job). In the future, the
`flaky-examples-check` job will not be allowed to fail.
-This was originally implemented in: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13021.
+This was originally implemented in: <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13021>.
[rspec-retry]: https://github.com/NoRedInk/rspec-retry
[`spec/spec_helper.rb`]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/spec_helper.rb
## Problems we had in the past at GitLab
-- [`rspec-retry` is bitting us when some API specs fail](https://gitlab.com/gitlab-org/gitlab-ce/issues/29242): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9825
-- [Sporadic RSpec failures due to `PG::UniqueViolation`](https://gitlab.com/gitlab-org/gitlab-ce/issues/28307#note_24958837): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9846
- - Follow-up: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10688
- - [Capybara.reset_session! should be called before requests are blocked](https://gitlab.com/gitlab-org/gitlab-ce/issues/33779): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12224
+- [`rspec-retry` is bitting us when some API specs fail](https://gitlab.com/gitlab-org/gitlab-ce/issues/29242): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9825>
+- [Sporadic RSpec failures due to `PG::UniqueViolation`](https://gitlab.com/gitlab-org/gitlab-ce/issues/28307#note_24958837): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9846>
+ - Follow-up: <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10688>
+ - [Capybara.reset_session! should be called before requests are blocked](https://gitlab.com/gitlab-org/gitlab-ce/issues/33779): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12224>
- FFaker generates funky data that tests are not ready to handle (and tests should be predictable so that's bad!):
- - [Make `spec/mailers/notify_spec.rb` more robust](https://gitlab.com/gitlab-org/gitlab-ce/issues/20121): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10015
- - [Transient failure in spec/requests/api/commits_spec.rb](https://gitlab.com/gitlab-org/gitlab-ce/issues/27988#note_25342521): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9944
- - [Replace FFaker factory data with sequences](https://gitlab.com/gitlab-org/gitlab-ce/issues/29643): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10184
- - [Transient failure in spec/finders/issues_finder_spec.rb](https://gitlab.com/gitlab-org/gitlab-ce/issues/30211#note_26707685): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10404
+ - [Make `spec/mailers/notify_spec.rb` more robust](https://gitlab.com/gitlab-org/gitlab-ce/issues/20121): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10015>
+ - [Transient failure in spec/requests/api/commits_spec.rb](https://gitlab.com/gitlab-org/gitlab-ce/issues/27988#note_25342521): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9944>
+ - [Replace FFaker factory data with sequences](https://gitlab.com/gitlab-org/gitlab-ce/issues/29643): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10184>
+ - [Transient failure in spec/finders/issues_finder_spec.rb](https://gitlab.com/gitlab-org/gitlab-ce/issues/30211#note_26707685): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10404>
### Time-sensitive flaky tests
-- https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10046
-- https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10306
+- <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10046>
+- <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10306>
### Array order expectation
-- https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10148
+- <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10148>
### Feature tests
-- [Be sure to create all the data the test need before starting exercize](https://gitlab.com/gitlab-org/gitlab-ce/issues/32622#note_31128195): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12059
-- [Bis](https://gitlab.com/gitlab-org/gitlab-ce/issues/34609#note_34048715): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12604
-- [Bis](https://gitlab.com/gitlab-org/gitlab-ce/issues/34698#note_34276286): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12664
-- [Assert against the underlying database state instead of against a page's content](https://gitlab.com/gitlab-org/gitlab-ce/issues/31437): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10934
+- [Be sure to create all the data the test need before starting exercize](https://gitlab.com/gitlab-org/gitlab-ce/issues/32622#note_31128195): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12059>
+- [Bis](https://gitlab.com/gitlab-org/gitlab-ce/issues/34609#note_34048715): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12604>
+- [Bis](https://gitlab.com/gitlab-org/gitlab-ce/issues/34698#note_34276286): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12664>
+- [Assert against the underlying database state instead of against a page's content](https://gitlab.com/gitlab-org/gitlab-ce/issues/31437): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10934>
#### Capybara viewport size related issues
-- [Transient failure of spec/features/issues/filtered_search/filter_issues_spec.rb](https://gitlab.com/gitlab-org/gitlab-ce/issues/29241#note_26743936): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10411
+- [Transient failure of spec/features/issues/filtered_search/filter_issues_spec.rb](https://gitlab.com/gitlab-org/gitlab-ce/issues/29241#note_26743936): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10411>
#### Capybara JS driver related issues
-- [Don't wait for AJAX when no AJAX request is fired](https://gitlab.com/gitlab-org/gitlab-ce/issues/30461): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10454
-- [Bis](https://gitlab.com/gitlab-org/gitlab-ce/issues/34647): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12626
+- [Don't wait for AJAX when no AJAX request is fired](https://gitlab.com/gitlab-org/gitlab-ce/issues/30461): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10454>
+- [Bis](https://gitlab.com/gitlab-org/gitlab-ce/issues/34647): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12626>
#### PhantomJS / WebKit related issues
-- Memory is through the roof! (TL;DR: Load images but block images requests!): https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12003
+- Memory is through the roof! (TL;DR: Load images but block images requests!): <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12003>
## Resources
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index f8993653aec..6012f1080ab 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -185,8 +185,8 @@ See this [section][vue-test].
`rake karma` runs the frontend-only (JavaScript) tests.
It consists of two subtasks:
-* `rake karma:fixtures` (re-)generates fixtures
-* `rake karma:tests` actually executes the tests
+- `rake karma:fixtures` (re-)generates fixtures
+- `rake karma:tests` actually executes the tests
As long as the fixtures don't change, `rake karma:tests` (or `yarn karma`)
is sufficient (and saves you some time).
@@ -243,14 +243,14 @@ supported by the PhantomJS test runner which is used for both Karma and RSpec
tests. We polyfill some JavaScript objects for older browsers, but some
features are still unavailable:
-* Array.from
-* Array.first
-* Async functions
-* Generators
-* Array destructuring
-* For..Of
-* Symbol/Symbol.iterator
-* Spread
+- Array.from
+- Array.first
+- Async functions
+- Generators
+- Array destructuring
+- For..Of
+- Symbol/Symbol.iterator
+- Spread
Until these are polyfilled appropriately, they should not be used. Please
update this list with additional unsupported features.
diff --git a/doc/development/testing_guide/img/review_apps_cicd_architecture.png b/doc/development/testing_guide/img/review_apps_cicd_architecture.png
new file mode 100644
index 00000000000..87e472076f3
--- /dev/null
+++ b/doc/development/testing_guide/img/review_apps_cicd_architecture.png
Binary files differ
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 309babb5f94..3af97717775 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -6,39 +6,73 @@ Review Apps are automatically deployed by each pipeline, both in
## How does it work?
+### CD/CD architecture diagram
+
+![Review Apps CI/CD architecture](img/review_apps_cicd_architecture.png)
+
+<details>
+<summary>Show mermaid source</summary>
+<pre>
+graph TD
+ B1 -.->|2. once gitlab:assets:compile is done,<br />triggers a CNG-mirror pipeline and wait for it to be done| A2
+ C1 -.->|2. once review-build-cng is done,<br />Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline| A3
+
+subgraph gitlab-ce/ee `test` stage
+ A1[gitlab:assets:compile]
+ B1[review-build-cng] -->|1. wait for| A1
+ C1[review-deploy] -->|1. wait for| B1
+ D1[review-qa-smoke] -->|1. wait for| C1
+ D1[review-qa-smoke] -.->|2. once review-deploy is done| E1>gitlab-qa runs the smoke<br/>suite against the Review App]
+ end
+
+subgraph CNG-mirror pipeline
+ A2>Cloud Native images are built];
+ end
+
+subgraph GCP `gitlab-review-apps` project
+ A3>"Cloud Native images are deployed to the<br />`review-apps-ce` or `review-apps-ee` Kubernetes (GKE) cluster"];
+ end
+</pre>
+</details>
+
+### Detailed explanation
+
1. On every [pipeline][gitlab-pipeline] during the `test` stage, the
- [`review-deploy`][review-deploy-job] job is automatically started.
-1. The `review-deploy` job:
- 1. Waits for the `gitlab:assets:compile` job to finish since the
- [`CNG-mirror`][cng-mirror] pipeline triggerred in the following step
- depends on it.
- 1. [Triggers a pipeline][cng-pipeline] in the [`CNG-mirror`][cng-mirror]
- project.
- - We use the `CNG-mirror` project so that the `CNG`, (**C**loud
- **N**ative **G**itLab), project's registry is not overloaded with a
- lot of transient Docker images.
- - The `CNG-mirror` pipeline creates the Docker images of each component
- (e.g. `gitlab-rails-ee`, `gitlab-shell`, `gitaly` etc.) based on the
- commit from the [GitLab pipeline][gitlab-pipeline] and store them in
- its [registry][cng-mirror-registry].
- 1. Once all images are built by [`CNG-mirror`][cng-mirror], the Review App
- is deployed using [the official GitLab Helm chart][helm-chart] to the
- [`review-apps-ce`][review-apps-ce] / [`review-apps-ee`][review-apps-ee]
- Kubernetes cluster on GCP.
- - The actual scripts used to deploy the Review App can be found at
- [`scripts/review_apps/review-apps.sh`][review-apps.sh].
- - These scripts are basically
- [our official Auto DevOps scripts][Auto-DevOps.gitlab-ci.yml] where the
- default CNG images are overridden with the images built and stored in the
- [`CNG-mirror` project's registry][cng-mirror-registry].
- - Since we're using [the official GitLab Helm chart][helm-chart], this means
- you get a dedicated environment for your branch that's very close to what
- it would look in production.
-1. Once the `review-deploy` job succeeds, you should be able to use your Review
- App thanks to the direct link to it from the MR widget. The default username
- is `root` and its password can be found in the 1Password secure note named
- **gitlab-{ce,ee} Review App's root password** (note that there's currently
- [a bug where the default password seems to be overridden][password-bug]).
+ [`review-build-cng`][review-build-cng] and
+ [`review-deploy`][review-deploy] jobs are automatically started.
+ - The [`review-deploy`][review-deploy] job waits for the
+ [`review-build-cng`][review-build-cng] job to finish.
+ - The [`review-build-cng`][review-build-cng] job waits for the
+ [`gitlab:assets:compile`][gitlab:assets:compile] job to finish since the
+ [`CNG-mirror`][cng-mirror] pipeline triggered in the following step depends on it.
+1. Once the [`gitlab:assets:compile`][gitlab:assets:compile] job is done,
+ [`review-build-cng`][review-build-cng] [triggers a pipeline][cng-pipeline]
+ in the [`CNG-mirror`][cng-mirror] project.
+ - The [`CNG-mirror`][cng-pipeline] pipeline creates the Docker images of
+ each component (e.g. `gitlab-rails-ee`, `gitlab-shell`, `gitaly` etc.)
+ based on the commit from the [GitLab pipeline][gitlab-pipeline] and store
+ them in its [registry][cng-mirror-registry].
+ - We use the [`CNG-mirror`][cng-mirror] project so that the `CNG`, (**C**loud
+ **N**ative **G**itLab), project's registry is not overloaded with a
+ lot of transient Docker images.
+1. Once the [`review-build-cng`][review-build-cng] job is done, the
+ [`review-deploy`][review-deploy] job deploys the Review App using
+ [the official GitLab Helm chart][helm-chart] to the
+ [`review-apps-ce`][review-apps-ce] / [`review-apps-ee`][review-apps-ee]
+ Kubernetes cluster on GCP.
+ - The actual scripts used to deploy the Review App can be found at
+ [`scripts/review_apps/review-apps.sh`][review-apps.sh].
+ - These scripts are basically
+ [our official Auto DevOps scripts][Auto-DevOps.gitlab-ci.yml] where the
+ default CNG images are overridden with the images built and stored in the
+ [`CNG-mirror` project's registry][cng-mirror-registry].
+ - Since we're using [the official GitLab Helm chart][helm-chart], this means
+ you get a dedicated environment for your branch that's very close to what
+ it would look in production.
+1. Once the [`review-deploy`][review-deploy] job succeeds, you should be able to
+ use your Review App thanks to the direct link to it from the MR widget. The
+ default username is `root` and its password can be found in the 1Password
+ secure note named **gitlab-{ce,ee} Review App's root password**.
**Additional notes:**
@@ -120,10 +154,13 @@ find a way to limit it to only us.**
> This isn't enabled for forks.
-[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/35850709
-[review-deploy-job]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/118076368
+[charts-1068]: https://gitlab.com/charts/gitlab/issues/1068
+[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587
+[gitlab:assets:compile]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511610
+[review-build-cng]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511623
+[review-deploy]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511624
[cng-mirror]: https://gitlab.com/gitlab-org/build/CNG-mirror
-[cng-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/35883435
+[cng-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/44364657
[cng-mirror-registry]: https://gitlab.com/gitlab-org/build/CNG-mirror/container_registry
[helm-chart]: https://gitlab.com/charts/gitlab/
[review-apps-ce]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-a/review-apps-ce?project=gitlab-review-apps
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index a8671fc3aa3..070b6477a7a 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -6,7 +6,7 @@ _This diagram demonstrates the relative priority of each test type we use. `e2e`
## Unit tests
-Formal definition: https://en.wikipedia.org/wiki/Unit_testing
+Formal definition: <https://en.wikipedia.org/wiki/Unit_testing>
These kind of tests ensure that a single unit of code (a method) works as
expected (given an input, it has a predictable output). These tests should be
@@ -32,7 +32,7 @@ records should use stubs/doubles as much as possible.
## Integration tests
-Formal definition: https://en.wikipedia.org/wiki/Integration_testing
+Formal definition: <https://en.wikipedia.org/wiki/Integration_testing>
These kind of tests ensure that individual parts of the application work well
together, without the overhead of the actual app environment (i.e. the browser).
@@ -75,8 +75,8 @@ of multiple components).
Formal definitions:
-- https://en.wikipedia.org/wiki/System_testing
-- https://en.wikipedia.org/wiki/White-box_testing
+- <https://en.wikipedia.org/wiki/System_testing>
+- <https://en.wikipedia.org/wiki/White-box_testing>
These kind of tests ensure the GitLab *Rails* application (i.e.
`gitlab-ce`/`gitlab-ee`) works as expected from a *browser* point of view.
@@ -135,8 +135,8 @@ The reasons why we should follow these best practices are as follows:
Formal definitions:
-- https://en.wikipedia.org/wiki/System_testing
-- https://en.wikipedia.org/wiki/Black-box_testing
+- <https://en.wikipedia.org/wiki/System_testing>
+- <https://en.wikipedia.org/wiki/Black-box_testing>
GitLab consists of [multiple pieces] such as [GitLab Shell], [GitLab Workhorse],
[Gitaly], [GitLab Pages], [GitLab Runner], and GitLab Rails. All theses pieces
diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md
index adf8795a5e3..01a0044f096 100644
--- a/doc/development/understanding_explain_plans.md
+++ b/doc/development/understanding_explain_plans.md
@@ -625,9 +625,9 @@ This is a bit of a difficult question to answer, because the definition of "bad"
is relative to the problem you are trying to solve. However, some patterns are
best avoided in most cases, such as:
-* Sequential scans on large tables
-* Filters that remove a lot of rows
-* Performing a certain step (e.g. an index scan) that requires _a lot_ of
+- Sequential scans on large tables
+- Filters that remove a lot of rows
+- Performing a certain step (e.g. an index scan) that requires _a lot_ of
buffers (e.g. more than 512 MB for GitLab.com).
As a general guideline, aim for a query that:
@@ -650,8 +650,8 @@ different queries. The only _rule_ is that you _must always measure_ your query
(preferably using a production-like database) using `EXPLAIN (ANALYZE, BUFFERS)`
and related tools such as:
-* <https://explain.depesz.com/>
-* <http://tatiyants.com/postgres-query-plan-visualization/>
+- <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
diff --git a/doc/development/utilities.md b/doc/development/utilities.md
index e5466ae8914..0e396baccff 100644
--- a/doc/development/utilities.md
+++ b/doc/development/utilities.md
@@ -4,7 +4,7 @@ We developed a number of utilities to ease development.
## [`MergeHash`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/merge_hash.rb)
-* Deep merges an array of hashes:
+- Deep merges an array of hashes:
``` ruby
Gitlab::Utils::MergeHash.merge(
@@ -31,7 +31,7 @@ We developed a number of utilities to ease development.
]
```
-* Extracts all keys and values from a hash into an array:
+- Extracts all keys and values from a hash into an array:
``` ruby
Gitlab::Utils::MergeHash.crush(
@@ -47,14 +47,14 @@ We developed a number of utilities to ease development.
## [`Override`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/override.rb)
-* This utility could help us check if a particular method would override
+- This utility could help us check if a particular method would override
another method or not. It has the same idea of Java's `@Override` annotation
or Scala's `override` keyword. However we only do this check when
`ENV['STATIC_VERIFICATION']` is set to avoid production runtime overhead.
This is useful to check:
- * If we have typos in overriding methods.
- * If we renamed the overridden methods, making original overriding methods
+ - If we have typos in overriding methods.
+ - If we renamed the overridden methods, making original overriding methods
overrides nothing.
Here's a simple example:
@@ -92,7 +92,7 @@ We developed a number of utilities to ease development.
## [`StrongMemoize`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/strong_memoize.rb)
-* Memoize the value even if it is `nil` or `false`.
+- Memoize the value even if it is `nil` or `false`.
We often do `@value ||= compute`, however this doesn't work well if
`compute` might eventually give `nil` and we don't want to compute again.
@@ -126,7 +126,7 @@ We developed a number of utilities to ease development.
end
```
-* Clear memoization
+- Clear memoization
``` ruby
class Find
diff --git a/doc/development/verifying_database_capabilities.md b/doc/development/verifying_database_capabilities.md
index ffdeff47d4a..661ab9cef1a 100644
--- a/doc/development/verifying_database_capabilities.md
+++ b/doc/development/verifying_database_capabilities.md
@@ -6,9 +6,9 @@ necessary to add database (version) specific behaviour.
To facilitate this we have the following methods that you can use:
-* `Gitlab::Database.postgresql?`: returns `true` if PostgreSQL is being used
-* `Gitlab::Database.mysql?`: returns `true` if MySQL is being used
-* `Gitlab::Database.version`: returns the PostgreSQL version number as a string
+- `Gitlab::Database.postgresql?`: returns `true` if PostgreSQL is being used
+- `Gitlab::Database.mysql?`: returns `true` if MySQL is being used
+- `Gitlab::Database.version`: returns the PostgreSQL version number as a string
in the format `X.Y.Z`. This method does not work for MySQL
This allows you to write code such as:
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 33f46e8d4f3..978ffde84ad 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -1,27 +1,28 @@
-# How to create a project in GitLab
+# Create a project
-> **Notes:**
-> - For a list of words that are not allowed to be used as project names see the
-> [reserved names][reserved].
+[Projects](../user/project/index.md) combine many features of GitLab together.
-1. In your dashboard, click the green **New project** button or use the plus
- icon in the upper right corner of the navigation bar.
+NOTE: **Note:**
+For a list of words that cannot be used as project names see
+[Reserved project and group names](../user/reserved_names.md).
- ![Create a project](img/create_new_project_button.png)
+To create a project in GitLab:
-1. This opens the **New project** page.
+1. In your dashboard, click the green **New project** button or use the plus
+ icon in the navigation bar. This opens the **New project** page.
+1. On the **New project** page, choose if you want to:
+ - Create a [blank project](#blank-projects).
+ - Create a project using with one of the available [project templates](#project-templates).
+ - [Import a project](../user/project/import/index.md) from a different repository,
+ if enabled on your GitLab instance. Contact your GitLab admin if this
+ is unavailable.
- ![Project information](img/create_new_project_info.png)
+## Blank projects
-1. Choose if you want start a blank project, or with one of the predefined
- [Project Templates](https://gitlab.com/gitlab-org/project-templates):
- this will kickstart your repository code and CI automatically.
- Otherwise, if you have a project in a different repository, you can [import it] by
- clicking on the **Import project** tab, provided this is enabled in
- your GitLab instance. Ask your administrator if not.
+To create a new blank project on the **New project** page:
-1. Provide the following information:
- - Enter the name of your project in the **Project name** field. You can't use
+1. On the **Blank project** tab, provide the following information:
+ - The name of your project in the **Project name** field. You can't use
special characters, but you can use spaces, hyphens, underscores or even
emoji.
- The **Project description (optional)** field enables you to enter a
@@ -31,11 +32,64 @@
- Changing the **Visibility Level** modifies the project's
[viewing and access rights](../public_access/public_access.md) for users.
- Selecting the **Initialize repository with a README** option creates a
- README so that the Git repository is initialized, has a default branch and
+ README file so that the Git repository is initialized, has a default branch, and
can be cloned.
-
1. Click **Create project**.
+## Project templates
+
+Project templates can pre-populate your project with necessary files to get you started quickly.
+
+There are two types of project templates:
+
+- [Built-in templates](#builtin-templates), sourced from the [`project-templates`](https://gitlab.com/gitlab-org/project-templates) group.
+- [Custom project templates](#custom-project-templates-premium-only), for custom templates configured by GitLab administrators and users.
+
+### Built-in templates
+
+Built-in templates are project templates that are:
+
+- Developed and maintained in the
+ [`project-templates`](https://gitlab.com/gitlab-org/project-templates) group.
+- Released with GitLab.
+
+To use a built-in template on the **New project** page:
+
+1. On the **Create from template** tab.
+1. From the list of available built-in templates, click the:
+ - **Preview** button to look at the template source itself.
+ - **Use template** button to start creating the project.
+1. Finish creating the project by filling out the project's details. The process is the same as for
+ using a [blank project](#blank-projects).
+
+TIP: **Tip:**
+You can improve the existing built-in templates or contribute new ones on the
+[`project-templates`](https://gitlab.com/gitlab-org/project-templates) group.
+
+### Custom project templates **[PREMIUM ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in
+[GitLab Premium](https://about.gitlab.com/pricing) 11.2.
+
+Creating new projects based on custom project templates is a convenient option to bootstrap a project.
+
+Custom projects are available from the **Instance** or **Group** tabs under the **Create from template** tab,
+depending on the type of template.
+
+To use a custom project template on the **New project** page:
+
+1. On the **Create from template** tab, select the **Instance** tab or the **Group** tab.
+1. From the list of available custom templates, click the:
+ - **Preview** button to look at the template source itself.
+ - **Use template** button to start creating the project.
+1. Finish creating the project by filling out the project's details. The process is the same as for
+ using a [blank project](#blank-projects).
+
+For information on configuring custom project templates, see:
+
+- [Custom instance-level project templates](../user/admin_area/custom_project_templates.md), for instance-level templates.
+- [Custom group-level project templates](../user/group/custom_project_templates.md), for group-level templates.
+
## Push to create a new project
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26388) in GitLab 10.5.
@@ -46,20 +100,20 @@ GitLab to create the new project, all without leaving your terminal. If you have
namespace, we will automatically create a new project under that GitLab namespace with its
visibility set to Private by default (you can later change it in the [project's settings](../public_access/public_access.md#how-to-change-project-visibility)).
-This can be done by using either SSH or HTTP:
+This can be done by using either SSH or HTTPS:
-```
+```sh
## Git push using SSH
git push --set-upstream git@gitlab.example.com:namespace/nonexistent-project.git master
-## Git push using HTTP
+## Git push using HTTPS
git push --set-upstream https://gitlab.example.com/namespace/nonexistent-project.git master
```
Once the push finishes successfully, a remote message will indicate
the command to set the remote and the URL to the new project:
-```
+```text
remote:
remote: The private project namespace/nonexistent-project was created.
remote:
@@ -70,6 +124,3 @@ remote: To view the project, visit:
remote: https://gitlab.example.com/namespace/nonexistent-project
remote:
```
-
-[import it]: ../workflow/importing/README.md
-[reserved]: ../user/reserved_names.md
diff --git a/doc/gitlab-basics/img/create_new_project_button.png b/doc/gitlab-basics/img/create_new_project_button.png
deleted file mode 100644
index 567f104880f..00000000000
--- a/doc/gitlab-basics/img/create_new_project_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/create_new_project_info.png b/doc/gitlab-basics/img/create_new_project_info.png
deleted file mode 100644
index 2693a7f9a6d..00000000000
--- a/doc/gitlab-basics/img/create_new_project_info.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index 0d9994c9925..e30afdf8a40 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -68,6 +68,54 @@ git config --global --list
## Basic Git commands
+Start using Git via the command line with the most basic
+commands as described below.
+
+### Clone a repository
+
+To start working locally on an existing remote repository,
+clone it with the command `git clone <repository path>`.
+By cloning a repository, you'll download a copy of its
+files into your local computer, preserving the Git
+connection with the remote repository.
+
+You can either clone it via HTTPS or [SSH](../ssh/README.md).
+If you chose to clone it via HTTPS, you'll have to enter your
+credentials every time you pull and push. With SSH, you enter
+your credentials once and can pull and push straightaway.
+
+You can find both paths (HTTPS and SSH) by navigating to
+your project's landing page and clicking **Clone**. GitLab
+will prompt you with both paths, from which you can copy
+and paste in your command line.
+
+As an example, consider a repository path:
+
+- HTTPS: `https://gitlab.com/gitlab-org/gitlab-ce.git`
+- SSH: `` git@gitlab.com:gitlab-org/gitlab-ce.git ``
+
+To get started, open a terminal window in the directory
+you wish to clone the repository files into, and run one
+of the following commands.
+
+Clone via HTTPS:
+
+```bash
+git clone https://gitlab.com/gitlab-org/gitlab-ce.git
+```
+
+Clone via SSH:
+
+```bash
+git clone git@gitlab.com:gitlab-org/gitlab-ce.git
+```
+
+Both commands will download a copy of the files in a
+folder named after the project's name.
+
+You can then navigate to the directory and start working
+on it locally.
+
### Go to the master branch to pull the latest changes from there
```bash
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index ce61ace60a0..e209a00b38c 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -60,7 +60,7 @@ Here's a list of the AWS services we will use, with links to pricing information
To minimize the permissions of the user, we'll create a new [IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html)
role with limited access:
-1. Navigate to the IAM dashboard https://console.aws.amazon.com/iam/home and
+1. Navigate to the IAM dashboard <https://console.aws.amazon.com/iam/home> and
click **Create role**.
1. Create a new role by selecting **AWS service > EC2**, then click
**Next: Permissions**.
@@ -78,7 +78,7 @@ Internet Gateway.
We'll now create a VPC, a virtual networking environment that you'll control:
-1. Navigate to https://console.aws.amazon.com/vpc/home.
+1. Navigate to <https://console.aws.amazon.com/vpc/home>.
1. Select **Your VPCs** from the left menu and then click **Create VPC**.
At the "Name tag" enter `gitlab-vpc` and at the "IPv4 CIDR block" enter
`10.0.0.0/16`. If you don't require dedicated hardware, you can leave
diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md
index ab5f7507f24..aa4b3dccf7d 100644
--- a/doc/install/google_cloud_platform/index.md
+++ b/doc/install/google_cloud_platform/index.md
@@ -22,14 +22,14 @@ Once you have performed those two steps, you can [create a 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.
+1. Go to <https://console.cloud.google.com/compute/instances> and login with your Google credentials.
1. Click on **Create**
![Search for GitLab](img/launch_vm.png)
1. On the next page, you can select the type of VM as well as the
- estimated costs. Provide the name of the instance, desired datacenter, and machine type. Note that GitLab recommends at least 2 vCPU's and 4GB of RAM.
+ estimated costs. Provide the name of the instance, desired datacenter, and machine type. Note that GitLab recommends at least 2 vCPU's and 4GB of RAM.
![Launch on Compute Engine](img/vm_details.png)
@@ -51,7 +51,7 @@ After a few seconds, the instance will be created and available to log in. The n
![GitLab first sign in](img/ssh_terminal.png)
-1. Next, follow the instructions for installing GitLab for the operating system you choose, at https://about.gitlab.com/installation/. You can use the IP address from the step above, as the hostname.
+1. Next, follow the instructions for installing GitLab for the operating system you choose, at <https://about.gitlab.com/installation/>. You can use the IP address from the step above, as the hostname.
1. Congratulations! GitLab is now installed and you can access it via your browser. To finish installation, open the URL in your browser and provide the initial administrator password. The username for this account is `root`.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index b7781dea384..b3ad1c5a91c 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -12,7 +12,7 @@ Since installations from source don't have Runit, Sidekiq can't be terminated an
## 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-6-stable`).
+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 please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
@@ -50,62 +50,85 @@ The GitLab installation consists of setting up the following components:
`sudo` is not installed on Debian by default. Make sure your system is
up-to-date and install it.
- # run as root!
- apt-get update -y
- apt-get upgrade -y
- apt-get install sudo -y
+```sh
+# run as root!
+apt-get update -y
+apt-get upgrade -y
+apt-get install sudo -y
+```
**Note:** During this installation some files will need to be edited manually. If you are familiar with vim set it as default editor with the commands below. If you are not familiar with vim please skip this and keep using the default editor.
- # Install vim and set as default editor
- sudo apt-get install -y vim
- sudo update-alternatives --set editor /usr/bin/vim.basic
+```sh
+# Install vim and set as default editor
+sudo apt-get install -y vim
+sudo update-alternatives --set editor /usr/bin/vim.basic
+```
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake
+```sh
+sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev \
+ libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev \
+ libxslt-dev libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake
+```
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
If you want to use Kerberos for user authentication, then install libkrb5-dev:
- sudo apt-get install libkrb5-dev
+```sh
+sudo apt-get install libkrb5-dev
+```
**Note:** If you don't know what Kerberos is, you can assume you don't need it.
Make sure you have the right version of Git installed
- # Install Git
- sudo apt-get install -y git-core
+```sh
+# Install Git
+sudo apt-get install -y git-core
- # Make sure Git is version 2.18.0 or higher
- git --version
+# Make sure Git is version 2.18.0 or higher
+git --version
+```
Is the system packaged Git too old? Remove it and compile from source.
- # Remove packaged Git
- sudo apt-get remove git-core
+```sh
+# 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 from source
+cd /tmp
+curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz
+echo '94faf2c0b02a7920b0b46f4961d8e9cad08e81418614102898a55f980fa3e7e4 git-2.18.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.18.0.tar.gz
+cd git-2.18.0/
+./configure
+make prefix=/usr/local all
- # Install dependencies
- sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev build-essential
+# Install into /usr/local/bin
+sudo make prefix=/usr/local install
- # Download and compile from source
- cd /tmp
- curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz
- echo '94faf2c0b02a7920b0b46f4961d8e9cad08e81418614102898a55f980fa3e7e4 git-2.18.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.18.0.tar.gz
- cd git-2.18.0/
- ./configure
- make prefix=/usr/local all
+# When editing config/gitlab.yml (Step 5), change the git -> bin_path to /usr/local/bin/git
+```
- # Install into /usr/local/bin
- sudo make prefix=/usr/local install
+For the [Custom Favicon](../customization/favicon.md) to work, graphicsmagick
+needs to be installed.
- # When editing config/gitlab.yml (Step 5), change the git -> bin_path to /usr/local/bin/git
+```sh
+sudo apt-get install -y graphicsmagick
+```
**Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 but this [has problems](https://gitlab.com/gitlab-org/gitlab-ce/issues/12754) while Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with:
- sudo apt-get install -y postfix
+```sh
+sudo apt-get install -y postfix
+```
Then select 'Internet Site' and press enter to confirm the hostname.
@@ -127,22 +150,28 @@ instructions are designed to install Ruby from the official source code.
Remove the old Ruby 1.8 if present:
- sudo apt-get remove ruby1.8
+```sh
+sudo apt-get remove ruby1.8
+```
Download Ruby and compile it:
- mkdir /tmp/ruby && cd /tmp/ruby
- curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
- echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz
- cd ruby-2.5.3
-
- ./configure --disable-install-rdoc
- make
- sudo make install
+```sh
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
+echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz
+cd ruby-2.5.3
+
+./configure --disable-install-rdoc
+make
+sudo make install
+```
Then install the Bundler Gem:
- sudo gem install bundler --no-document
+```sh
+sudo gem install bundler --no-document --version '< 2'
+```
## 3. Go
@@ -151,31 +180,35 @@ GitLab we need a Go compiler. The instructions below assume you use 64-bit
Linux. You can find downloads for other platforms at the [Go download
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
- sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
- rm go1.10.3.linux-amd64.tar.gz
+```sh
+# 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
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.10.3.linux-amd64.tar.gz
+```
## 4. Node
Since GitLab 8.17, GitLab requires the use of Node to compile javascript
assets, and Yarn to manage javascript dependencies. The current minimum
-requirements for these are node >= v6.0.0 and yarn >= v1.2.0. In many distros
+requirements for these are node >= v8.10.0 and yarn >= v1.10.0. In many distros
the versions provided by the official package repositories are out of date, so
we'll need to install through the following commands:
- # install node v8.x
- curl --location https://deb.nodesource.com/setup_8.x | sudo bash -
- sudo apt-get install -y nodejs
-
- curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
- echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
- sudo apt-get update
- sudo apt-get install yarn
+```sh
+# install node v8.x
+curl --location https://deb.nodesource.com/setup_8.x | sudo bash -
+sudo apt-get install -y nodejs
+
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
Visit the official websites for [node](https://nodejs.org/en/download/package-manager/) and [yarn](https://yarnpkg.com/en/docs/install/) if you have any trouble with these steps.
@@ -183,7 +216,9 @@ Visit the official websites for [node](https://nodejs.org/en/download/package-ma
Create a `git` user for GitLab:
- sudo adduser --disabled-login --gecos 'GitLab' git
+```sh
+sudo adduser --disabled-login --gecos 'GitLab' git
+```
## 6. Database
@@ -195,37 +230,37 @@ you need at least PostgreSQL 9.2.
1. Install the database packages:
- ```bash
+ ```sh
sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib
```
1. Create a database user for GitLab:
- ```bash
+ ```sh
sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
```
1. Create the `pg_trgm` extension (required for GitLab 8.6+):
- ```bash
+ ```sh
sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
```
1. Create the GitLab production database and grant all privileges on database:
- ```bash
+ ```sh
sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
```
1. Try connecting to the new database with the new user:
- ```bash
+ ```sh
sudo -u git -H psql -d gitlabhq_production
```
1. Check if the `pg_trgm` extension is enabled:
- ```bash
+ ```sh
SELECT true AS enabled
FROM pg_available_extensions
WHERE name = 'pg_trgm'
@@ -243,7 +278,7 @@ you need at least PostgreSQL 9.2.
1. Quit the database session:
- ```bash
+ ```sh
gitlabhq_production> \q
```
@@ -262,7 +297,7 @@ 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.
-```
+```sh
# Configure redis to use sockets
sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
@@ -294,89 +329,95 @@ sudo usermod -aG redis git
## 8. GitLab
- # We'll install GitLab into home directory of the user "git"
- cd /home/git
+```sh
+# We'll install GitLab into home directory of the user "git"
+cd /home/git
+```
### Clone the Source
- # Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-6-stable gitlab
+```sh
+# Clone GitLab repository
+sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-7-stable gitlab
+```
-**Note:** You can change `11-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `11-7-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
- # Go to GitLab installation folder
- cd /home/git/gitlab
+```sh
+# Go to GitLab installation folder
+cd /home/git/gitlab
- # Copy the example GitLab config
- sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml
+# Copy the example GitLab config
+sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml
- # Update GitLab config file, follow the directions at top of file
- sudo -u git -H editor config/gitlab.yml
+# Update GitLab config file, follow the directions at top of file
+sudo -u git -H editor config/gitlab.yml
- # Copy the example secrets file
- sudo -u git -H cp config/secrets.yml.example config/secrets.yml
- sudo -u git -H chmod 0600 config/secrets.yml
+# Copy the example secrets file
+sudo -u git -H cp config/secrets.yml.example config/secrets.yml
+sudo -u git -H chmod 0600 config/secrets.yml
- # Make sure GitLab can write to the log/ and tmp/ directories
- sudo chown -R git log/
- sudo chown -R git tmp/
- sudo chmod -R u+rwX,go-w log/
- sudo chmod -R u+rwX tmp/
+# Make sure GitLab can write to the log/ and tmp/ directories
+sudo chown -R git log/
+sudo chown -R git tmp/
+sudo chmod -R u+rwX,go-w log/
+sudo chmod -R u+rwX tmp/
- # Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
- sudo chmod -R u+rwX tmp/pids/
- sudo chmod -R u+rwX tmp/sockets/
+# Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
+sudo chmod -R u+rwX tmp/pids/
+sudo chmod -R u+rwX tmp/sockets/
- # Create the public/uploads/ directory
- sudo -u git -H mkdir public/uploads/
+# Create the public/uploads/ directory
+sudo -u git -H mkdir public/uploads/
- # Make sure only the GitLab user has access to the public/uploads/ directory
- # now that files in public/uploads are served by gitlab-workhorse
- sudo chmod 0700 public/uploads
+# Make sure only the GitLab user has access to the public/uploads/ directory
+# now that files in public/uploads are served by gitlab-workhorse
+sudo chmod 0700 public/uploads
- # Change the permissions of the directory where CI job traces are stored
- sudo chmod -R u+rwX builds/
+# Change the permissions of the directory where CI job traces are stored
+sudo chmod -R u+rwX builds/
- # Change the permissions of the directory where CI artifacts are stored
- sudo chmod -R u+rwX shared/artifacts/
+# Change the permissions of the directory where CI artifacts are stored
+sudo chmod -R u+rwX shared/artifacts/
- # Change the permissions of the directory where GitLab Pages are stored
- sudo chmod -R ug+rwX shared/pages/
+# Change the permissions of the directory where GitLab Pages are stored
+sudo chmod -R ug+rwX shared/pages/
- # Copy the example Unicorn config
- sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
+# Copy the example Unicorn config
+sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
- # Find number of cores
- nproc
+# Find number of cores
+nproc
- # Enable cluster mode if you expect to have a high load instance
- # Set the number of workers to at least the number of cores
- # Ex. change amount of workers to 3 for 2GB RAM server
- sudo -u git -H editor config/unicorn.rb
+# Enable cluster mode if you expect to have a high load instance
+# Set the number of workers to at least the number of cores
+# Ex. change amount of workers to 3 for 2GB RAM server
+sudo -u git -H editor config/unicorn.rb
- # Copy the example Rack attack config
- sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
+# Copy the example Rack attack config
+sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
- # Configure Git global settings for git user
- # 'autocrlf' is needed for the web editor
- sudo -u git -H git config --global core.autocrlf input
+# Configure Git global settings for git user
+# 'autocrlf' is needed for the web editor
+sudo -u git -H git config --global core.autocrlf input
- # Disable 'git gc --auto' because GitLab already runs 'git gc' when needed
- sudo -u git -H git config --global gc.auto 0
+# Disable 'git gc --auto' because GitLab already runs 'git gc' when needed
+sudo -u git -H git config --global gc.auto 0
- # Enable packfile bitmaps
- sudo -u git -H git config --global repack.writeBitmaps true
+# Enable packfile bitmaps
+sudo -u git -H git config --global repack.writeBitmaps true
- # Enable push options
- sudo -u git -H git config --global receive.advertisePushOptions true
+# Enable push options
+sudo -u git -H git config --global receive.advertisePushOptions true
- # Configure Redis connection settings
- sudo -u git -H cp config/resque.yml.example config/resque.yml
+# Configure Redis connection settings
+sudo -u git -H cp config/resque.yml.example config/resque.yml
- # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration
- sudo -u git -H editor config/resque.yml
+# Change the Redis socket path if you are not using the default Debian / Ubuntu configuration
+sudo -u git -H editor config/resque.yml
+```
**Important Note:** Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup.
@@ -384,33 +425,37 @@ sudo usermod -aG redis git
### Configure GitLab DB Settings
- # PostgreSQL only:
- sudo -u git cp config/database.yml.postgresql config/database.yml
-
- # MySQL only:
- sudo -u git cp config/database.yml.mysql config/database.yml
-
- # MySQL and remote PostgreSQL only:
- # Update username/password in config/database.yml.
- # You only need to adapt the production settings (first part).
- # If you followed the database guide then please do as follows:
- # Change 'secure password' with the value you have given to $password
- # You can keep the double quotes around the password
- sudo -u git -H editor config/database.yml
-
- # PostgreSQL and MySQL:
- # Make config/database.yml readable to git only
- sudo -u git -H chmod o-rwx config/database.yml
+```sh
+# PostgreSQL only:
+sudo -u git cp config/database.yml.postgresql config/database.yml
+
+# MySQL only:
+sudo -u git cp config/database.yml.mysql config/database.yml
+
+# MySQL and remote PostgreSQL only:
+# Update username/password in config/database.yml.
+# You only need to adapt the production settings (first part).
+# If you followed the database guide then please do as follows:
+# Change 'secure password' with the value you have given to $password
+# You can keep the double quotes around the password
+sudo -u git -H editor config/database.yml
+
+# PostgreSQL and MySQL:
+# Make config/database.yml readable to git only
+sudo -u git -H chmod o-rwx config/database.yml
+```
### Install Gems
**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](https://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2.
- # For PostgreSQL (note, the option says "without ... mysql")
- sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
+```sh
+# For PostgreSQL (note, the option says "without ... mysql")
+sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
- # Or if you use MySQL (note, the option says "without ... postgres")
- sudo -u git -H bundle install --deployment --without development test postgres aws kerberos
+# Or if you use MySQL (note, the option says "without ... postgres")
+sudo -u git -H bundle install --deployment --without development test postgres aws kerberos
+```
**Note:** If you want to use Kerberos for user authentication, then omit `kerberos` in the `--without` option above.
@@ -418,12 +463,14 @@ sudo usermod -aG redis git
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
- # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production SKIP_STORAGE_VALIDATION=true
+```sh
+# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
+sudo -u git -H bundle exec rake gitlab:shell:install REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production SKIP_STORAGE_VALIDATION=true
- # By default, the gitlab-shell config is generated from your main GitLab config.
- # You can review (and modify) the gitlab-shell config as follows:
- sudo -u git -H editor /home/git/gitlab-shell/config.yml
+# By default, the gitlab-shell config is generated from your main GitLab config.
+# You can review (and modify) the gitlab-shell config as follows:
+sudo -u git -H editor /home/git/gitlab-shell/config.yml
+```
**Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps.
@@ -431,9 +478,9 @@ GitLab Shell is an SSH access and repository management software developed speci
**Note:** GitLab Shell application startup time can be greatly reduced by disabling RubyGems. This can be done in several manners:
-* Export `RUBYOPT=--disable-gems` environment variable for the processes
-* Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommended for system-wide Ruby.
-* Omnibus GitLab [replaces the *shebang* line of the `gitlab-shell/bin/*` scripts](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1707)
+- Export `RUBYOPT=--disable-gems` environment variable for the processes.
+- Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommended for system-wide Ruby.
+- Omnibus GitLab [replaces the *shebang* line of the `gitlab-shell/bin/*` scripts](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1707).
### Install gitlab-workhorse
@@ -441,57 +488,73 @@ GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The
following command-line will install GitLab-Workhorse in `/home/git/gitlab-workhorse`
which is the recommended location.
- sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
+```
You can specify a different Git repository by providing it as an extra parameter:
- sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse,https://example.com/gitlab-workhorse.git]" RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse,https://example.com/gitlab-workhorse.git]" RAILS_ENV=production
+```
### Install gitlab-pages
GitLab-Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is optional and only needed if you wish to host static sites from within GitLab. The following commands will install GitLab-Pages in `/home/git/gitlab-pages`. For additional setup steps, please consult the [administration guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/administration/pages/source.md) for your version of GitLab as the GitLab Pages daemon can be ran several different ways.
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
+```sh
+cd /home/git
+sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+cd gitlab-pages
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+sudo -u git -H make
+```
### Install Gitaly
- # Fetch Gitaly source with Git and compile with Go
- sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
+```sh
+# Fetch Gitaly source with Git and compile with Go
+sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
+```
You can specify a different Git repository by providing it as an extra parameter:
- sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
+```
Next, make sure gitaly configured:
- # Restrict Gitaly socket access
- sudo chmod 0700 /home/git/gitlab/tmp/sockets/private
- sudo chown git /home/git/gitlab/tmp/sockets/private
+```sh
+# Restrict Gitaly socket access
+sudo chmod 0700 /home/git/gitlab/tmp/sockets/private
+sudo chown git /home/git/gitlab/tmp/sockets/private
- # If you are using non-default settings you need to update config.toml
- cd /home/git/gitaly
- sudo -u git -H editor config.toml
+# If you are using non-default settings you need to update config.toml
+cd /home/git/gitaly
+sudo -u git -H editor config.toml
+```
For more information about configuring Gitaly see
[doc/administration/gitaly](../administration/gitaly).
### Initialize Database and Activate Advanced Features
- sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
- # Type 'yes' to create the database tables.
+```sh
+sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
+# Type 'yes' to create the database tables.
- # or you can skip the question by adding force=yes
- sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production force=yes
+# or you can skip the question by adding force=yes
+sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production force=yes
- # When done you see 'Administrator account created:'
+# When done you see 'Administrator account created:'
+```
**Note:** You can set the Administrator/root password and e-mail by supplying them in environmental variables, `GITLAB_ROOT_PASSWORD` and `GITLAB_ROOT_EMAIL` respectively, as seen below. If you don't set the password (and it is set to the default one) please wait with exposing GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login you'll be forced to change the default password.
- sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword GITLAB_ROOT_EMAIL=youremail
+```sh
+sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword GITLAB_ROOT_EMAIL=youremail
+```
### Secure secrets.yml
@@ -503,43 +566,58 @@ Otherwise your secrets are exposed if one of your backups is compromised.
Download the init script (will be `/etc/init.d/gitlab`):
- sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```sh
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
And if you are installing with a non-default folder or user copy and edit the defaults file:
- sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab
+```sh
+sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab
+```
If you installed GitLab in another directory or as a user other than the default you should change these settings in `/etc/default/gitlab`. Do not edit `/etc/init.d/gitlab` as it will be changed on upgrade.
Make GitLab start on boot:
- sudo update-rc.d gitlab defaults 21
+```sh
+sudo update-rc.d gitlab defaults 21
+```
### Set up Logrotate
- sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
+```sh
+sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
+```
### Check Application Status
Check if GitLab and its environment are configured correctly:
- sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
-
+```sh
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
### Compile GetText PO files
- sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+```
### Compile Assets
- sudo -u git -H yarn install --production --pure-lockfile
- sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+```sh
+sudo -u git -H yarn install --production --pure-lockfile
+sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+```
### Start Your GitLab Instance
- sudo service gitlab start
- # or
- sudo /etc/init.d/gitlab restart
+```sh
+sudo service gitlab start
+# or
+sudo /etc/init.d/gitlab restart
+```
## 9. Nginx
@@ -547,27 +625,33 @@ Check if GitLab and its environment are configured correctly:
### Installation
- sudo apt-get install -y nginx
+```sh
+sudo apt-get install -y nginx
+```
### Site Configuration
Copy the example site config:
- sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab
- sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
+```sh
+sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab
+sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
+```
Make sure to edit the config file to match your setup. Also, ensure that you match your paths to GitLab, especially if installing for a user other than the 'git' user:
- # Change YOUR_SERVER_FQDN to the fully-qualified
- # domain name of your host serving GitLab.
- #
- # Remember to match your paths to GitLab, especially
- # if installing for a user other than 'git'.
- #
- # If using Ubuntu default nginx install:
- # either remove the default_server from the listen line
- # or else sudo rm -f /etc/nginx/sites-enabled/default
- sudo editor /etc/nginx/sites-available/gitlab
+```sh
+# Change YOUR_SERVER_FQDN to the fully-qualified
+# domain name of your host serving GitLab.
+#
+# Remember to match your paths to GitLab, especially
+# if installing for a user other than 'git'.
+#
+# If using Ubuntu default nginx install:
+# either remove the default_server from the listen line
+# or else sudo rm -f /etc/nginx/sites-enabled/default
+sudo editor /etc/nginx/sites-available/gitlab
+```
If you intend to enable GitLab pages, there is a separate Nginx config you need
to use. Read all about the needed configuration at the
@@ -579,13 +663,17 @@ to use. Read all about the needed configuration at the
Validate your `gitlab` or `gitlab-ssl` Nginx config file with the following command:
- sudo nginx -t
+```sh
+sudo nginx -t
+```
You should receive `syntax is okay` and `test is successful` messages. If you receive errors check your `gitlab` or `gitlab-ssl` Nginx config file for typos, etc. as indicated in the error message given.
### Restart
- sudo service nginx restart
+```sh
+sudo service nginx restart
+```
## Done!
@@ -593,7 +681,9 @@ You should receive `syntax is okay` and `test is successful` messages. If you re
To make sure you didn't miss anything run a more thorough check with:
- sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
If all items are green, then congratulations on successfully installing GitLab!
@@ -680,31 +770,39 @@ for the changes to take effect.
If you'd like to connect to a Redis server on a non-standard port or on a different host, you can configure its connection string via the `config/resque.yml` file.
- # example
- production:
- url: redis://redis.example.tld:6379
+```
+# example
+production:
+ url: redis://redis.example.tld:6379
+```
If you want to connect the Redis server via socket, then use the "unix:" URL scheme and the path to the Redis socket file in the `config/resque.yml` file.
- # example
- production:
- url: unix:/path/to/redis/socket
+```
+# example
+production:
+ url: unix:/path/to/redis/socket
+```
Also you can use environment variables in the `config/resque.yml` file:
- # example
- production:
- url: <%= ENV.fetch('GITLAB_REDIS_URL') %>
+```
+# example
+production:
+ url: <%= ENV.fetch('GITLAB_REDIS_URL') %>
+```
### Custom SSH Connection
If you are running SSH on a non-standard port, you must change the GitLab user's SSH config.
- # Add to /home/git/.ssh/config
- host localhost # Give your setup a name (here: override localhost)
- user git # Your remote git user
- port 2222 # Your port number
- hostname 127.0.0.1; # Your server name or IP
+```
+# Add to /home/git/.ssh/config
+host localhost # Give your setup a name (here: override localhost)
+ user git # Your remote git user
+ port 2222 # Your port number
+ hostname 127.0.0.1; # Your server name or IP
+```
You also need to change the corresponding options (e.g. `ssh_user`, `ssh_host`, `admin_uri`) in the `config\gitlab.yml` file.
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
index 498b702cab1..2ae5485869e 100644
--- a/doc/install/kubernetes/gitlab_omnibus.md
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -7,7 +7,7 @@ instead. A comparison of the two charts is available in [this video](https://you
For more information on available GitLab Helm Charts, see the [charts overview](index.md#chart-overview).
- This GitLab-Omnibus chart has been tested on Google Kubernetes Engine and Azure Container Service.
-- This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work.
+- This work is based partially on: <https://github.com/lwolf/kubernetes-gitlab/>. GitLab would like to thank Sergey Nuzhdin for his work.
## Introduction
@@ -32,7 +32,7 @@ The deployment includes:
## Limitations
[High Availability](../../administration/high_availability/README.md) and
-[Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html) are not supported.
+[Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html) are not supported.
## Requirements
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 25a87dbd9de..509020d1975 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -8,6 +8,12 @@ date: 2016-06-28
# How to install GitLab on OpenShift Origin 3
+CAUTION: **Deprecated:**
+This article is deprecated. Use the official Kubernetes Helm charts for
+installing GitLab to OpenShift. Check out the
+[official installation docs](https://gitlab.com/charts/gitlab/blob/master/doc/cloud/openshift.md)
+for details.
+
## Introduction
[OpenShift Origin][openshift] is an open source container application
diff --git a/doc/install/structure.md b/doc/install/structure.md
index d58b0040eef..8fc6ab4ab2f 100644
--- a/doc/install/structure.md
+++ b/doc/install/structure.md
@@ -9,10 +9,10 @@ This is the directory structure you will end up with following the instructions
| |-- 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)**
+- `/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.*
diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md
index 200fe6f5206..35024a78fca 100644
--- a/doc/integration/akismet.md
+++ b/doc/integration/akismet.md
@@ -18,7 +18,7 @@ from happening.
To use Akismet:
-1. Go to the URL: https://akismet.com/account/
+1. Go to the URL: <https://akismet.com/account/>
1. Sign-in or create a new account.
diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md
index bccaeec3706..e2ed7a4b1ab 100644
--- a/doc/integration/auth0.md
+++ b/doc/integration/auth0.md
@@ -21,12 +21,12 @@ configuration file. For example:
- Client Secret: `KbveM3nqfjwCbrhaUy_gDu2dss8TIlHIdzlyf33pB7dEK5u_NyQdp65O_o02hXs2`
1. Fill in the Allowed Callback URLs:
- - http://`YOUR_GITLAB_URL`/users/auth/auth0/callback (or)
- - https://`YOUR_GITLAB_URL`/users/auth/auth0/callback
+ - `http://YOUR_GITLAB_URL/users/auth/auth0/callback` (or)
+ - `https://YOUR_GITLAB_URL/users/auth/auth0/callback`
1. Fill in the Allowed Origins (CORS):
- - http://`YOUR_GITLAB_URL` (or)
- - https://`YOUR_GITLAB_URL`
+ - `http://YOUR_GITLAB_URL` (or)
+ - `https://YOUR_GITLAB_URL`
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/azure.md b/doc/integration/azure.md
index 634dd952448..7a6d4bb143f 100644
--- a/doc/integration/azure.md
+++ b/doc/integration/azure.md
@@ -15,12 +15,12 @@ To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your ap
- Type: 'WEB APPLICATION AND/OR WEB API'
1. On the "App properties" page enter the needed URI's and click the "Complete" button.
- - SIGN-IN URL: Enter the URL of your GitLab installation (e.g 'https://gitlab.mycompany.com/')
- - APP ID URI: Enter the endpoint URL for Microsoft to use, just has to be unique (e.g 'https://mycompany.onmicrosoft.com/gitlab')
+ - SIGN-IN URL: Enter the URL of your GitLab installation (e.g `https://gitlab.mycompany.com/`)
+ - APP ID URI: Enter the endpoint URL for Microsoft to use, just has to be unique (e.g `https://mycompany.onmicrosoft.com/gitlab`)
1. Select "Configure" in the top menu.
-1. Add a "Reply URL" pointing to the Azure OAuth callback of your GitLab installation (e.g. https://gitlab.mycompany.com/users/auth/azure_oauth2/callback).
+1. Add a "Reply URL" pointing to the Azure OAuth callback of your GitLab installation (e.g. `https://gitlab.mycompany.com/users/auth/azure_oauth2/callback`).
1. Create a "Client secret" by selecting a duration, the secret will be generated as soon as you click the "Save" button in the bottom menu..
@@ -28,7 +28,7 @@ To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your ap
1. Select "View endpoints" from the bottom menu.
-1. You will see lots of endpoint URLs in the form 'https://login.microsoftonline.com/TENANT ID/...', note down the TENANT ID part of one of those endpoints.
+1. You will see lots of endpoint URLs in the form `https://login.microsoftonline.com/TENANT ID/...`, note down the TENANT ID part of one of those endpoints.
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/gmail_action_buttons_for_gitlab.md b/doc/integration/gmail_action_buttons_for_gitlab.md
index c19320471e3..cc5d444c069 100644
--- a/doc/integration/gmail_action_buttons_for_gitlab.md
+++ b/doc/integration/gmail_action_buttons_for_gitlab.md
@@ -14,9 +14,9 @@ To get this functioning, you need to be registered with Google.
Pay close attention to:
-* Email account used by GitLab to send notification emails needs to have "Consistent history of sending a high volume of mail from your domain (order of hundred emails a day minimum to Gmail) for a few weeks at least".
-* "A very very low rate of spam complaints from users."
-* Emails must be authenticated via DKIM or SPF
-* Before sending the final form("Gmail Schema Whitelist Request"), you must send a real email from your production server. This means that you will have to find a way to send this email from the email address you are registering. You can do this by, for example, forwarding the real email from the email address you are registering or going into the rails console on the GitLab server and triggering the email sending from there.
+- Email account used by GitLab to send notification emails needs to have "Consistent history of sending a high volume of mail from your domain (order of hundred emails a day minimum to Gmail) for a few weeks at least".
+- "A very very low rate of spam complaints from users."
+- Emails must be authenticated via DKIM or SPF.
+- Before sending the final form ("Gmail Schema Whitelist Request"), you must send a real email from your production server. This means that you will have to find a way to send this email from the email address you are registering. You can do this by, for example, forwarding the real email from the email address you are registering or going into the rails console on the GitLab server and triggering the email sending from there.
You can check how it looks going through all the steps laid out in the "Registering with Google" doc in [this GitLab.com issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/1517).
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index acc9db15826..c02a29dffb4 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -3,8 +3,11 @@
This document is about using GitLab as an OAuth authentication service provider
to sign in to other services.
-If you want to use other OAuth authentication service providers to sign in to
-GitLab, please see the [OAuth2 client documentation](../api/oauth2.md).
+If you want to use:
+
+- Other OAuth authentication service providers to sign in to
+ GitLab, see the [OAuth2 client documentation](omniauth.md).
+- The related API, see [Applications API](../api/applications.md).
## Introduction to OAuth
@@ -28,7 +31,7 @@ GitLab supports two ways of adding a new OAuth2 application to an instance. You
can either add an application as a regular user or add it in the admin area.
What this means is that GitLab can actually have instance-wide and a user-wide
applications. There is no difference between them except for the different
-permission levels they are set (user/admin). The default callback URL is
+permission levels they are set (user/admin). The default callback URL is
`http://your-gitlab.example.com/users/auth/gitlab/callback`
## Adding an application through the profile
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 4e1d5ba9b35..f5f1f9486c2 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -66,7 +66,7 @@ that are in common for all providers that we need to consider.
To change these settings:
-* **For omnibus package**
+- **For omnibus package**
Open the configuration file:
@@ -89,7 +89,7 @@ To change these settings:
gitlab_rails['omniauth_block_auto_created_users'] = true
```
-* **For installations from source**
+- **For installations from source**
Open the configuration file:
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index a7470d27b4b..bb3cd9a005f 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -287,7 +287,7 @@ so you will not be able to sign in using local credentials. Make sure that at le
of the SAML users has admin permissions.
You may also bypass the auto signin feature by browsing to
-https://gitlab.example.com/users/sign_in?auto_sign_in=false.
+`https://gitlab.example.com/users/sign_in?auto_sign_in=false`.
### `attribute_statements`
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index d0976b6201e..1cbfd81dfa9 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -10,8 +10,8 @@ To enable the Twitter OmniAuth provider you must register your application with
- Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or
something else descriptive.
- Description: Create a description.
- - Website: The URL to your GitLab installation. 'https://gitlab.example.com'
- - Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
+ - Website: The URL to your GitLab installation. `https://gitlab.example.com`
+ - Callback URL: `https://gitlab.example.com/users/auth/twitter/callback`
- Agree to the "Developer Agreement".
![Twitter App Details](img/twitter_app_details.png)
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 9347a834510..4c4b423f40f 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -71,7 +71,7 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production SKIP=r
```
If this fails you need to fix it before upgrading to 8.0. Also see
-https://about.gitlab.com/getting-help/
+<https://about.gitlab.com/get-help/>
### 2. Check source and target database types
@@ -118,7 +118,7 @@ From this point on, GitLab CI will be unavailable for your end users.
### 1. Upgrade GitLab to 8.0
First upgrade your GitLab server to version 8.0:
-https://about.gitlab.com/update/
+<https://about.gitlab.com/update/>
### 2. Disable CI on the GitLab server during the migration
diff --git a/doc/policy/maintenance.md b/doc/policy/maintenance.md
index 03ba2ae8817..1d656574acd 100644
--- a/doc/policy/maintenance.md
+++ b/doc/policy/maintenance.md
@@ -3,21 +3,23 @@
## Versioning
GitLab follows the [Semantic Versioning](http://semver.org/) for its releases:
-`(Major).(Minor).(Patch)` in a [pragmatic way].
-
-- **Major version**: Whenever there is something significant or any backwards
- incompatible changes are introduced to the public API.
-- **Minor version**: When new, backwards compatible functionality is introduced
- to the public API or a minor feature is introduced, or when a set of smaller
- features is rolled out.
-- **Patch number**: When backwards compatible bug fixes are introduced that fix
- incorrect behavior.
+`(Major).(Minor).(Patch)` in a [pragmatic way](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e).
For example, for GitLab version 10.5.7:
-* `10` represents major version
-* `5` represents minor version
-* `7` represents patch number
+- `10` represents the major version. The major release was 10.0.0, but often referred to as 10.0.
+- `5` represents the minor version. The minor release was 10.5.0, but often referred to as 10.5.
+- `7` represents the patch number.
+
+Any part of the version number can increment into multiple digits, for example, 13.10.11.
+
+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. | |
+| 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. |
## Patch releases
@@ -55,20 +57,20 @@ cases you need to consider.
It is considered safe to jump between patch versions and minor versions within
one major version. For example, it is safe to:
-* Upgrade the patch version:
- * `8.9.0` -> `8.9.7`
- * `8.9.0` -> `8.9.1`
- * `8.9.2` -> `8.9.6`
-* Upgrade the minor version:
- * `8.9.4` -> `8.12.3`
- * `9.2.3` -> `9.5.5`
+- Upgrade the patch version:
+ - `8.9.0` -> `8.9.7`
+ - `8.9.0` -> `8.9.1`
+ - `8.9.2` -> `8.9.6`
+- Upgrade the minor version:
+ - `8.9.4` -> `8.12.3`
+ - `9.2.3` -> `9.5.5`
Upgrading the major version requires more attention.
We cannot guarantee that upgrading between major versions will be seamless. As previously mentioned, major versions are reserved for backwards incompatible changes.
We recommend that you first upgrade to the latest available minor version within
your major version. By doing this, you can address any deprecation messages
-that could possibly change behaviour in the next major release.
+that could change behavior in the next major release.
Please see the table below for some examples:
@@ -79,9 +81,5 @@ Please see the table below for some examples:
| 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-tools documentation][rel]. You may also want to read our
-[Responsible Disclosure Policy][disclosure].
-
-[rel]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/
-[disclosure]: https://about.gitlab.com/disclosure/
-[pragmatic way]: https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e
+[release documentation](https://gitlab.com/gitlab-org/release/docs). You may also want to read our
+[Responsible Disclosure Policy](https://about.gitlab.com/security/disclosure/).
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 57bc71d2903..037b71a27b9 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -311,6 +311,11 @@ For installations from source:
remote_directory: 'my.s3.bucket'
# Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
# encryption: 'AES256'
+ # Turns on AWS Server-Side Encryption with Amazon Customer-Provided Encryption Keys for backups, this is optional
+ # This should be set to the base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data.
+ # 'encryption' must also be set in order for this to have any effect.
+ # To avoid storing the key on disk, the key can also be specified via the `GITLAB_BACKUP_ENCRYPTION_KEY` environment variable.
+ # encryption_key: '<base64 key>'
# Specifies Amazon S3 storage class to use for backups, this is optional
# storage_class: 'STANDARD'
```
@@ -373,7 +378,7 @@ with the name of your bucket:
If you want to use Google Cloud Storage to save backups, you'll have to create
an access key from the Google console first:
-1. Go to the storage settings page https://console.cloud.google.com/storage/settings
+1. Go to the storage settings page <https://console.cloud.google.com/storage/settings>
1. Select "Interoperability" and create an access key
1. Make note of the "Access Key" and "Secret" and replace them in the
configurations below
diff --git a/doc/security/crime_vulnerability.md b/doc/security/crime_vulnerability.md
index 94ba5d1375d..d61a205d954 100644
--- a/doc/security/crime_vulnerability.md
+++ b/doc/security/crime_vulnerability.md
@@ -17,8 +17,8 @@ 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:
-* [Source installation NGINX file][source-nginx]
-* [Omnibus installation NGINX file][omnibus-nginx]
+- [Source installation NGINX file][source-nginx]
+- [Omnibus installation NGINX file][omnibus-nginx]
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
@@ -52,9 +52,9 @@ vulnerability.
### References
-* Nginx ["Module ngx_http_spdy_module"][ngx-spdy]
-* Tenable Network Security, Inc. ["Transport Layer Security (TLS) Protocol CRIME Vulnerability"][nessus]
-* Wikipedia contributors, ["CRIME"][wiki-crime] Wikipedia, The Free Encyclopedia
+- Nginx ["Module ngx_http_spdy_module"][ngx-spdy]
+- Tenable Network Security, Inc. ["Transport Layer Security (TLS) Protocol CRIME Vulnerability"][nessus]
+- Wikipedia contributors, ["CRIME"][wiki-crime] Wikipedia, The Free Encyclopedia
[source-nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/nginx/gitlab-ssl
[omnibus-nginx]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index b17b0a4bc4a..8c26bbac6a7 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -6,13 +6,13 @@ With [Webhooks](../user/project/integrations/webhooks.md), you and your project
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.
-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`):
+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)
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index e570627bfc1..09a97fcea07 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -318,7 +318,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
+How to 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/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index cce02d218c2..6d63906ea4d 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -272,7 +272,7 @@ If the user is blocked via LDAP, `state` will be `ldap_blocked`.
}
```
-`owner_name` and `owner_email` are always `null`. Please see https://gitlab.com/gitlab-org/gitlab-ce/issues/39675.
+`owner_name` and `owner_email` are always `null`. Please see <https://gitlab.com/gitlab-org/gitlab-ce/issues/39675>.
**Group removed:**
@@ -289,7 +289,7 @@ If the user is blocked via LDAP, `state` will be `ldap_blocked`.
}
```
-`owner_name` and `owner_email` are always `null`. Please see https://gitlab.com/gitlab-org/gitlab-ce/issues/39675.
+`owner_name` and `owner_email` are always `null`. Please see <https://gitlab.com/gitlab-org/gitlab-ce/issues/39675>.
**Group renamed:**
@@ -309,7 +309,7 @@ If the user is blocked via LDAP, `state` will be `ldap_blocked`.
}
```
-`owner_name` and `owner_email` are always `null`. Please see https://gitlab.com/gitlab-org/gitlab-ce/issues/39675.
+`owner_name` and `owner_email` are always `null`. Please see <https://gitlab.com/gitlab-org/gitlab-ce/issues/39675>.
**New Group Member:**
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index e937d372384..ff798a51eb7 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -284,21 +284,44 @@ to understand how each one works.
### Auto Build
-Auto Build creates a build of the application in one of two ways:
-
-- If there is a `Dockerfile`, it will use `docker build` to create a Docker image.
-- Otherwise, it will use [Herokuish](https://github.com/gliderlabs/herokuish)
- and [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks)
- to automatically detect and build the application into a Docker image.
+Auto Build creates a build of the application using an existing `Dockerfile` or
+Heroku buildpacks.
Either way, the resulting Docker image is automatically pushed to the
[Container Registry][container-registry] and tagged with the commit SHA.
-CAUTION: **Important:**
+#### Auto Build using a Dockerfile
+
+If a project's repository contains a `Dockerfile`, Auto Build will use
+`docker build` to create a Docker image.
+
If you are also using Auto Review Apps and Auto Deploy and choose to provide
your own `Dockerfile`, make sure you expose your application to port
`5000` as this is the port assumed by the default Helm chart.
+#### Auto Build using Heroku buildpacks
+
+Auto Build builds an application using a project's `Dockerfile` if present, or
+otherwise it will use [Herokuish](https://github.com/gliderlabs/herokuish)
+and [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks)
+to automatically detect and build the application into a Docker image.
+
+Each buildpack requires certain files to be in your project's repository for
+Auto Build to successfully build your application. For example, the following
+files are required at the root of your application's repository, depending on
+the language:
+
+- A `Pipfile` or `requirements.txt` file for Python projects.
+- A `Gemfile` or `Gemfile.lock` file for Ruby projects.
+
+For the requirements of other languages and frameworks, read the
+[buildpacks docs](https://devcenter.heroku.com/articles/buildpacks#officially-supported-buildpacks).
+
+TIP: **Tip:**
+If Auto Build fails despite the project meeting the buildpack requirements, set
+a project variable `TRACE=true` to enable verbose logging, which may help to
+troubleshoot.
+
### Auto Test
Auto Test automatically runs the appropriate tests for your application using
@@ -516,9 +539,9 @@ a helm pre-upgrade hook.
For example, in a Rails application:
-* `DB_INITIALIZE` can be set to `cd /app && RAILS_ENV=production
+- `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_MIGRATE` can be set to `cd /app && RAILS_ENV=production bin/update`
NOTE: **Note:**
The `/app` path is the directory of your project inside the docker image
@@ -609,7 +632,12 @@ repo or by specifying a project variable:
one](https://gitlab.com/charts/auto-deploy-app).
This can be a great way to control exactly how your application is deployed.
- **Project variable** - Create a [project variable](../../ci/variables/README.md#variables)
- `AUTO_DEVOPS_CHART` with the URL of a custom chart to use.
+ `AUTO_DEVOPS_CHART` with the URL of a custom chart to use or create two project variables `AUTO_DEVOPS_CHART_REPOSITORY` with the URL of a custom chart repository and `AUTO_DEVOPS_CHART` with the path to the chart.
+
+### 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).
### Customizing `.gitlab-ci.yml`
@@ -655,10 +683,13 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| ------------ | --------------- |
| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). |
| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/auto-deploy-app). |
+| `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
| `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 |
+| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
+| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
@@ -679,6 +710,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `REVIEW_DISABLED` | From GitLab 11.0, this variable can be used to disable the `review` and the manual `review:stop` job. If the variable is present, these jobs will not be created. |
| `DAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dast` job. If the variable is present, the job will not be created. |
| `PERFORMANCE_DISABLED` | From GitLab 11.0, this variable can be used to disable the `performance` job. If the variable is present, the job will not be created. |
+| `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) will be made available by Auto DevOps as environment variables to the deployed application. |
TIP: **Tip:**
Set up the replica variables using a
@@ -690,6 +722,63 @@ You should *not* scale your application using Kubernetes directly. This can
cause confusion with Helm not detecting the change, and subsequent deploys with
Auto DevOps can undo your changes.
+#### Application secret variables
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/49056) in GitLab 11.7.
+
+Some applications need to define secret variables that are
+accessible by the deployed application. Auto DevOps detects variables where the key starts with
+`K8S_SECRET_` and make these prefixed variables available to the
+deployed application, as environment variables.
+
+To configure your application variables:
+
+1. Go to your project's **Settings > CI/CD**, then expand the section
+ called **Variables**.
+
+2. Create a CI Variable, ensuring the key is prefixed with
+ `K8S_SECRET_`. For example, you can create a variable with key
+`K8S_SECRET_RAILS_MASTER_KEY`.
+
+3. Run an Auto Devops pipeline either by manually creating a new
+ pipeline or by pushing a code change to GitLab.
+
+Auto DevOps pipelines will take your application secret variables to
+populate a Kubernetes secret. This secret is unique per environment.
+When deploying your application, the secret is loaded as environment
+variables in the container running the application. Following the
+example above, you can see the secret below containing the
+`RAILS_MASTER_KEY` variable.
+
+```sh
+$ kubectl get secret production-secret -n minimal-ruby-app-54 -o yaml
+apiVersion: v1
+data:
+ RAILS_MASTER_KEY: MTIzNC10ZXN0
+kind: Secret
+metadata:
+ creationTimestamp: 2018-12-20T01:48:26Z
+ name: production-secret
+ namespace: minimal-ruby-app-54
+ resourceVersion: "429422"
+ selfLink: /api/v1/namespaces/minimal-ruby-app-54/secrets/production-secret
+ uid: 57ac2bfd-03f9-11e9-b812-42010a9400e4
+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
+application pods will not have the updated secrets. In this case, you
+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.
+
#### Advanced replica variables setup
Apart from the two replica-related variables for production mentioned above,
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 7c7e44d29e7..404a686f1cf 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -241,7 +241,7 @@ 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/gitlab-geo/README.html) for cloning and fetching projects, in addition to reading any data. This will make working with large repositories over large distances much faster.
+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
@@ -303,7 +303,7 @@ A [tool](https://docs.gitlab.com/ee/integration/external-issue-tracker.html) use
### 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).
+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
@@ -407,7 +407,7 @@ A free disaster recovery [software](https://help.ubuntu.com/community/MondoMindi
#### Mount
-External reference:
+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."
@@ -423,7 +423,7 @@ A set of symbols that are used to organize objects of various kinds so that thes
### 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.
+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
@@ -447,7 +447,7 @@ Software for which the original source code is freely [available](https://openso
#### Open Source Stewardship
-[Related blog post](https://about.gitlab.com/2016/01/11/being-a-good-open-source-steward/).
+[Related blog post](https://about.gitlab.com/2016/01/11/being-a-good-open-source-steward/).
### Owner
@@ -557,7 +557,7 @@ Software that is hosted centrally and accessed on-demand (i.e. whenever you want
This term is often used by people when they mean "Version Control."
-### Scrum
+### 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.
@@ -697,7 +697,7 @@ A [website/system](http://www.wiki.com/) that allows for collaborative editing o
### Working area
-Files that have been modified but are not committed. Check them by using the command "git status".
+Files that have been modified but are not committed. Check them by using the command "git status".
### Working Tree
diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md
index b21cf27c1d3..77a1892b656 100644
--- a/doc/university/high-availability/aws/README.md
+++ b/doc/university/high-availability/aws/README.md
@@ -66,10 +66,10 @@ RDS instances as well.
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
+- 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
@@ -161,7 +161,7 @@ private subnets.
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
+subnet and security group and
![Redis Cluster details](img/redis-cluster-det.png)
@@ -206,7 +206,7 @@ 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.
+associate it with our new EC2 instance.
### RDS and Redis Security Group
@@ -268,8 +268,8 @@ 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_encoding'] = "unicode"
+ gitlab_rails['db_database'] = "gitlabhq_production"
gitlab_rails['db_username'] = "gitlab"
gitlab_rails['db_password'] = "mypassword"
gitlab_rails['db_host'] = "<rds-endpoint>"
@@ -288,9 +288,9 @@ to make the EFS integration easier to manage.
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
+ 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.
@@ -395,5 +395,5 @@ 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](http://docs.gitlab.com/ee/gitlab-geo/README.html)
+- [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)
diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md
index 701533358c8..53578a34d1c 100644
--- a/doc/university/training/end-user/README.md
+++ b/doc/university/training/end-user/README.md
@@ -50,7 +50,7 @@ project.
- Use the tools at your disposal when you get stuck.
- Use `git help <command>` command
- Use Google (i.e. StackOverflow, Google groups)
- - Read documentation at https://git-scm.com
+ - Read documentation at <https://git-scm.com>
---
@@ -62,7 +62,7 @@ Workshop Time!
### Setup
- Windows: Install 'Git for Windows'
- - https://git-for-windows.github.io
+ - <https://git-for-windows.github.io>
- Mac: Type `git` in the Terminal application.
- If it's not installed, it will prompt you to install it.
- Linux
@@ -142,7 +142,7 @@ cd ~/workspace
- Sign in into your gitlab.com account
- Create a project
-- 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>
- On your machine clone the `training-examples` project
---
@@ -242,10 +242,11 @@ git push origin squash_some_bugs
---
### Merge Conflicts
-* Happen often
-* Learning to fix conflicts is hard
-* Practice makes perfect
-* Force push after fixing conflicts. Be careful!
+
+- Happen often
+- Learning to fix conflicts is hard
+- Practice makes perfect
+- Force push after fixing conflicts. Be careful!
---
@@ -306,10 +307,10 @@ Create a merge request on the GitLab web UI. You'll see a conflict warning.
### Notes
-* When to use `git merge` and when to use `git rebase`
-* Rebase when updating your branch with master
-* Merge when bringing changes from feature to master
-* Reference: https://www.atlassian.com/git/tutorials/merging-vs-rebasing/
+- When to use `git merge` and when to use `git rebase`
+- Rebase when updating your branch with master
+- Merge when bringing changes from feature to master
+- Reference: <https://www.atlassian.com/git/tutorials/merging-vs-rebasing/>
---
diff --git a/doc/university/training/topics/env_setup.md b/doc/university/training/topics/env_setup.md
index bdf805711e0..78ca30e0f55 100644
--- a/doc/university/training/topics/env_setup.md
+++ b/doc/university/training/topics/env_setup.md
@@ -8,7 +8,7 @@ comments: false
## Install
- **Windows**
- - Install 'Git for Windows' from https://git-for-windows.github.io
+ - Install 'Git for Windows' from <https://git-for-windows.github.io>
- **Mac**
- Type '`git`' in the Terminal application.
diff --git a/doc/university/training/topics/getting_started.md b/doc/university/training/topics/getting_started.md
index 66cb08feacb..d76ff57bfa3 100644
--- a/doc/university/training/topics/getting_started.md
+++ b/doc/university/training/topics/getting_started.md
@@ -8,12 +8,12 @@ comments: false
## Instantiating Repositories
-* Create a new repository by instantiating it through
+- Create a new repository by instantiating it through:
```bash
git init
```
-* Copy an existing project by cloning the repository through
+- Copy an existing project by cloning the repository through:
```bash
git clone <url>
@@ -23,9 +23,9 @@ comments: false
## Central Repos
-* To instantiate a central repository a `--bare` flag is required.
-* Bare repositories don't allow file editing or committing changes.
-* Create a bare repo with
+- To instantiate a central repository a `--bare` flag is required.
+- Bare repositories don't allow file editing or committing changes.
+- Create a bare repo with:
```bash
git init --bare project-name.git
@@ -97,5 +97,5 @@ git log
## Note
-* git fetch vs pull
-* Pull is git fetch + git merge
+- git fetch vs pull
+- Pull is git fetch + git merge
diff --git a/doc/university/training/topics/git_add.md b/doc/university/training/topics/git_add.md
index b1483e725fe..e02a7deab91 100644
--- a/doc/university/training/topics/git_add.md
+++ b/doc/university/training/topics/git_add.md
@@ -10,13 +10,13 @@ comments: false
Adds content to the index or staging area.
-* Adds a list of file
+- Adds a list of file:
```bash
git add <files>
```
-* Adds all files including deleted ones
+- Adds all files including deleted ones:
```bash
git add -A
@@ -26,19 +26,19 @@ Adds content to the index or staging area.
## Git add continued
-* Add all text files in current dir
+- Add all text files in current dir:
```bash
git add *.txt
```
-* Add all text file in the project
+- Add all text file in the project:
```bash
git add "*.txt*"
```
-* Adds all files in directory
+- Adds all files in directory:
```bash
git add views/layouts/
diff --git a/doc/university/training/topics/git_intro.md b/doc/university/training/topics/git_intro.md
index 7e502d6dad4..127323c292c 100644
--- a/doc/university/training/topics/git_intro.md
+++ b/doc/university/training/topics/git_intro.md
@@ -8,7 +8,7 @@ comments: false
## Intro
-https://git-scm.com/about
+<https://git-scm.com/about>
- Distributed version control
- Does not rely on connection to a central server
@@ -25,4 +25,4 @@ Use the tools at your disposal when you get stuck.
- Use '`git help <command>`' command
- Use Google
-- Read documentation at https://git-scm.com
+- Read documentation at <https://git-scm.com>
diff --git a/doc/university/training/topics/git_log.md b/doc/university/training/topics/git_log.md
index 6ba6f9eb69d..127fdf4d44a 100644
--- a/doc/university/training/topics/git_log.md
+++ b/doc/university/training/topics/git_log.md
@@ -8,19 +8,19 @@ comments: false
Git log lists commit history. It allows searching and filtering.
-* Initiate log
+- Initiate log:
```
git log
```
-* Retrieve set number of records:
+- Retrieve set number of records:
```
git log -n 2
```
-* Search commits by author. Allows user name or a regular expression.
+- Search commits by author. Allows user name or a regular expression.
```
git log --author="user_name"
@@ -28,13 +28,13 @@ Git log lists commit history. It allows searching and filtering.
----------
-* Search by comment message.
+- Search by comment message:
```
git log --grep="<pattern>"
```
-* Search by date
+- Search by date:
```
git log --since=1.month.ago --until=3.weeks.ago
diff --git a/doc/university/training/topics/merge_conflicts.md b/doc/university/training/topics/merge_conflicts.md
index 071baddf508..a7d42904229 100644
--- a/doc/university/training/topics/merge_conflicts.md
+++ b/doc/university/training/topics/merge_conflicts.md
@@ -68,7 +68,8 @@ git push origin conflicts_branch -f
----------
## Note
-* When to use 'git merge' and when to use 'git rebase'
-* Rebase when updating your branch with master
-* Merge when bringing changes from feature to master
-* Reference: https://www.atlassian.com/git/tutorials/merging-vs-rebasing/
+
+- When to use 'git merge' and when to use 'git rebase'
+- Rebase when updating your branch with master
+- Merge when bringing changes from feature to master
+- Reference: <https://www.atlassian.com/git/tutorials/merging-vs-rebasing/>
diff --git a/doc/university/training/topics/rollback_commits.md b/doc/university/training/topics/rollback_commits.md
index 44304634f36..ca3a64e4347 100644
--- a/doc/university/training/topics/rollback_commits.md
+++ b/doc/university/training/topics/rollback_commits.md
@@ -8,13 +8,13 @@ comments: false
## Undo Commits
-* Undo last commit putting everything back into the staging area.
+- Undo last commit putting everything back into the staging area:
```
git reset --soft HEAD^
```
-* Add files and change message with:
+- Add files and change message with:
```
git commit --amend -m "New Message"
@@ -22,13 +22,13 @@ comments: false
----------
-* Undo last and remove changes
+- Undo last and remove changes:
```
git reset --hard HEAD^
```
-* Same as last one but for two commits back
+- Same as last one but for two commits back:
```
git reset --hard HEAD^^
@@ -73,9 +73,9 @@ 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
+- git revert vs git reset
+- Reset removes the commit while revert removes the changes but leaves the commit
+- Revert is safer considering we can revert a revert
```
# Changed file
diff --git a/doc/university/training/topics/stash.md b/doc/university/training/topics/stash.md
index 42eedea14e5..f1c91fb1b37 100644
--- a/doc/university/training/topics/stash.md
+++ b/doc/university/training/topics/stash.md
@@ -9,7 +9,7 @@ comments: false
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
+- Stash:
```
git stash save
@@ -19,7 +19,7 @@ and we need to change to a different branch.
git stash save "this is a message to display on the list"
```
-* Apply stash to keep working on it
+- Apply stash to keep working on it:
```
git stash apply
@@ -29,7 +29,7 @@ and we need to change to a different branch.
----------
-* Every time we save a stash it gets stacked so by using list we can see all our
+- Every time we save a stash it gets stacked so by using list we can see all our
stashes.
```
@@ -38,7 +38,7 @@ stashes.
git stash list --stat
```
-* To clean our stack we need to manually remove them.
+- To clean our stack we need to manually remove them:
```
# drop top stash
@@ -51,15 +51,14 @@ stashes.
----------
-* Apply and drop on one command
+- Apply and drop on one command:
```
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.
+- If we meet conflicts we need to either reset or commit our changes.
+- Conflicts through `pop` will not drop a stash afterwards.
----------
diff --git a/doc/university/training/topics/subtree.md b/doc/university/training/topics/subtree.md
index b5a892dc17b..ba7c3394938 100644
--- a/doc/university/training/topics/subtree.md
+++ b/doc/university/training/topics/subtree.md
@@ -4,20 +4,20 @@ comments: false
# Subtree
-* Used when there are nested repositories.
-* Not recommended when the amount of dependencies is too large
-* For these cases we need a dependency control system
-* Command are painfully long so aliases are necessary
+- Used when there are nested repositories.
+- Not recommended when the amount of dependencies is too large.
+- 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'.
----------
diff --git a/doc/university/training/topics/unstage.md b/doc/university/training/topics/unstage.md
index ee7913637b9..da36a3218e5 100644
--- a/doc/university/training/topics/unstage.md
+++ b/doc/university/training/topics/unstage.md
@@ -8,13 +8,13 @@ comments: false
## 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.
```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:
+- 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:
```bash
git checkout -- <file>
@@ -22,14 +22,14 @@ comments: false
----------
-* To remove a file from disk and repo use 'git rm' and to rm a dir use the '-r' flag.
+- To remove a file from disk and repo use 'git rm' and to rm a dir use the '-r' flag:
```
git rm '*.txt'
git rm -r <dirname>
```
-* 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`.
+- If we want to remove a file from the repository but keep it on disk, say we forgot to add it to our `.gitignore` file then use `--cache`:
```
git rm <filename> --cache
diff --git a/doc/update/10.0-to-10.1.md b/doc/update/10.0-to-10.1.md
index 10cf02a984f..d4373ca3f23 100644
--- a/doc/update/10.0-to-10.1.md
+++ b/doc/update/10.0-to-10.1.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.1-to-10.2.md b/doc/update/10.1-to-10.2.md
index 20895a05567..0705b58ed7a 100644
--- a/doc/update/10.1-to-10.2.md
+++ b/doc/update/10.1-to-10.2.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.2-to-10.3.md b/doc/update/10.2-to-10.3.md
index 441a241d053..33a52d3e807 100644
--- a/doc/update/10.2-to-10.3.md
+++ b/doc/update/10.2-to-10.3.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.3-to-10.4.md b/doc/update/10.3-to-10.4.md
index 9f3efdd790e..3ba96535965 100644
--- a/doc/update/10.3-to-10.4.md
+++ b/doc/update/10.3-to-10.4.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.4-to-10.5.md b/doc/update/10.4-to-10.5.md
index 3766645a141..f00bbcaeaa6 100644
--- a/doc/update/10.4-to-10.5.md
+++ b/doc/update/10.4-to-10.5.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.5-to-10.6.md b/doc/update/10.5-to-10.6.md
index 986ecbf5ef0..6c3f8b663cc 100644
--- a/doc/update/10.5-to-10.6.md
+++ b/doc/update/10.5-to-10.6.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.6-to-10.7.md b/doc/update/10.6-to-10.7.md
index 10d29837bfb..9bd354a5bcd 100644
--- a/doc/update/10.6-to-10.7.md
+++ b/doc/update/10.6-to-10.7.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.7-to-10.8.md b/doc/update/10.7-to-10.8.md
index 0cc46fc5aa9..9aafd3f269f 100644
--- a/doc/update/10.7-to-10.8.md
+++ b/doc/update/10.7-to-10.8.md
@@ -52,7 +52,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.8-to-11.0.md b/doc/update/10.8-to-11.0.md
index ad3305d8ebd..f6fdc342e3d 100644
--- a/doc/update/10.8-to-11.0.md
+++ b/doc/update/10.8-to-11.0.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.0-to-11.1.md b/doc/update/11.0-to-11.1.md
index 5b2dd48a744..25a7c1cf929 100644
--- a/doc/update/11.0-to-11.1.md
+++ b/doc/update/11.0-to-11.1.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.1-to-11.2.md b/doc/update/11.1-to-11.2.md
index cb09d0a2505..ced59c245b8 100644
--- a/doc/update/11.1-to-11.2.md
+++ b/doc/update/11.1-to-11.2.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.2-to-11.3.md b/doc/update/11.2-to-11.3.md
index 228ff6cb70e..fa0c6872182 100644
--- a/doc/update/11.2-to-11.3.md
+++ b/doc/update/11.2-to-11.3.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.3-to-11.4.md b/doc/update/11.3-to-11.4.md
index 5f64bf81127..18bbfe4747e 100644
--- a/doc/update/11.3-to-11.4.md
+++ b/doc/update/11.3-to-11.4.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.4-to-11.5.md b/doc/update/11.4-to-11.5.md
index fd7a8e5c2ae..8f588f8b2ae 100644
--- a/doc/update/11.4-to-11.5.md
+++ b/doc/update/11.4-to-11.5.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
@@ -218,7 +218,7 @@ log_bin_trust_function_creators=1
Note: we have made [changes](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22372) to `unicorn.rb` to allow GitLab run with both Unicorn and Puma in future.
-- Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/11-5-stable/config/unicorn.rb.example but with your settings.
+- Make `/home/git/gitlab/config/unicorn.rb` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/11-5-stable/config/unicorn.rb.example> but with your settings.
- In particular, make sure that `require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"` line exists and the `before_exec`, `before_fork`, and `after_fork` handlers are configured as shown below:
```ruby
diff --git a/doc/update/11.5-to-11.6.md b/doc/update/11.5-to-11.6.md
index 2e9ec5d71de..f95ce54650e 100644
--- a/doc/update/11.5-to-11.6.md
+++ b/doc/update/11.5-to-11.6.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
@@ -218,7 +218,7 @@ log_bin_trust_function_creators=1
We have made [changes](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22372) to `unicorn.rb` to allow GitLab run with both Unicorn and Puma in future.
-Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/11-6-stable/config/unicorn.rb.example but with your settings.
+Make `/home/git/gitlab/config/unicorn.rb` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/11-6-stable/config/unicorn.rb.example> but with your settings.
In particular, make sure that `require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"` line exists and the `before_exec`, `before_fork`, and `after_fork` handlers are configured as shown below:
```ruby
@@ -317,11 +317,11 @@ sudo systemctl daemon-reload
```bash
cd /home/git/gitlab
-# MySQL installations (note: the line below states '--without postgres')
-sudo -u git -H bundle install --without postgres development test --deployment
-
# PostgreSQL installations (note: the line below states '--without mysql')
-sudo -u git -H bundle install --without mysql development test --deployment
+sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --deployment --without development test postgres aws kerberos
# Optional: clean up old gems
sudo -u git -H bundle clean
diff --git a/doc/update/11.6-to-11.7.md b/doc/update/11.6-to-11.7.md
new file mode 100644
index 00000000000..b4d830e8ce0
--- /dev/null
+++ b/doc/update/11.6-to-11.7.md
@@ -0,0 +1,390 @@
+---
+comments: false
+---
+
+# From 11.6 to 11.7
+
+Make sure you view this update guide 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 at the top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+NOTE: If you installed GitLab from source, make sure `rsync` is installed.
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: Beginning in GitLab 11.0, we only support Ruby 2.4 or higher, and dropped
+support for Ruby 2.3. Be sure to upgrade if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download Ruby and compile it:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
+echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz
+cd ruby-2.5.3
+
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-document --version '< 2'
+```
+
+### 4. Update Node
+
+GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets.
+This requires a minimum version of node v6.0.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v6.0.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+GitLab also requires the use of yarn `>= v1.10.0` to manage JavaScript
+dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 5. Update Go
+
+NOTE: GitLab 11.4 and higher only supports Go 1.10.x and newer, and dropped support for Go
+1.9.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# 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
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.10.5.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git fetch --all --prune
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 11-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 11-7-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 10. Update gitlab-pages
+
+#### Only needed if you use GitLab Pages
+
+Install and compile gitlab-pages. GitLab-Pages uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-pages
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+sudo -u git -H make
+```
+
+### 11. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 12. Update configuration files
+
+#### New `unicorn.rb` configuration
+
+We have made [changes](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22372) to `unicorn.rb` to allow GitLab run with both Unicorn and Puma in future.
+
+Make `/home/git/gitlab/config/unicorn.rb` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/11-7-stable/config/unicorn.rb.example> but with your settings.
+In particular, make sure that `require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"` line exists and the `before_exec`, `before_fork`, and `after_fork` handlers are configured as shown below:
+
+```ruby
+require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
+
+before_exec do |server|
+ # Signal application hooks that we're about to restart
+ Gitlab::Cluster::LifecycleEvents.do_master_restart
+end
+
+before_fork do |server, worker|
+ # Signal application hooks that we're about to fork
+ Gitlab::Cluster::LifecycleEvents.do_before_fork
+end
+
+after_fork do |server, worker|
+ # Signal application hooks of worker start
+ Gitlab::Cluster::LifecycleEvents.do_worker_start
+end
+```
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/11-6-stable:config/gitlab.yml.example origin/11-7-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/11-6-stable:lib/support/nginx/gitlab-ssl origin/11-7-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/11-6-stable:lib/support/nginx/gitlab origin/11-7-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-7-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-7-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/11-6-stable:lib/support/init.d/gitlab.default.example origin/11-7-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 13. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --deployment --without development test postgres aws kerberos
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+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
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 15. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (11.6)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 11.5 to 11.6](11.5-to-11.6.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-7-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-7-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/update/11.7-to-11.8.md b/doc/update/11.7-to-11.8.md
new file mode 100644
index 00000000000..d5cd557d7b5
--- /dev/null
+++ b/doc/update/11.7-to-11.8.md
@@ -0,0 +1,394 @@
+---
+comments: false
+---
+
+# From 11.7 to 11.8
+
+Make sure you view this update guide from the branch (version) of GitLab you would
+like to install (e.g., `11-8-stable`. You can select the branch in the version
+dropdown at the top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+NOTE: If you installed GitLab from source, make sure `rsync` is installed.
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: Beginning in GitLab 11.0, we only support Ruby 2.4 or higher, and dropped
+support for Ruby 2.3. Be sure to upgrade if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download Ruby and compile it:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
+echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz
+cd ruby-2.5.3
+
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-document --version '< 2'
+```
+
+### 4. Update Node
+
+NOTE: Beginning in GitLab 11.8, we only support node 8 or higher, and dropped
+support for node 6. Be sure to upgrade if necessary.
+
+GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets.
+This requires a minimum version of node v8.10.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v8.10.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+GitLab also requires the use of yarn `>= v1.10.0` to manage JavaScript
+dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 5. Update Go
+
+NOTE: GitLab 11.4 and higher only supports Go 1.10.x and newer, and dropped support for Go
+1.9.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# 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
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.10.5.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git fetch --all --prune
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 11-8-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 11-8-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 10. Update gitlab-pages
+
+#### Only needed if you use GitLab Pages
+
+Install and compile gitlab-pages. GitLab-Pages uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-pages
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+sudo -u git -H make
+```
+
+### 11. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 12. Update configuration files
+
+#### New `unicorn.rb` configuration
+
+We have made [changes](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22372) to `unicorn.rb` to allow GitLab run with both Unicorn and Puma in future.
+
+Make `/home/git/gitlab/config/unicorn.rb` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/config/unicorn.rb.example> but with your settings.
+In particular, make sure that `require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"` line exists and the `before_exec`, `before_fork`, and `after_fork` handlers are configured as shown below:
+
+```ruby
+require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
+
+before_exec do |server|
+ # Signal application hooks that we're about to restart
+ Gitlab::Cluster::LifecycleEvents.do_master_restart
+end
+
+before_fork do |server, worker|
+ # Signal application hooks that we're about to fork
+ Gitlab::Cluster::LifecycleEvents.do_before_fork
+end
+
+after_fork do |server, worker|
+ # Signal application hooks of worker start
+ Gitlab::Cluster::LifecycleEvents.do_worker_start
+end
+```
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/11-7-stable:config/gitlab.yml.example origin/11-8-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/11-7-stable:lib/support/nginx/gitlab-ssl origin/11-8-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/11-7-stable:lib/support/nginx/gitlab origin/11-8-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/11-7-stable:lib/support/init.d/gitlab.default.example origin/11-8-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 13. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --deployment --without development test postgres aws kerberos
+
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+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
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 15. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (11.7)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 11.6 to 11.7](11.6-to-11.7.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md
index 4faf5fa549e..bcc9058ff99 100644
--- a/doc/update/5.1-to-5.2.md
+++ b/doc/update/5.1-to-5.2.md
@@ -73,15 +73,15 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
## 5. Update config files
-- Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-2-stable/config/gitlab.yml.example but with your settings.
-- Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-2-stable/config/puma.rb.example but with your settings.
+- Make `/home/git/gitlab/config/gitlab.yml` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/5-2-stable/config/gitlab.yml.example> but with your settings.
+- Make `/home/git/gitlab/config/puma.rb` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/5-2-stable/config/puma.rb.example> but with your settings.
## 6. Update Init script
```bash
cd /home/git/gitlab
sudo rm /etc/init.d/gitlab
-sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
```
diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md
index 212343bac3f..5767c9cc121 100644
--- a/doc/update/5.1-to-5.4.md
+++ b/doc/update/5.1-to-5.4.md
@@ -70,8 +70,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
## 5. Update config files
-- Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/gitlab.yml.example but with your settings.
-- Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/puma.rb.example but with your settings.
+- Make `/home/git/gitlab/config/gitlab.yml` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/gitlab.yml.example> but with your settings.
+- Make `/home/git/gitlab/config/puma.rb` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/puma.rb.example> but with your settings.
## 6. Update Init script
diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md
index 865d38e0ca4..4993d034b6e 100644
--- a/doc/update/5.1-to-6.0.md
+++ b/doc/update/5.1-to-6.0.md
@@ -185,8 +185,8 @@ sudo -u git -H git config --global core.autocrlf input
Note: We switched from Puma in GitLab 5.x to unicorn in GitLab 6.0.
-- Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-0-stable/config/gitlab.yml.example but with your settings.
-- Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-0-stable/config/unicorn.rb.example but with your settings.
+- Make `/home/git/gitlab/config/gitlab.yml` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/6-0-stable/config/gitlab.yml.example> but with your settings.
+- Make `/home/git/gitlab/config/unicorn.rb` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/6-0-stable/config/unicorn.rb.example> but with your settings.
## 7. Update Init script
diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md
index ed4f3ebdd53..c378d2798f4 100644
--- a/doc/update/5.2-to-5.3.md
+++ b/doc/update/5.2-to-5.3.md
@@ -64,8 +64,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
## 4. Update config files
-- Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-3-stable/config/gitlab.yml.example but with your settings.
-- Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-3-stable/config/puma.rb.example but with your settings.
+- Make `/home/git/gitlab/config/gitlab.yml` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/5-3-stable/config/gitlab.yml.example> but with your settings.
+- Make `/home/git/gitlab/config/puma.rb` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/5-3-stable/config/puma.rb.example> but with your settings.
## 5. Update Init script
diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md
index 7277250eb32..77b1e9e5329 100644
--- a/doc/update/5.3-to-5.4.md
+++ b/doc/update/5.3-to-5.4.md
@@ -68,8 +68,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
## 5. Update config files
-- Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/gitlab.yml.example but with your settings.
-- Make `/home/git/gitlab/config/puma.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/puma.rb.example but with your settings.
+- Make `/home/git/gitlab/config/gitlab.yml` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/gitlab.yml.example> but with your settings.
+- Make `/home/git/gitlab/config/puma.rb` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/5-4-stable/config/puma.rb.example> but with your settings.
## 6. Update Init script
diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md
index dacdf05cc9c..2d2da769b89 100644
--- a/doc/update/5.4-to-6.0.md
+++ b/doc/update/5.4-to-6.0.md
@@ -118,8 +118,8 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
Note: We switched from Puma in GitLab 5.4 to unicorn in GitLab 6.0.
-- Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example but with your settings.
-- Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/unicorn.rb.example but with your settings.
+- Make `/home/git/gitlab/config/gitlab.yml` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example> but with your settings.
+- Make `/home/git/gitlab/config/unicorn.rb` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/unicorn.rb.example> but with your settings.
## 7. Update Init script
diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md
index a3c52a1cfb4..dd409175c27 100644
--- a/doc/update/6.0-to-6.1.md
+++ b/doc/update/6.0-to-6.1.md
@@ -85,8 +85,8 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
## 5. Update config files
-- Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-1-stable/config/gitlab.yml.example but with your settings.
-- Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-1-stable/config/unicorn.rb.example but with your settings.
+- Make `/home/git/gitlab/config/gitlab.yml` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/6-1-stable/config/gitlab.yml.example> but with your settings.
+- Make `/home/git/gitlab/config/unicorn.rb` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/6-1-stable/config/unicorn.rb.example> but with your settings.
## 6. Update Init script
diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md
index 36a395bf01e..cace80c99b7 100644
--- a/doc/update/6.1-to-6.2.md
+++ b/doc/update/6.1-to-6.2.md
@@ -79,15 +79,15 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
## 6. Update config files
-TIP: to see what changed in `gitlab.yml.example` in this release use next command:
+TIP: to see what changed in `gitlab.yml.example` in this release use next command:
```
git diff 6-1-stable:config/gitlab.yml.example 6-2-stable:config/gitlab.yml.example
```
-- Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-2-stable/config/gitlab.yml.example but with your settings.
+- Make `/home/git/gitlab/config/gitlab.yml` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/6-2-stable/config/gitlab.yml.example> but with your settings.
-- Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-2-stable/config/unicorn.rb.example but with your settings.
+- Make `/home/git/gitlab/config/unicorn.rb` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/6-2-stable/config/unicorn.rb.example> but with your settings.
- Copy rack attack middleware config:
diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md
index 02e87a08b8f..7205575942a 100644
--- a/doc/update/6.2-to-6.3.md
+++ b/doc/update/6.2-to-6.3.md
@@ -81,8 +81,8 @@ TIP: to see what changed in gitlab.yml.example in this release use next command:
git diff 6-2-stable:config/gitlab.yml.example 6-3-stable:config/gitlab.yml.example
```
-- Make `/home/git/gitlab/config/gitlab.yml` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-3-stable/config/gitlab.yml.example but with your settings.
-- Make `/home/git/gitlab/config/unicorn.rb` same as https://gitlab.com/gitlab-org/gitlab-ce/blob/6-3-stable/config/unicorn.rb.example but with your settings.
+- Make `/home/git/gitlab/config/gitlab.yml` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/6-3-stable/config/gitlab.yml.example> but with your settings.
+- Make `/home/git/gitlab/config/unicorn.rb` same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/6-3-stable/config/unicorn.rb.example> but with your settings.
```bash
# Copy rack attack middleware config
diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md
index 781c90e4198..e1ca34305b4 100644
--- a/doc/update/6.9-to-7.0.md
+++ b/doc/update/6.9-to-7.0.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 3. Get latest code
@@ -110,8 +110,8 @@ There are new configuration options available for gitlab.yml. View them with the
git diff origin/6-9-stable:config/gitlab.yml.example origin/7-0-stable:config/gitlab.yml.example
```
-* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab> but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl> but with your setting.
### 7. Start application
diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md
index 6fcec5b7974..674163091be 100644
--- a/doc/update/6.x-or-7.x-to-7.14.md
+++ b/doc/update/6.x-or-7.x-to-7.14.md
@@ -67,7 +67,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
## 3. Get latest code
@@ -178,16 +178,16 @@ TIP: to see what changed in `gitlab.yml.example` in this release use next comman
git diff 6-0-stable:config/gitlab.yml.example 7-14-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/unicorn.rb.example but with your settings.
-* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.5/config.yml.example but with your settings.
-* Copy rack attack middleware config
+- Make `/home/git/gitlab/config/gitlab.yml` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/gitlab.yml.example> but with your settings.
+- Make `/home/git/gitlab/config/unicorn.rb` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/config/unicorn.rb.example> but with your settings.
+- Make `/home/git/gitlab-shell/config.yml` the same as <https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.5/config.yml.example> but with your settings.
+- Copy rack attack middleware config.
```bash
sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
```
-* Set up logrotate
+- Set up logrotate
```bash
sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
@@ -195,9 +195,9 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab-ssl but with your settings.
-* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab> but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-14-stable/lib/support/nginx/gitlab-ssl> but with your settings.
+- A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
### Check the version of /usr/local/bin/git
diff --git a/doc/update/7.0-to-7.1.md b/doc/update/7.0-to-7.1.md
index fb4710faad5..8b69431dee1 100644
--- a/doc/update/7.0-to-7.1.md
+++ b/doc/update/7.0-to-7.1.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 3. Get latest code
diff --git a/doc/update/7.1-to-7.2.md b/doc/update/7.1-to-7.2.md
index 07f92ac3af6..44e5fc676b3 100644
--- a/doc/update/7.1-to-7.2.md
+++ b/doc/update/7.1-to-7.2.md
@@ -94,8 +94,8 @@ There are new configuration options available for `gitlab.yml`. View them with t
git diff 7-1-stable:config/gitlab.yml.example 7-2-stable:config/gitlab.yml.example
```
-* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab> but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl> but with your setting.
Update rack attack middleware config
diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md
index a16f9de54e4..2625df2def8 100644
--- a/doc/update/7.2-to-7.3.md
+++ b/doc/update/7.2-to-7.3.md
@@ -108,8 +108,8 @@ git diff origin/7-2-stable:config/gitlab.yml.example origin/7-3-stable:config/gi
sudo -u git -H sed -i 's/:backlog => 64/:backlog => 1024/' config/unicorn.rb
```
-* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab> but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl> but with your setting.
### 7. Start application
diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md
index 734c655f1d1..ad7930e8728 100644
--- a/doc/update/7.3-to-7.4.md
+++ b/doc/update/7.3-to-7.4.md
@@ -75,11 +75,11 @@ sudo -u git -H editor config/unicorn.rb
#### Change Nginx HTTPS settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as <https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl> but with your setting.
#### MySQL Databases: Update database.yml config file
-* Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql][mysql]:
+- Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql][mysql]:
```
sudo -u git -H editor config/database.yml
@@ -136,7 +136,7 @@ SET foreign_key_checks = 1;
# Find MySQL users
mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
-# If git user exists and gitlab user does not exist
+# If git user exists and gitlab user does not exist
# you are done with the database cleanup tasks
mysql> \q
diff --git a/doc/update/7.4-to-7.5.md b/doc/update/7.4-to-7.5.md
index 7a3a49ff948..d93d32d1b60 100644
--- a/doc/update/7.4-to-7.5.md
+++ b/doc/update/7.4-to-7.5.md
@@ -77,8 +77,8 @@ git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting.
### 6. Start application
diff --git a/doc/update/7.5-to-7.6.md b/doc/update/7.5-to-7.6.md
index 0d45a9528b9..0e87918c8c0 100644
--- a/doc/update/7.5-to-7.6.md
+++ b/doc/update/7.5-to-7.6.md
@@ -79,8 +79,8 @@ git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting.
#### Set up time zone (optional)
diff --git a/doc/update/7.6-to-7.7.md b/doc/update/7.6-to-7.7.md
index 5e0b2ca7bcd..c24b5647f21 100644
--- a/doc/update/7.6-to-7.7.md
+++ b/doc/update/7.6-to-7.7.md
@@ -79,8 +79,8 @@ git diff origin/7-6-stable:config/gitlab.yml.example origin/7-7-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your setting.
#### Set up time zone (optional)
diff --git a/doc/update/7.7-to-7.8.md b/doc/update/7.7-to-7.8.md
index f5b1ebf0a9c..61bd5fb1298 100644
--- a/doc/update/7.7-to-7.8.md
+++ b/doc/update/7.7-to-7.8.md
@@ -79,9 +79,9 @@ git diff origin/7-7-stable:config/gitlab.yml.example origin/7-8-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
-* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
+- A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Set up time zone (optional)
diff --git a/doc/update/7.8-to-7.9.md b/doc/update/7.8-to-7.9.md
index 0db7698936b..c13dd5b60e6 100644
--- a/doc/update/7.8-to-7.9.md
+++ b/doc/update/7.8-to-7.9.md
@@ -81,9 +81,9 @@ git diff origin/7-8-stable:config/gitlab.yml.example origin/7-9-stable:config/gi
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
-* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
+- A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Set up time zone (optional)
diff --git a/doc/update/7.9-to-7.10.md b/doc/update/7.9-to-7.10.md
index 782fb0736e6..4dece93652e 100644
--- a/doc/update/7.9-to-7.10.md
+++ b/doc/update/7.9-to-7.10.md
@@ -77,9 +77,9 @@ git diff origin/7-9-stable:config/gitlab.yml.example origin/7-10-stable:config/g
#### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
-* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+- HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`][nginx] but with your settings.
+- HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`][nginx-ssl] but with your settings.
+- A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
#### Set up time zone (optional)
diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md
index 12a465e1602..f8415b5159b 100644
--- a/doc/update/8.10-to-8.11.md
+++ b/doc/update/8.10-to-8.11.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md
index b9a7986d5ba..07ac8129b4f 100644
--- a/doc/update/8.11-to-8.12.md
+++ b/doc/update/8.11-to-8.12.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md
index 37e61794e7e..bf622deaba8 100644
--- a/doc/update/8.12-to-8.13.md
+++ b/doc/update/8.12-to-8.13.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md
index 927f453b9bf..43b636ea958 100644
--- a/doc/update/8.13-to-8.14.md
+++ b/doc/update/8.13-to-8.14.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md
index d98a60d31c8..eadf1743597 100644
--- a/doc/update/8.14-to-8.15.md
+++ b/doc/update/8.14-to-8.15.md
@@ -50,7 +50,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.15-to-8.16.md b/doc/update/8.15-to-8.16.md
index 94b0102ed48..4e8d54d5010 100644
--- a/doc/update/8.15-to-8.16.md
+++ b/doc/update/8.15-to-8.16.md
@@ -50,7 +50,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.16-to-8.17.md b/doc/update/8.16-to-8.17.md
index 5a4f620a164..cab28a4d1c6 100644
--- a/doc/update/8.16-to-8.17.md
+++ b/doc/update/8.16-to-8.17.md
@@ -50,7 +50,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md
index 38f7d22437a..55cf0842df4 100644
--- a/doc/update/8.17-to-9.0.md
+++ b/doc/update/8.17-to-9.0.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.0-to-9.1.md b/doc/update/9.0-to-9.1.md
index a4d2e7be23c..10214fd8aca 100644
--- a/doc/update/9.0-to-9.1.md
+++ b/doc/update/9.0-to-9.1.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md
index dd808c51985..79d92f05257 100644
--- a/doc/update/9.1-to-9.2.md
+++ b/doc/update/9.1-to-9.2.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md
index d2bcf45a28e..98443b8bfa6 100644
--- a/doc/update/9.2-to-9.3.md
+++ b/doc/update/9.2-to-9.3.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md
index dae2162a964..640b9c3997e 100644
--- a/doc/update/9.3-to-9.4.md
+++ b/doc/update/9.3-to-9.4.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.4-to-9.5.md b/doc/update/9.4-to-9.5.md
index f2811e9471f..e6cfa70975e 100644
--- a/doc/update/9.4-to-9.5.md
+++ b/doc/update/9.4-to-9.5.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.5-to-10.0.md b/doc/update/9.5-to-10.0.md
index 333a6e35714..8b565f67cb1 100644
--- a/doc/update/9.5-to-10.0.md
+++ b/doc/update/9.5-to-10.0.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/upgrading_postgresql_using_slony.md b/doc/update/upgrading_postgresql_using_slony.md
index f009906256e..d2e2cf439b5 100644
--- a/doc/update/upgrading_postgresql_using_slony.md
+++ b/doc/update/upgrading_postgresql_using_slony.md
@@ -57,7 +57,7 @@ server.
## Installing Slony
Slony will be used to upgrade the database without requiring long downtimes.
-Slony can be downloaded from http://www.slony.info/. If you have installed
+Slony can be downloaded from <http://www.slony.info/>. If you have installed
PostgreSQL using your operating system's package manager you may also be able to
install Slony using said package manager.
@@ -176,10 +176,10 @@ if ($ENV{"SLONYSET"}) {
In this configuration file you should replace a few placeholders before you can
use it. The following placeholders should be replaced:
-* `OLD_HOST`: the address of the old database server.
-* `NEW_HOST`: the address of the new database server.
-* `SLONY_PASSWORD`: the password of the Slony user created earlier.
-* `TABLES`: the tables to replicate.
+- `OLD_HOST`: the address of the old database server.
+- `NEW_HOST`: the address of the new database server.
+- `SLONY_PASSWORD`: the password of the Slony user created earlier.
+- `TABLES`: the tables to replicate.
The list of tables to replicate can be generated by running the following
command on your old PostgreSQL database:
diff --git a/doc/user/admin_area/broadcast_messages.md b/doc/user/admin_area/broadcast_messages.md
new file mode 100644
index 00000000000..51949088521
--- /dev/null
+++ b/doc/user/admin_area/broadcast_messages.md
@@ -0,0 +1,51 @@
+# Broadcast Messages
+
+GitLab can display messages to all users of a GitLab instance in a banner that appears in the UI.
+
+![Broadcast Message](img/broadcast_messages.png)
+
+NOTE: **Note:**
+If more than one banner message is active at one time, they are displayed in a stack in order of creation.
+
+## Adding a broadcast message
+
+To display messages to users on your GitLab instance, add broadcast message.
+
+To add a broadcast message:
+
+1. Navigate to the **Admin Area > Messages** page.
+1. Add the text for the message to the **Message** field. Markdown and emoji are supported.
+1. If required, click the **Customize colors** link to edit the background color and font color of the message.
+1. Select a date for the message to start and end.
+1. Click the **Add broadcast message** button.
+
+NOTE: **Note:**
+Once a broadcast message has expired, it is no longer displayed in the UI but is still listed in the
+list of broadcast messages.
+
+## Editing a broadcast message
+
+If changes are required to a broadcast message, they can be edited.
+
+To edit a broadcast message:
+
+1. Navigate to the **Admin Area > Messages** page.
+1. From the list of broadcast messages, click the appropriate button to edit the message.
+1. After making the required changes, click the **Update broadcast message** button.
+
+TIP: **Tip:**
+Expired messages can be made active again by changing their end date.
+
+## Deleting a broadcast message
+
+Broadcast messages that are no longer required can be deleted.
+
+To delete a broadcast message:
+
+1. Navigate to the **Admin Area > Messages** page.
+1. From the list of broadcast messages, click the appropriate button to delete the message.
+
+Once deleted, the broadcast message is removed from the list of broadcast messages.
+
+NOTE: **Note:**
+Broadcast messages can be deleted while active.
diff --git a/doc/user/admin_area/custom_project_templates.md b/doc/user/admin_area/custom_project_templates.md
index 5afbf9f2934..e34ba045c54 100644
--- a/doc/user/admin_area/custom_project_templates.md
+++ b/doc/user/admin_area/custom_project_templates.md
@@ -2,24 +2,25 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in [GitLab Premium](https://about.gitlab.com/pricing) 11.2.
-When you create a new project, creating it based on custom project templates is
-a convenient option to bootstrap from an existing project boilerplate.
-The administration setting to configure a GitLab group that serves as template
-source can be found under **Admin > Settings > Custom project templates**.
+When you create a new [project](../project/index.md), creating it based on custom project templates is
+a convenient bootstrap option.
+
+GitLab administrators can configure a GitLab group that serves as template
+source for an entire GitLab instance under **Admin area > Settings > Custom project templates**.
+
+NOTE: **Note:**
+To set project templates at a group level,
+see [Custom group-level project templates](../group/custom_project_templates.md).
Within this section, you can configure the group where all the custom project
templates are sourced. Every project directly under the group namespace will be
available to the user if they have access to them. For example, every public
-project in the group will be available to every logged user. However,
-private projects will be available only if the user has view [permissions](../permissions.md)
-in the project:
+project in the group will be available to every logged in user.
-- Project Owner, Maintainer, Developer, Reporter or Guest
-- Is a member of the Group: Owner, Maintainer, Developer, Reporter or Guest
+However, private projects will be available only if the user is a member of the project.
+NOTE: **Note:**
Projects below subgroups of the template group are **not** supported.
Repository and database information that are copied over to each new project are
identical to the data exported with [GitLab's Project Import/Export](../project/settings/import_export.md).
-
-If you would like to set project templates at a group level, please see [Custom group-level project templates](../group/custom_project_templates.md). \ No newline at end of file
diff --git a/doc/user/admin_area/img/broadcast_messages.png b/doc/user/admin_area/img/broadcast_messages.png
new file mode 100644
index 00000000000..926d38ae049
--- /dev/null
+++ b/doc/user/admin_area/img/broadcast_messages.png
Binary files differ
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index bd0155dc712..e165a120162 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -12,9 +12,9 @@ If enabled, version check will inform you if a new version is available and the
importance of it through a status. This is shown on the help page (i.e. `/help`)
for all signed in users, and on the admin pages. The statuses are:
-* Green: You are running the latest version of GitLab.
-* Orange: An updated version of GitLab is available.
-* Red: The version of GitLab you are running is vulnerable. You should install
+- Green: You are running the latest version of GitLab.
+- Orange: An updated version of GitLab is available.
+- Red: The version of GitLab you are running is vulnerable. You should install
the latest version with security fixes as soon as possible.
![Orange version check example](img/update-available.png)
diff --git a/doc/user/award_emojis.md b/doc/user/award_emojis.md
index 93be3da44d4..e4fd08a582c 100644
--- a/doc/user/award_emojis.md
+++ b/doc/user/award_emojis.md
@@ -1,24 +1,24 @@
# Award emoji
-> **Notes:**
-> - First [introduced][1825] in GitLab 8.2.
-> - GitLab 9.0 [introduced][ce-9570] the usage of native emojis if the platform
+> - First [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825) in GitLab 8.2.
+> - GitLab 9.0 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9570) the usage of native emoji if the platform
> supports them and falls back to images or CSS sprites. This change greatly
-> improved the award emoji performance overall.
+> improved award emoji performance overall.
When you're collaborating online, you get fewer opportunities for high-fives
-and thumbs-ups. Emoji can be awarded to issues, merge requests, snippets, and
-virtually everywhere where you can have a discussion.
+and thumbs-ups. Emoji can be awarded to [issues](project/issues/index.md), [merge requests](project/merge_requests/index.md),
+[snippets](snippets.md), and anywhere you can have a discussion.
![Award emoji](img/award_emoji_select.png)
Award emoji make it much easier to give and receive feedback without a long
-comment thread. Comments that are only emoji will automatically become
-award emoji.
+comment thread.
+
+For information on the relevant API, see [Award Emoji API](../api/award_emoji.md).
## Sort issues and merge requests on vote count
-> [Introduced][2871] in GitLab 8.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781) in GitLab 8.5.
You can quickly sort issues and merge requests by the number of votes they
have received. The sort options can be found in the dropdown menu as "Most
@@ -32,20 +32,16 @@ downvotes.
## Award emoji for comments
-> [Introduced][4291] in GitLab 8.9.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4291) in GitLab 8.9.
Award emoji can also be applied to individual comments when you want to
celebrate an accomplishment or agree with an opinion.
-To add an award emoji, click the smile in the top right of the comment and pick
-an emoji from the dropdown. If you want to remove an award emoji, just click
-the emoji again and the vote will be removed.
+To:
+
+- Add an award emoji, click the smile in the top right of the comment and pick an emoji from the dropdown.
+- Remove an award emoji, click the emoji again and the vote will be removed.
![Picking an emoji for a comment](img/award_emoji_comment_picker.png)
![An award emoji has been applied to a comment](img/award_emoji_comment_awarded.png)
-
-[2871]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781
-[1825]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825
-[4291]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4291
-[ce-9570]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9570
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 9379d047fca..84f4b0b3922 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -280,7 +280,7 @@ Additionally locked issues can not be reopened.
For issues with many comments like activity notes and user comments, sometimes
finding useful information can be hard. There is a way to filter comments from single notes and discussions for merge requests and issues.
-From a merge request's **Discussion** tab, or from an issue overview, find the filter's dropdown menu on the right side of the page, from which you can choose one of the following options:
+From a merge request's **Discussion** tab, or from an epic/issue overview, find the filter's dropdown menu on the right side of the page, from which you can choose one of the following options:
- **Show all activity**: displays all user comments and system notes
(issue updates, mentions from other issues, changes to the description, etc).
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index e14e716a5eb..68a0f1a5837 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -281,12 +281,14 @@ of proposed changes can be found at
GitLab.com adjusts the memory limits for the [unicorn-worker-killer][unicorn-worker-killer] gem.
Base default:
-* `memory_limit_min` = 750MiB
-* `memory_limit_max` = 1024MiB
+
+- `memory_limit_min` = 750MiB
+- `memory_limit_max` = 1024MiB
Web front-ends:
-* `memory_limit_min` = 1024MiB
-* `memory_limit_max` = 1280MiB
+
+- `memory_limit_min` = 1024MiB
+- `memory_limit_max` = 1280MiB
## GitLab.com at scale
diff --git a/doc/user/group/custom_project_templates.md b/doc/user/group/custom_project_templates.md
index eaf0273050b..8e101407ac0 100644
--- a/doc/user/group/custom_project_templates.md
+++ b/doc/user/group/custom_project_templates.md
@@ -2,22 +2,25 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6861) in [GitLab Premium](https://about.gitlab.com/pricing) 11.6.
-When you create a new project, creating it based on custom project templates is
-a convenient option to bootstrap from an existing project boilerplate.
-The group-level setting to configure a GitLab group that serves as template
-source can be found under **Group > Settings > General > Custom project templates**.
+When you create a new [project](../project/index.md), creating it based on custom project templates is
+a convenient bootstrap option.
+
+Users can configure a GitLab group that serves as template
+source under a group's **Settings > General > Custom project templates**.
+
+NOTE: **Note:**
+GitLab administrators can
+[set project templates for an entire GitLab instance](../admin_area/custom_project_templates.md).
Within this section, you can configure the group where all the custom project
templates are sourced. Every project directly under the group namespace will be
available to the user if they have access to them. For example, every public
-project in the group will be available to every logged in user. However,
-private projects will be available only if the user has view [permissions](../permissions.md)
-in the project. That is, users with Owner, Maintainer, Developer, Reporter or Guest roles for projects,
-or for groups to which the project belongs.
+project in the group will be available to every logged in user.
+However, private projects will be available only if the user is a member of the project.
+
+NOTE: **Note:**
Projects of nested subgroups of a selected template source cannot be used.
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).
-
-If you would like to set project templates at an instance level, please see [Custom instance-level project templates](../admin_area/custom_project_templates.md). \ No newline at end of file
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 943b0c693c0..b6f8f55978b 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -21,8 +21,8 @@ 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.
See the following links for more information:
-* <https://gitlab.com/gitlab-org/gitlab-ce/issues/30472>
-* <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10885>
+- <https://gitlab.com/gitlab-org/gitlab-ce/issues/30472>
+- <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10885>
## Overview
@@ -164,9 +164,11 @@ and you can choose the group of people to be notified.
Here's a list of what you can't do with subgroups:
-- [GitLab Pages](../../project/pages/index.md) are not currently working for
- projects hosted under a subgroup. That means that only projects hosted under
- the first parent group will work.
+- [GitLab Pages](../../project/pages/index.md) supports projects hosted under
+ a subgroup, but not subgroup websites.
+ That means that only the highest-level group supports
+ [group websites](../../project/pages/introduction.html#user-or-group-pages),
+ although you can have project websites under a subgroup.
- It is not possible to share a project with a group that's an ancestor of
the group the project is in. That means you can only share as you walk down
the hierarchy. For example, `group/subgroup01/project` **cannot** be shared
diff --git a/doc/user/instance_statistics/user_cohorts.md b/doc/user/instance_statistics/user_cohorts.md
index 70d5912dc4e..f52f24ef5f7 100644
--- a/doc/user/instance_statistics/user_cohorts.md
+++ b/doc/user/instance_statistics/user_cohorts.md
@@ -23,5 +23,5 @@ the month, but who have never actually had any activity in the instance.
How do we measure the activity of users? GitLab considers a user active if:
-* the user signs in
-* the user has Git activity (whether push or pull).
+- The user signs in.
+- The user has Git activity (whether push or pull).
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 893658290e5..f2448f240ca 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -300,7 +300,7 @@ You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/
If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px">. All you need to do is to look up one of the supported codes.
-Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px">
+Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px">
Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index ed00f86f9de..0c358390046 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -44,6 +44,7 @@ The following table depicts the various user permission levels in a project.
| View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| View license management reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| View Security reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| View project code | [^1] | ✓ | ✓ | ✓ | ✓ |
| Pull project code | [^1] | ✓ | ✓ | ✓ | ✓ |
| Download project | [^1] | ✓ | ✓ | ✓ | ✓ |
| Assign issues | | ✓ | ✓ | ✓ | ✓ |
@@ -177,9 +178,7 @@ group.
| Remove group | | | | | ✓ |
| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
-| View private group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
-| View internal group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| View public group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ |
| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ |
| Delete group epic **[ULTIMATE]** | | | | | ✓ |
| View group Audit Events | | | | | ✓ |
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 76f7e869ff7..83bc79925e1 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -43,8 +43,8 @@ or a U2F device.
1. Install a compatible application. We recommend [Google Authenticator]
\(proprietary\) or [FreeOTP] \(open source\).
1. In the application, add a new entry in one of two ways:
- * Scan the code with your phone's camera to add the entry automatically.
- * Enter the details provided to add the entry manually.
+ - Scan the code with your phone's camera to add the entry automatically.
+ - Enter the details provided to add the entry manually.
**In GitLab:**
@@ -106,7 +106,7 @@ Enter the pin from your one time password authenticator's application or a recov
### Log in via U2F device
-1. Click **Login via U2F Device**
+1. Click **Login via U2F Device**.
1. A light will start blinking on your device. Activate it by pressing its button.
You will see a message indicating that your device responded to the authentication request.
@@ -135,9 +135,9 @@ authenticate with Git over HTTPS on the command line or when using
To disable two-factor authentication on your account (for example, if you
have lost your code generation device) you can:
-* [Use a saved recovery code](#use-a-saved-recovery-code)
-* [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-ssh)
-* [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account)
+- [Use a saved recovery code](#use-a-saved-recovery-code).
+- [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-ssh).
+- [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account).
### Use a saved recovery code
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 2f989a26725..efb031b8239 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -73,7 +73,7 @@ which also covers the case where you have projects hosted with
## Private profile
-The following information will be hidden from the user profile page (https://gitlab.example.com/username) if this feature is enabled:
+The following information will be hidden from the user profile page (`https://gitlab.example.com/username`) if this feature is enabled:
- Atom feed
- Date when account is created
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 6f334af4fb7..bb815695cb1 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -178,8 +178,11 @@ When creating a cluster in GitLab, you will be asked if you would like to create
[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.
-Whether ABAC or RBAC is enabled, GitLab will create the necessary
-service accounts and privileges in order to install and run
+NOTE: **Note:**
+[RBAC](#role-based-access-control-rbac) is recommended and the GitLab default.
+
+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):
- If GitLab is creating the cluster, a `gitlab` service account with
diff --git a/doc/user/project/clusters/serverless/img/serverless-details.png b/doc/user/project/clusters/serverless/img/serverless-details.png
new file mode 100644
index 00000000000..61e0735199a
--- /dev/null
+++ b/doc/user/project/clusters/serverless/img/serverless-details.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/serverless-page.png b/doc/user/project/clusters/serverless/img/serverless-page.png
index 960d6e736d6..814b8532205 100644
--- a/doc/user/project/clusters/serverless/img/serverless-page.png
+++ b/doc/user/project/clusters/serverless/img/serverless-page.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index a423212a879..bebccf97987 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -32,7 +32,7 @@ To run Knative on Gitlab, you will need:
1. **`.gitlab-ci.yml`:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko)
to build the application and the [TriggerMesh CLI](https://github.com/triggermesh/tm) to simplify the
deployment of knative services and functions.
-1. **`serverless.yaml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yaml` file
+1. **`serverless.yml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yml` file
will contain the information for all the functions being hosted in the repository as well as a reference to the
runtime being used.
1. **`Dockerfile`** (for [applications only](#deploying-serverless-applications): Knative requires a `Dockerfile` in order to build your application. It should be included
@@ -102,11 +102,8 @@ In order to deploy functions to your Knative instance, the following files must
The `gitlab-ci.yml` template creates a `Deploy` stage with a `functions` job that invokes the `tm` CLI with the required parameters.
-2. `serverless.yaml`: This file contains the metadata for your functions,
- such as name, runtime, and environment. It must be included at the root of your repository. The following is a sample `echo` function which shows the required structure for the file.
-
- NOTE: **Note:**
- The file extension for the `serverless.yaml` file must be specified as `.yaml` in order to the file to be parsed properly. Specifying the extension as `.yml` will not work.
+2. `serverless.yml`: This file contains the metadata for your functions,
+ such as name, runtime, and environment. It must be included at the root of your repository. The following is a sample `echo` function which shows the required structure for the file. You can find the relevant files for this project in the [functions example project](https://gitlab.com/knative-examples/functions).
```yaml
service: my-functions
@@ -124,13 +121,13 @@ In order to deploy functions to your Knative instance, the following files must
runtime: https://gitlab.com/triggermesh/runtimes/raw/master/nodejs.yaml
description: "echo function using node.js runtime"
buildargs:
- - DIRECTORY=echo
- environment:
- FUNCTION: echo
+ - DIRECTORY=echo
+ environment:
+ FUNCTION: echo
```
-The `serverless.yaml` file contains three sections with distinct parameters:
+The `serverless.yml` file is referencing both an `echo` directory (under `buildargs`) and an `echo` file (under `handler`) which is a reference to `echo.js` in the [repository](https://gitlab.com/knative-examples/functions). Additionally, it contains three sections with distinct parameters:
### `service`
@@ -144,13 +141,13 @@ The `serverless.yaml` file contains three sections with distinct parameters:
| Parameter | Description |
|-----------|-------------|
-| `name` | Indicates which provider is used to execute the `serverless.yaml` file. In this case, the TriggerMesh `tm` CLI. |
+| `name` | Indicates which provider is used to execute the `serverless.yml` file. In this case, the TriggerMesh `tm` CLI. |
| `registry-secret` | Indicates which registry will be used to store docker images. The sample function is using the GitLab Registry (`gitlab-registry`). A different registry host may be specified using `registry` key in the `provider` object. If changing the default, update the permission and the secret value on the `gitlab-ci.yml` file |
| `environment` | Includes the environment variables to be passed as part of function execution for **all** functions in the file, where `FOO` is the variable name and `BAR` are he variable contents. You may replace this with you own variables. |
### `functions`
-In the `serverless.yaml` example above, the function name is `echo` and the subsequent lines contain the function attributes.
+In the `serverless.yml` example above, the function name is `echo` and the subsequent lines contain the function attributes.
| Parameter | Description |
@@ -161,7 +158,7 @@ In the `serverless.yaml` example above, the function name is `echo` and the subs
| `buildargs` | Pointer to the function file in the repo. In the sample the function is located in the `echo` directory. |
| `environment` | Sets an environment variable for the specific function only. |
-After the `gitlab-ci.yml` template has been added and the `serverless.yaml` file has been
+After the `gitlab-ci.yml` template has been added and the `serverless.yml` file has been
created, each function must be defined as a single file in your repository. Committing a
function to your project will result in a
CI pipeline being executed which will deploy each function as a Knative service.
@@ -170,8 +167,8 @@ appear under **Operations > Serverless**.
![serverless page](img/serverless-page.png)
-This page contains all functions available for the project, the URL for
-accessing the function, and if available, the function's runtime information.
+This page contains all functions available for the project, the description for
+accessing the function, and, if available, the function's runtime information.
The details are derived from the Knative installation inside each of the project's
Kubernetes cluster.
@@ -187,6 +184,12 @@ The sample function can now be triggered from any HTTP client using a simple `PO
Currently, the Serverless page presents all functions available in all clusters registered for the project with Knative installed.
+Clicking on the function name will provide additional details such as the
+function's URL as well as runtime statistics such as the number of active pods
+available to service the request based on load.
+
+![serverless function details](img/serverless-details.png)
+
## Deploying Serverless applications
> Introduced in GitLab 11.5.
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index cac64fc0cb6..638b73bfcb6 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -15,7 +15,7 @@
With the Docker Container Registry integrated into GitLab, every project can
have its own space to store its Docker images.
-You can read more about Docker Registry at https://docs.docker.com/registry/introduction/.
+You can read more about Docker Registry at <https://docs.docker.com/registry/introduction/>.
## Enable the Container Registry for your project
@@ -283,9 +283,9 @@ In the example above, we see the following trace on the mitmproxy window:
The above image shows:
-* The initial PUT requests went through fine with a 201 status code.
-* The 201 redirected the client to the S3 bucket.
-* The HEAD request to the AWS bucket reported a 403 Unauthorized.
+- The initial PUT requests went through fine with a 201 status code.
+- The 201 redirected the client to the S3 bucket.
+- The HEAD request to the AWS bucket reported a 403 Unauthorized.
What does this mean? This strongly suggests that the S3 user does not have the right
[permissions to perform a HEAD request](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html).
diff --git a/doc/user/project/img/issue_boards_multiple.png b/doc/user/project/img/issue_boards_multiple.png
index 7bb088aad0b..242460925f7 100644
--- a/doc/user/project/img/issue_boards_multiple.png
+++ b/doc/user/project/img/issue_boards_multiple.png
Binary files differ
diff --git a/doc/user/project/img/releases.png b/doc/user/project/img/releases.png
deleted file mode 100644
index aec1db89a75..00000000000
--- a/doc/user/project/img/releases.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index 42da2210fab..cf99dded5e2 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -18,16 +18,16 @@ GitHub without the constraints of a Sidekiq worker.
The following aspects of a project are imported:
- * Repository description (GitLab.com & 7.7+)
- * Git repository data (GitLab.com & 7.7+)
- * Issues (GitLab.com & 7.7+)
- * Pull requests (GitLab.com & 8.4+)
- * Wiki pages (GitLab.com & 8.4+)
- * Milestones (GitLab.com & 8.7+)
- * Labels (GitLab.com & 8.7+)
- * Release note descriptions (GitLab.com & 8.12+)
- * Pull request review comments (GitLab.com & 10.2+)
- * Regular issue and pull request comments
+- Repository description (GitLab.com & 7.7+)
+- Git repository data (GitLab.com & 7.7+)
+- Issues (GitLab.com & 7.7+)
+- Pull requests (GitLab.com & 8.4+)
+- Wiki pages (GitLab.com & 8.4+)
+- Milestones (GitLab.com & 8.7+)
+- Labels (GitLab.com & 8.7+)
+- Release note descriptions (GitLab.com & 8.12+)
+- Pull request review comments (GitLab.com & 10.2+)
+- Regular issue and pull request comments
References to pull requests and issues are preserved (GitLab.com & 8.7+), and
each imported repository maintains visibility level unless that [visibility
@@ -92,7 +92,7 @@ integration enabled, that should be the preferred method to import your reposito
If you are not using the GitHub integration, you can still perform an authorization with GitHub to grant GitLab access your repositories:
-1. Go to https://github.com/settings/tokens/new
+1. Go to <https://github.com/settings/tokens/new>
1. Enter a token description.
1. Select the repo scope.
1. Click **Generate token**.
@@ -132,8 +132,8 @@ Admin access to the GitLab server is required.
For large projects it may take a while to import all data. To reduce the time necessary, you can increase the number of
Sidekiq workers that process the following queues:
-* `github_importer`
-* `github_importer_advance_stage`
+- `github_importer`
+- `github_importer_advance_stage`
For an optimal experience, it's recommended having at least 4 Sidekiq processes (each running a number of threads equal
to the number of CPU cores) that *only* process these queues. It's also recommended that these processes run on separate
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index 2f5efbe84d9..ca02e4e9e96 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -25,3 +25,10 @@ but issues and merge requests can't be imported.
If you want to retain all metadata like issues and merge requests, you can use
the [import/export feature](../settings/import_export.md).
+
+## Migrating between two self-hosted GitLab instances
+
+The best method for migrating a project from one GitLab instance to another,
+perhaps from an old server to a new server for example, is to
+[back up the project](../../../raketasks/backup_restore.md),
+then restore it on the new server.
diff --git a/doc/user/project/import/repo_by_url.md b/doc/user/project/import/repo_by_url.md
index f43e384de88..c20b1cb7f5e 100644
--- a/doc/user/project/import/repo_by_url.md
+++ b/doc/user/project/import/repo_by_url.md
@@ -6,7 +6,7 @@ You can import your existing repositories by providing the Git URL:
1. Switch to the **Import project** tab
1. Click on the **Repo by URL** button
1. Fill in the "Git repository URL" and the remaining project fields
-1. Click **Create project** to being the import process
+1. Click **Create project** to begin the import process
1. Once complete, you will be redirected to your newly created project
![Import project by repo URL](img/import_projects_from_repo_url.png)
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index e63ed88249d..6a1aadf058e 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -65,19 +65,22 @@ common actions on issues or merge requests
browse, and download job artifacts
- [Pipeline settings](pipelines/settings.md): Set up Git strategy (choose the default way your repository is fetched from GitLab in a job),
timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- - [GKE cluster integration](clusters/index.md): Connecting your GitLab project
- with Google Kubernetes Engine
+ - [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project
+ with a Kubernetes cluster
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
website with GitLab Pages
**Other features:**
-- [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
-- [Syntax highlighting](highlighting.md): An alternative to customize
-your code blocks, overriding GitLab's default choice of language
-- [Badges](badges.md): Badges for the project overview
+- [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.
+- [Syntax highlighting](highlighting.md): an alternative to customize
+your code blocks, overriding GitLab's default choice of language.
+- [Badges](badges.md): badges for the project overview.
+- [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.
### Project's integrations
@@ -146,3 +149,24 @@ When [renaming a user](../profile/index.md#changing-your-username),
work after a rename, making any transition a lot smoother.
- The redirects will be available as long as the original path is not claimed by
another group, user or project.
+
+## Use your project as a Go package
+
+Any project can be used as a Go package including private projects in subgroups. To use packages
+hosted in private projects with the `go get` command, use a [`.netrc` file](https://ec.haxx.se/usingcurl-netrc.html)
+and a [personal access token](../profile/personal_access_tokens.md) in the password field.
+
+For example:
+
+```text
+machine example.gitlab.com
+login <gitlab_user_name>
+password <personal_access_token>
+```
+
+## Access project page with project ID
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53671) in GitLab 11.8.
+
+To quickly access a project from the GitLab UI using the project ID,
+visit the `/projects/:id` URL in your browser or other tool accessing the project.
diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md
index 70c0d434f1f..48aabd02438 100644
--- a/doc/user/project/integrations/bamboo.md
+++ b/doc/user/project/integrations/bamboo.md
@@ -40,7 +40,7 @@ service in GitLab.
1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services)
1. Click 'Atlassian Bamboo CI'
1. Select the 'Active' checkbox.
-1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com'
+1. Enter the base URL of your Bamboo server. `https://bamboo.example.com`
1. Enter the build key from your Bamboo build plan. Build keys are typically made
up from the Project Key and Plan Key that are set on project/plan creation and
separated with a dash (`-`), for example **PROJ-PLAN**. This is a short, all
diff --git a/doc/user/project/integrations/emails_on_push.md b/doc/user/project/integrations/emails_on_push.md
index df320de7e16..61359dcaa90 100644
--- a/doc/user/project/integrations/emails_on_push.md
+++ b/doc/user/project/integrations/emails_on_push.md
@@ -10,10 +10,10 @@ In the _Recipients_ area, provide a list of emails separated by spaces or newlin
The following options are available:
-+ **Push events** - Email will be triggered when a push event is received
-+ **Tag push events** - Email will be triggered when a tag is created and pushed
-+ **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`).
-+ **Disable code diffs** - Don't include possibly sensitive code diffs in notification body.
+- **Push events** - Email will be triggered when a push event is received.
+- **Tag push events** - Email will be triggered when a tag is created and pushed.
+- **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`).
+- **Disable code diffs** - Don't include possibly sensitive code diffs in notification body.
| Settings | Notification |
| --- | --- |
diff --git a/doc/user/project/integrations/hipchat.md b/doc/user/project/integrations/hipchat.md
index eee779c50d4..0fd847d415f 100644
--- a/doc/user/project/integrations/hipchat.md
+++ b/doc/user/project/integrations/hipchat.md
@@ -18,7 +18,7 @@ allow GitLab to send messages only to *one* room.
### Complete these steps in HipChat
-1. Go to: https://admin.hipchat.com/admin
+1. Go to: <https://admin.hipchat.com/admin>
1. Click on "Group Admin" -> "Integrations".
1. Find "Build Your Own!" and click "Create".
1. Select the desired room, name the integration "GitLab", and click "Create".
diff --git a/doc/user/project/integrations/irker.md b/doc/user/project/integrations/irker.md
index ecdd83ce8f0..f220fa8497a 100644
--- a/doc/user/project/integrations/irker.md
+++ b/doc/user/project/integrations/irker.md
@@ -4,12 +4,12 @@ GitLab provides a way to push update messages to an Irker server. When
configured, pushes to a project will trigger the service to send data directly
to the Irker server.
-See the project homepage for further info: https://gitlab.com/esr/irker
+See the project homepage for further info: <https://gitlab.com/esr/irker>
## Needed setup
You will first need an Irker daemon. You can download the Irker code from its
-repository on https://gitlab.com/esr/irker:
+repository on <https://gitlab.com/esr/irker>:
```
git clone https://gitlab.com/esr/irker.git
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index bc4bba40e59..754711f5919 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -31,7 +31,7 @@ directly from GitLab, as covered in the article
Each GitLab project can be configured to connect to an entire Jira instance. That
means one GitLab project can interact with _all_ Jira projects in that instance, once
-configured. Therefore, you will not have to explicitly associate
+configured. Therefore, you will not have to explicitly associate
a GitLab project with any single Jira project.
If you have one Jira instance, you can pre-fill the settings page with a default
@@ -46,10 +46,12 @@ project in Jira and then enter the correct values in GitLab.
### Configuring Jira
When connecting to **JIRA Server**, which supports basic authentication, a **username and password** are required. Check the link below and proceed to the next step:
-* [Setting up an user in JIRA server](jira_server_configuration.md)
+
+- [Setting up an 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 an user in JIRA cloud](jira_cloud_configuration.md)
### Configuring GitLab
@@ -114,11 +116,11 @@ USER mentioned this issue in RESOURCE_NAME of [PROJECT_NAME|LINK_TO_COMMENT]:
ENTITY_TITLE
```
-* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
-* `LINK_TO_THE_COMMENT` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
-* `RESOURCE_NAME` Kind of resource which referenced the issue. Can be a commit or merge request.
-* `PROJECT_NAME` GitLab project name.
-* `ENTITY_TITLE` Merge request title or commit message first line.
+- `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
+- `LINK_TO_THE_COMMENT` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
+- `RESOURCE_NAME` Kind of resource which referenced the issue. Can be a commit or merge request.
+- `PROJECT_NAME` GitLab project name.
+- `ENTITY_TITLE` Merge request title or commit message first line.
![example of mentioning or closing the Jira issue](img/jira_issue_reference.png)
@@ -190,11 +192,11 @@ Make sure that the Jira issue is not already marked as resolved; that is,
the Jira issue resolution field is not set. (It should not be struck through in
Jira lists.)
-### CAPTCHA
+### CAPTCHA
-CAPTCHA may be triggered after several consecutive failed login attempts
+CAPTCHA may be triggered after several consecutive failed login attempts
which may lead to a `401 unauthorized` error when testing your Jira integration.
-If CAPTCHA has been triggered, you will not be able to use Jira's REST API to
+If CAPTCHA has been triggered, you will not be able to use Jira's REST API to
authenticate with the Jira site. You will need to log in to your Jira instance
and complete the CAPTCHA.
diff --git a/doc/user/project/integrations/jira_cloud_configuration.md b/doc/user/project/integrations/jira_cloud_configuration.md
index cae66526175..849df707521 100644
--- a/doc/user/project/integrations/jira_cloud_configuration.md
+++ b/doc/user/project/integrations/jira_cloud_configuration.md
@@ -3,7 +3,7 @@
An API token is needed when integrating with JIRA Cloud, follow the steps
below to create one:
-1. Log in to https://id.atlassian.com with your email.
+1. Log in to <https://id.atlassian.com> with your email.
1. **Click API tokens**, then **Create API token**.
![JIRA API token](img/jira_api_token_menu.png)
diff --git a/doc/user/project/integrations/mattermost.md b/doc/user/project/integrations/mattermost.md
index ed4367c1135..8c5461de42f 100644
--- a/doc/user/project/integrations/mattermost.md
+++ b/doc/user/project/integrations/mattermost.md
@@ -10,7 +10,7 @@ To enable Mattermost integration you must create an incoming webhook integration
1. Save it, copy the **Webhook URL**, we'll need this later for GitLab.
There might be some cases that Incoming Webhooks are blocked by admin, ask your mattermost admin to enable
-it on https://mattermost.example/admin_console/integrations/custom.
+it on `https://mattermost.example/admin_console/integrations/custom`.
Display name override is not enabled by default, you need to ask your admin to enable it on that same section.
@@ -38,7 +38,7 @@ At the end, fill in your Mattermost details:
| Field | Description |
| ----- | ----------- |
-| **Webhook** | The incoming webhook URL which you have to set up on Mattermost, it will be something like: http://mattermost.example/hooks/5xo… |
+| **Webhook** | The incoming webhook URL which you have to set up on Mattermost, it will be something like: `http://mattermost.example/hooks/5xo…` |
| **Username** | Optional username which can be on messages sent to Mattermost. Fill this in if you want to change the username of the bot. |
| **Notify only broken pipelines** | If you choose to enable the **Pipeline** event and you want to be only notified about failed pipelines. |
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 5de8e66e7eb..d6ee678443f 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -9,8 +9,9 @@ within the GitLab interface.
![Environment Dashboard](img/prometheus_dashboard.png)
There are two ways to set up Prometheus integration, depending on where your apps are running:
-* For deployments on Kubernetes, GitLab can automatically [deploy and manage Prometheus](#managed-prometheus-on-kubernetes)
-* For other deployment targets, simply [specify the Prometheus server](#manual-configuration-of-prometheus).
+
+- For deployments on Kubernetes, GitLab can automatically [deploy and manage Prometheus](#managed-prometheus-on-kubernetes).
+- For other deployment targets, simply [specify the Prometheus server](#manual-configuration-of-prometheus).
Once enabled, GitLab will automatically detect metrics from known services in the [metric library](#monitoring-ci-cd-environments).
@@ -23,8 +24,8 @@ GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cl
#### Requirements
-* A [connected Kubernetes cluster](../clusters/index.md)
-* Helm Tiller [installed by GitLab](../clusters/index.md#installing-applications)
+- A [connected Kubernetes cluster](../clusters/index.md)
+- Helm Tiller [installed by GitLab](../clusters/index.md#installing-applications)
#### Getting started
@@ -42,9 +43,9 @@ Prometheus is deployed into the `gitlab-managed-apps` namespace, using the [offi
The Prometheus server will [automatically detect and monitor](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Ckubernetes_sd_config%3E) nodes, pods, and endpoints. To configure a resource to be monitored by Prometheus, simply set the following [Kubernetes annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/):
-* `prometheus.io/scrape` to `true` to enable monitoring of the resource.
-* `prometheus.io/port` to define the port of the metrics endpoint.
-* `prometheus.io/path` to define the path of the metrics endpoint. Defaults to `/metrics`.
+- `prometheus.io/scrape` to `true` to enable monitoring of the resource.
+- `prometheus.io/port` to define the port of the metrics endpoint.
+- `prometheus.io/path` to define the path of the metrics endpoint. Defaults to `/metrics`.
CPU and Memory consumption is monitored, but requires [naming conventions](prometheus_library/kubernetes.html#specifying-the-environment) in order to determine the environment. If you are using [Auto DevOps](../../../topics/autodevops/), this is handled automatically.
diff --git a/doc/user/project/integrations/prometheus_library/index.md b/doc/user/project/integrations/prometheus_library/index.md
index 9b9b4f6c8ca..a79bc2bce06 100644
--- a/doc/user/project/integrations/prometheus_library/index.md
+++ b/doc/user/project/integrations/prometheus_library/index.md
@@ -10,7 +10,8 @@ Currently supported exporters are:
- [Kubernetes](kubernetes.md)
- [NGINX](nginx.md)
-- [NGINX Ingress Controller](nginx_ingress.md)
+- [NGINX Ingress Controller 0.9.0-0.15.x](nginx_ingress_vts.md)
+- [NGINX Ingress Controller 0.16.0+](nginx_ingress.md)
- [HAProxy](haproxy.md)
- [Amazon Cloud Watch](cloudwatch.md)
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index 40ac855c74f..b7601f26802 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -1,8 +1,10 @@
# Monitoring NGINX Ingress Controller
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22133) in GitLab 11.7.
-GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built in Prometheus metrics included in [version 0.9.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#09-beta1) and above of the ingress.
+NOTE: **Note:** NGINX Ingress versions prior to 0.16.0 offer an included [VTS Prometheus metrics exporter](nginx_ingress_vts.md), which exports metrics different than the built-in metrics.
+
+GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built-in Prometheus metrics included starting with [version 0.16.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0160).
## Requirements
@@ -12,27 +14,29 @@ GitLab has support for automatically detecting and monitoring the Kubernetes NGI
| Name | Query |
| ---- | ----- |
-| Throughput (req/sec) | sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code) |
-| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
-| HTTP Error Rate (%) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100 |
+| Throughput (req/sec) | sum(label_replace(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m]), "status_code", "${1}xx", "status", "(.)..")) by (status_code) |
+| Latency (ms) | sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_sum{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_count{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 1000 |
+| HTTP Error Rate (%) | sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 100 |
## Configuring NGINX ingress monitoring
If you have deployed NGINX Ingress using GitLab's [Kubernetes cluster integration](../../clusters/index.md#installing-applications), it will [automatically be monitored](#about-managed-nginx-ingress-deployments) by Prometheus.
For other deployments, there is [some configuration](#manually-setting-up-nginx-ingress-for-prometheus-monitoring) required depending on your installation:
-* NGINX Ingress should be version 0.9.0 or above, with metrics enabled
-* NGINX Ingress should be annotated for Prometheus monitoring
-* Prometheus should be configured to monitor annotated pods
+
+- NGINX Ingress should be version 0.16.0 or above, with metrics enabled.
+- NGINX Ingress should be annotated for Prometheus monitoring.
+- Prometheus should be configured to monitor annotated pods.
### About managed NGINX Ingress deployments
NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](../../clusters/index.md#getting-the-external-ip-address).
NGINX is configured for Prometheus monitoring, by setting:
-* `enable-vts-status: "true"`, to export Prometheus metrics
-* `prometheus.io/scrape: "true"`, to enable automatic discovery
-* `prometheus.io/port: "10254"`, to specify the metrics port
+
+- `enable-vts-status: "true"`, to export Prometheus metrics.
+- `prometheus.io/scrape: "true"`, to enable automatic discovery.
+- `prometheus.io/port: "10254"`, to specify the metrics port.
When used in conjunction with the GitLab deployed Prometheus service, response metrics will be automatically collected.
@@ -42,13 +46,13 @@ Version 0.9.0 and above of [NGINX ingress](https://github.com/kubernetes/ingress
Next, the ingress needs to be annotated for Prometheus monitoring. Two new annotations need to be added:
-* `prometheus.io/scrape: "true"`
-* `prometheus.io/port: "10254"`
+- `prometheus.io/scrape: "true"`
+- `prometheus.io/port: "10254"`
Managing these settings depends on how NGINX ingress has been deployed. If you have deployed via the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress), metrics can be enabled with `controller.stats.enabled` along with the required annotations. Alternatively it is possible edit the NGINX ingress YML directly in the [Kubernetes dashboard](https://github.com/kubernetes/dashboard).
## Specifying the Environment label
-In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`.
+In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `ingress` label must `<CI_ENVIRONMENT_SLUG>`.
If you have used [Auto Deploy](../../../../topics/autodevops/index.md#auto-deploy) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
new file mode 100644
index 00000000000..081eb8732ad
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md
@@ -0,0 +1,58 @@
+# Monitoring NGINX Ingress Controller with VTS metrics
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5.
+
+NOTE: **Note:** [NGINX Ingress version 0.16](nginx_ingress.md) and above have built-in Prometheus metrics, which are different than the VTS based metrics.
+
+GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the included VTS Prometheus metrics exporter in [version 0.9.0](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#09-beta1) through [0.15.x](https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md#0150).
+
+## Requirements
+
+[Prometheus integration](../prometheus.md) must be active.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Throughput (req/sec) | sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code) |
+| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
+| HTTP Error Rate (%) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100 |
+
+## Configuring NGINX ingress monitoring
+
+If you have deployed NGINX Ingress using GitLab's [Kubernetes cluster integration](../../clusters/index.md#installing-applications), it will [automatically be monitored](#about-managed-nginx-ingress-deployments) by Prometheus.
+
+For other deployments, there is [some configuration](#manually-setting-up-nginx-ingress-for-prometheus-monitoring) required depending on your installation:
+
+- NGINX Ingress should be version 0.9.0 or above, with metrics enabled.
+- NGINX Ingress should be annotated for Prometheus monitoring.
+- Prometheus should be configured to monitor annotated pods.
+
+### About managed NGINX Ingress deployments
+
+NGINX Ingress is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress). NGINX Ingress will be [externally reachable via the Load Balancer's IP](../../clusters/index.md#getting-the-external-ip-address).
+
+NGINX is configured for Prometheus monitoring, by setting:
+
+- `enable-vts-status: "true"`, to export Prometheus metrics.
+- `prometheus.io/scrape: "true"`, to enable automatic discovery.
+- `prometheus.io/port: "10254"`, to specify the metrics port.
+
+When used in conjunction with the GitLab deployed Prometheus service, response metrics will be automatically collected.
+
+### Manually setting up NGINX Ingress for Prometheus monitoring
+
+Version 0.9.0 and above of [NGINX ingress](https://github.com/kubernetes/ingress-nginx) have built-in support for exporting Prometheus metrics. To enable, a ConfigMap setting must be passed: `enable-vts-status: "true"`. Once enabled, a Prometheus metrics endpoint will start running on port 10254.
+
+Next, the ingress needs to be annotated for Prometheus monitoring. Two new annotations need to be added:
+
+- `prometheus.io/scrape: "true"`
+- `prometheus.io/port: "10254"`
+
+Managing these settings depends on how NGINX ingress has been deployed. If you have deployed via the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress), metrics can be enabled with `controller.stats.enabled` along with the required annotations. Alternatively it is possible edit the NGINX ingress YML directly in the [Kubernetes dashboard](https://github.com/kubernetes/dashboard).
+
+## Specifying the Environment label
+
+In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do this, GitLab will search for metrics with appropriate labels. In this case, the `upstream` label must be of the form `<KUBE_NAMESPACE>-<CI_ENVIRONMENT_SLUG>-*`.
+
+If you have used [Auto Deploy](../../../../topics/autodevops/index.md#auto-deploy) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/doc/user/project/integrations/slack.md b/doc/user/project/integrations/slack.md
index af4ca35a215..bb8d276c2fc 100644
--- a/doc/user/project/integrations/slack.md
+++ b/doc/user/project/integrations/slack.md
@@ -23,4 +23,54 @@ The Slack Notifications Service allows your GitLab project to send events (e.g.
Your Slack team will now start receiving GitLab event notifications as configured.
-![Slack configuration](img/slack_configuration.png) \ No newline at end of file
+![Slack configuration](img/slack_configuration.png)
+
+## Troubleshooting
+
+If you're having trouble with the Slack integration not working, then start by
+searching through the [Sidekiq logs](../../../administration/logs.md#sidekiqlog)
+for errors relating to your Slack service.
+
+### Something went wrong on our end
+
+This is a generic error shown in the GitLab UI and doesn't mean much by itself.
+You'll need to look in [the logs](../../../administration/logs.md#productionlog) to find
+an error message and keep troubleshooting from there.
+
+### `certificate verify failed`
+
+You may see an entry similar to the following in your Sidekiq log:
+
+```text
+2019-01-10_13:22:08.42572 2019-01-10T13:22:08.425Z 6877 TID-abcdefg ProjectServiceWorker JID-3bade5fb3dd47a85db6d78c5 ERROR: {:class=>"ProjectServiceWorker", :service_class=>"SlackService", :message=>"SSL_connect returned=1 errno=0 state=error: certificate verify failed"}
+```
+
+This is probably a problem either with GitLab communicating with Slack, or GitLab
+communicating with itself. The former is less likely since Slack's security certificates
+should _hopefully_ always be trusted. We can establish which we're dealing with by using
+the below rails console script.
+
+```sh
+# start a rails console:
+sudo gitlab-rails console production
+
+# or for source installs:
+bundle exec rails console production
+```
+
+```ruby
+# run this in the Rails console
+# replace <SLACK URL> with your actual Slack URL
+result = Net::HTTP.get(URI('https://<SLACK URL>'));0
+
+# replace <GITLAB URL> with your actual GitLab URL
+result = Net::HTTP.get(URI('https://<GITLAB URL>'));0
+```
+
+If it's an issue with GitLab not trusting HTTPS connections to itself, then you may simply
+need to [add your certificate to GitLab's trusted certificates](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+
+If it's an issue with GitLab not trusting connections to Slack, then the GitLab
+OpenSSL trust store probably got messed up somehow. Typically this is from overriding
+the trust store with `gitlab_rails['env'] = {"SSL_CERT_FILE" => "/path/to/file.pem"}`
+or by accidentally modifying the default CA bundle `/opt/gitlab/embedded/ssl/certs/cacert.pem`.
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 9e2434c02ec..7962eeada5c 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -175,6 +175,7 @@ products.
Clicking on the current board name in the upper left corner will reveal a
menu from where you can create another Issue Board and rename or delete the
existing one.
+Using the search box at the top of the menu, you can filter the listed boards.
When you're revisiting an issue board in a project or group with multiple boards,
GitLab will automatically load the last board you visited.
diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md
index c33d1365001..2bf4fa287e9 100644
--- a/doc/user/project/issues/create_new_issue.md
+++ b/doc/user/project/issues/create_new_issue.md
@@ -39,34 +39,40 @@ It opens a new issue for that project labeled after its respective list.
## New issue via email
-*This feature needs [incoming email](../../../administration/incoming_email.md)
-to be configured by a GitLab administrator to be available for CE/EE users, and
-it's available on GitLab.com.*
+At the bottom of a project's Issues List page, a link to **Email a new issue to this project**
+is displayed if your GitLab instance has [incoming email](../../../administration/incoming_email.md) configured.
-At the bottom of a project's issue page, click
-**Email a new issue to this project**, and you will find an email address
-which belongs to you. You could add this address to your contact.
+![Bottom of a project issues page](img/new_issue_from_email.png)
+
+When you click this link, an email address is displayed which belongs to you for creating issues in this project.
+You can save this address as a contact in your email client for easy acceess.
-This is a private email address, generated just for you.
-**Keep it to yourself** as anyone who gets ahold of it can create issues or
-merge requests as if they were you. You can add this address to your contact
-list for easy access.
+CAUTION: **Caution:**
+This is a private email address, generated just for you. **Keep it to yourself**,
+as anyone who gets ahold of it can create issues or merge requests as if they
+were you. If the address is compromised, or you'd like it to be regenerated for
+any reason, click **Email a new issue to this project** again and click the reset link.
Sending an email to this address will create a new issue on your behalf for
-this project, where the email subject becomes the issue title, and the email
-body becomes the issue description. [Markdown] and [quick actions] are
-supported.
+this project, where:
-![Bottom of a project issues page](img/new_issue_from_email.png)
+- The email subject becomes the issue title.
+- The email body becomes the issue description.
+- [Markdown](../../markdown.md) and [quick actions](../quick_actions.md) are supported.
+
+NOTE: **Note:**
+In GitLab 11.7, we updated the format of the generated email address.
+However the older format is still supported, allowing existing aliases
+or contacts to continue working._
## New issue via URL with prefilled fields
You can link directly to the new issue page for a given project, with prefilled
-field values using query string parameters in a URL. This is useful for embedding
-a URL in an external HTML page, and also certain scenarios where you want the user to
+field values using query string parameters in a URL. This is useful for embedding
+a URL in an external HTML page, and also certain scenarios where you want the user to
create an issue with certain fields prefilled.
-The title, description, and description template fields can be prefilled using
+The title, description, and description template fields can be prefilled using
this method. The description and description template fields cannot be pre-entered
in the same URL (since a description template just populates the description field).
diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md
new file mode 100644
index 00000000000..032e3a73ad0
--- /dev/null
+++ b/doc/user/project/issues/csv_import.md
@@ -0,0 +1,63 @@
+# Importing Issues from CSV
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23532) in GitLab 11.7.
+
+Issues can be imported to a project by uploading a CSV file. Supported fields are
+`title` and `description`.
+
+The user uploading the CSV file will be set as the author of the imported issues.
+
+> **Note:** A permission level of `Developer` or higher is required to import issues.
+
+To import issues:
+
+1. Ensure your CSV file meets the [file format](#csv-file-format) requirements.
+1. Navigate to a project's Issues list page.
+1. If existing issues are present, click the import icon at the top right, next to the **Edit issues** button.
+1. For a project without any issues, click the button labeled **Import CSV** in the middle of the page.
+1. Select the file and click the **Import issues** button.
+
+The file is processed in the background and a notification email is sent
+to you once the import is completed.
+
+## CSV File Format
+
+### Header row
+
+CSV files must contain a header row beginning with at least two columns, `title` and `description`, in that order.
+If additional columns are present, they will be ignored.
+
+### Column separator
+
+The column separator is automatically detected from the header row.
+
+Supported separator characters are: commas (`,`), semicolons (`;`), and tabs (`\t`).
+
+### Row separator
+
+Lines ending in either `CRLF` or `LF` are supported.
+
+### Quote character
+
+The double-quote (`"`) character is used to quote fields so you can use the column separator within a field. To insert
+a double-quote (`"`) within a quoted field, use two double-quote characters in succession, i.e. `""`.
+
+### Data rows
+
+After the header row, succeeding rows must follow the same column order. The issue title is required while the
+description is optional.
+
+### File size
+
+The limit depends on the configuration value of Max Attachment Size for the GitLab instance.
+
+For GitLab.com, it is set to 10 MB.
+
+## Sample Data
+
+```csv
+title,description
+My Issue Title,My Issue Description
+Another Title,"A description, with a comma"
+"One More Title","One More Description"
+```
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 200b3a642a1..5a3ac9c175b 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -142,20 +142,29 @@ to find out more about this feature.
With [GitLab Starter](https://about.gitlab.com/pricing/), you can also
create various boards per project with [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards).
+### Import Issues from CSV
+
+You can import a CSV file containing issue titles and descriptions to create
+a batch of issues simultaneously.
+
+When you navigate to the Issues list page, an import button is displayed.
+
+For further details, see [Importing issues from CSV](csv_import.md)
+
### External Issue Tracker
Alternatively to GitLab's built-in Issue Tracker, you can also use an [external
tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine,
or Bugzilla.
-### Issue's API
+### Issue API
-Read through the [API documentation](../../../api/issues.md).
+See the [API documentation](../../../api/issues.md).
### Bulk editing issues
-Find out about [bulk editing issues](../../project/bulk_editing.md).
+See the [bulk editing issues](../../project/bulk_editing.md) page.
### Similar issues
-Find out about [similar issues](similar_issues.md).
+See the [similar issues](similar_issues.md) page.
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index 2c2e8e2d556..2a490e3fd7f 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -77,7 +77,7 @@ GitLab users to the project.
Once done, hit **Add users to project** and watch that there is a new member
with the e-mail address we used above. From there on, you can resend the
-invitation, change their access level or even delete them.
+invitation, change their access level, or even delete them.
![Invite user members list](img/add_user_email_accept.png)
diff --git a/doc/user/project/merge_requests/allow_collaboration.md b/doc/user/project/merge_requests/allow_collaboration.md
index 859ac92ef89..da6e6b5fd3a 100644
--- a/doc/user/project/merge_requests/allow_collaboration.md
+++ b/doc/user/project/merge_requests/allow_collaboration.md
@@ -1,20 +1,72 @@
# Allow collaboration on merge requests across forks
-> [Introduced][ce-17395] in GitLab 10.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395)
+ in GitLab 10.6.
+
+When a user opens a merge request from a fork, they are given the option to allow
+upstream members to collaborate with them on the source branch. This allows
+the members of the upstream project to make small fixes or rebase branches
+before merging, reducing the back and forth of accepting external contributions.
This feature is available for merge requests across forked projects that are
-publicly accessible. It makes it easier for members of projects to
-collaborate on merge requests across forks.
+publicly accessible.
When enabled for a merge request, members with merge access to the target
branch of the project will be granted write permissions to the source branch
of the merge request.
+## Enabling commit edits from upstream members
+
The feature can only be enabled by users who already have push access to the
-source project, and only lasts while the merge request is open.
+source project and only lasts while the merge request is open. Once enabled,
+upstream members will also be able to retry the pipelines and jobs of the
+merge request:
+
+1. Enable the contribution while creating or editing a merge request.
+
+ ![Enable contribution](img/allow_collaboration.png)
+
+1. Once the merge request is created, you'll see that commits from members who
+ can merge to the target branch are allowed.
+
+ ![Check that contribution is enabled](img/allow_collaboration_after_save.png)
+
+## Pushing to the fork as the upstream member
+
+If the creator of the merge request has enabled contributions from upstream
+members, you can push directly to the branch of the forked repository.
+
+Assuming that:
+
+- The forked project URL is `git@gitlab.com:thedude/awesome-project.git`.
+- The branch of the merge request is `update-docs`.
+
+Here's how the process would look like:
+
+1. First, you need to get the changes that the merge request has introduced.
+ Click the **Check out branch** button that has some pre-populated
+ commands that you can run.
+
+ ![Check out branch button](img/checkout_button.png)
+
+1. Use the copy to clipboard button to copy the first command and paste them
+ in your terminal:
+
+ ```sh
+ git fetch git@gitlab.com:thedude/awesome-project.git update-docs
+ git checkout -b thedude-awesome-project-update-docs FETCH_HEAD
+ ```
+
+ This will fetch the branch of the forked project and then create a local branch
+ based off the fetched branch.
-Enable this functionality while creating or editing a merge request:
+1. Make any changes you want and commit.
+1. Push to the forked project:
-![Enable collaboration](./img/allow_collaboration.png)
+ ```sh
+ git push git@gitlab.com:thedude/awesome-project.git thedude-awesome-project-update-docs:update-docs
+ ```
-[ce-17395]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395
+ 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.
diff --git a/doc/user/project/merge_requests/img/allow_collaboration.png b/doc/user/project/merge_requests/img/allow_collaboration.png
index 3c81e4c27b8..e40e8a6b11c 100644
--- a/doc/user/project/merge_requests/img/allow_collaboration.png
+++ b/doc/user/project/merge_requests/img/allow_collaboration.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/allow_collaboration_after_save.png b/doc/user/project/merge_requests/img/allow_collaboration_after_save.png
new file mode 100644
index 00000000000..4ba4c84c8c5
--- /dev/null
+++ b/doc/user/project/merge_requests/img/allow_collaboration_after_save.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/checkout_button.png b/doc/user/project/merge_requests/img/checkout_button.png
new file mode 100644
index 00000000000..9850795c9b4
--- /dev/null
+++ b/doc/user/project/merge_requests/img/checkout_button.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/create_from_email.png b/doc/user/project/merge_requests/img/create_from_email.png
index 610f0b3d0c1..5cb2afaf976 100644
--- a/doc/user/project/merge_requests/img/create_from_email.png
+++ b/doc/user/project/merge_requests/img/create_from_email.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index d4f8cf929f6..2c8a590fc45 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -92,6 +92,15 @@ request widget will show the "Removes source branch" text.
![Remove source branch status](img/remove_source_branch_status.png)
+## Allow collaboration on merge requests across forks
+
+When a user opens a merge request from a fork, they are given the option to allow
+upstream maintainers to collaborate with them on the source branch. This allows
+the maintainers of the upstream project to make small fixes or rebase branches
+before merging, reducing the back and forth of accepting community contributions.
+
+[Learn more about allowing upstream members to push to forks.](allow_collaboration.md)
+
## Authorization for merge requests
There are two main ways to have a merge request flow with GitLab:
@@ -169,9 +178,9 @@ those conflicts in the GitLab UI.
## Create new merge requests by email
-*This feature needs [incoming email](../../../administration/incoming_email.md)
+_This feature needs [incoming email](../../../administration/incoming_email.md)
to be configured by a GitLab administrator to be available for CE/EE users, and
-it's available on GitLab.com.*
+it's available on GitLab.com._
You can create a new merge request by sending an email to a user-specific email
address. The address can be obtained on the merge requests page by clicking on
@@ -183,8 +192,16 @@ will be used as the merge request description. You need
this feature. If it's not enabled to your instance, you may ask your GitLab
administrator to do so.
+This is a private email address, generated just for you. **Keep it to yourself**
+as anyone who gets ahold of it can create issues or merge requests as if they were you.
+You can add this address to your contact list for easy access.
+
![Create new merge requests by email](img/create_from_email.png)
+_In GitLab 11.7, we updated the format of the generated email address.
+However the older format is still supported, allowing existing aliases
+or contacts to continue working._
+
### Adding patches when creating a merge request via e-mail
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22723) in GitLab 11.5.
@@ -267,7 +284,11 @@ you can preview the changes submitted to a feature-branch through a merge reques
in a per-branch basis. No need to checkout the branch, install and preview locally;
all your changes will be available to preview by anyone with the Review Apps link.
-[Read more about Review Apps.](../../../ci/review_apps/index.md)
+With GitLab's [Route Maps](../../../ci/review_apps/index.md#route-maps) set, the
+merge request widget takes you directly to the pages changed, making it easier and
+faster to preview proposed modifications.
+
+[Read more about Review Apps](../../../ci/review_apps/index.md).
## Pipelines for merge requests
@@ -320,8 +341,8 @@ troubleshooting steps.
This can occur for one of two reasons:
-* Sidekiq doesn't pick up the changes fast enough
-* Because of the bug described in [#41545](https://gitlab.com/gitlab-org/gitlab-ce/issues/41545)
+- Sidekiq doesn't pick up the changes fast enough
+- Because of the bug described in [#41545](https://gitlab.com/gitlab-org/gitlab-ce/issues/41545)
#### Sidekiq
diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md
index 2ec423dcf70..1b57331dbe7 100644
--- a/doc/user/project/merge_requests/squash_and_merge.md
+++ b/doc/user/project/merge_requests/squash_and_merge.md
@@ -18,7 +18,7 @@ Into a single commit on merge:
![A squashed commit followed by a merge commit][squashed-commit]
-The squashed commit's commit message is the merge request title. And note that
+The squashed commit's commit message is the merge request title. And note that
the squashed commit is still followed by a merge commit, as the merge
method for this example repository uses a merge commit. Squashing also works
with the fast-forward merge strategy, see
@@ -30,7 +30,7 @@ details.
When working on a feature branch, you sometimes want to commit your current
progress, but don't really care about the commit messages. Those 'work in
progress commits' don't necessarily contain important information and as such
-you'd rather not include them in your target branch.
+you'd rather not include them in your target branch.
With squash and merge, when the merge request is ready to be merged,
all you have to do is enable squashing before you press merge to join
@@ -56,9 +56,9 @@ This can then be overridden at the time of accepting the merge request:
The squashed commit has the following metadata:
-* Message: the title of the merge request.
-* Author: the author of the merge request.
-* Committer: the user who initiated the squash.
+- Message: the title of the merge request.
+- Author: the author of the merge request.
+- Committer: the user who initiated the squash.
## Squash and fast-forward merge
diff --git a/doc/user/project/operations/error_tracking.md b/doc/user/project/operations/error_tracking.md
new file mode 100644
index 00000000000..fe4b36062f7
--- /dev/null
+++ b/doc/user/project/operations/error_tracking.md
@@ -0,0 +1,30 @@
+# Error Tracking
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/169) in GitLab 11.8.
+
+Error tracking allows developers to easily discover and view the errors that their application may be generating. By surfacing error information where the code is being developed, efficiency and awareness can be increased.
+
+## Sentry error tracking
+
+[Sentry](https://sentry.io/) is an open source error tracking system. GitLab allows administrators to connect Sentry to GitLab, to allow users to view a list of Sentry errors in GitLab itself.
+
+### Deploying Sentry
+
+You may sign up to the cloud hosted <https://sentry.io> or deploy your own [on-premise instance](https://docs.sentry.io/server/installation/).
+
+### Enabling Sentry
+
+GitLab provides an easy way to connect Sentry to your project:
+
+1. Sign up to Sentry.io or [deploy your own](#deploying-sentry) Sentry instance.
+1. [Find or generate](https://docs.sentry.io/api/auth/) a Sentry auth token for your Sentry project.
+1. Navigate to your project’s **Settings > Operations** and provide the Sentry API URL and auth token.
+1. Ensure that the 'Active' checkbox is set.
+1. Click **Save changes** for the changes to take effect.
+1. You can now visit **Operations > Error Tracking** in your project's sidebar to [view a list](#error-tracking-list) of Sentry errors.
+
+## Error Tracking List
+
+The Error Tracking list may be found at **Operations > Error Tracking** in your project's sidebar.
+
+![Error Tracking list](img/error_tracking_list.png)
diff --git a/doc/user/project/operations/img/error_tracking_list.png b/doc/user/project/operations/img/error_tracking_list.png
new file mode 100644
index 00000000000..aa0f9867fdb
--- /dev/null
+++ b/doc/user/project/operations/img/error_tracking_list.png
Binary files differ
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 290dfa5af84..595241b2cba 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -85,6 +85,12 @@ and a project within this group is called `blog`. Your project
URL is `https://gitlab.com/websites/blog/`. Once you enable
GitLab Pages for this project, the site will live under
`https://websites.gitlab.io/blog/`.
+- You created a group for your engineering department called `engineering`,
+a subgroup for all your documentation websites called `docs`,
+and a project within this subgroup is called `workflows`. Your project
+URL is `https://gitlab.com/engineering/docs/workflows/`. Once you enable
+GitLab Pages for this project, the site will live under
+`https://engineering.gitlab.io/docs/workflows`.
#### User and Group Websites
@@ -97,9 +103,7 @@ will be published under `https://john.gitlab.io`.
Once you enable GitLab Pages for your project,
your website will be published under `https://websites.gitlab.io`.
->**Note:**
-GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations).
-You can only create the highest level group website.
+> Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
**General example:**
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index ed049e2e648..a7846b1ee18 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -38,6 +38,7 @@ be served on.
| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` |
| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` |
| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`|
+| Project pages owned by a subgroup | `subgroup/projectname` | `http(s)://groupname.example.io/subgroup/projectname`|
> **Warning:**
> There are some known [limitations](#limitations) regarding namespaces served
@@ -494,8 +495,8 @@ 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 subgroups](../../group/subgroups/index.md#limitations).
-You can only create the highest level group website.
+GitLab Pages [does **not** support group websites for subgroups](../../group/subgroups/index.md#limitations).
+You can only create the highest-level group website.
## Redirects in GitLab Pages
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 051277dfe02..ec8b8444d99 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -3,7 +3,7 @@
> **Notes**:
> - This feature was introduced in 9.1 as [Trigger Schedule][ce-10533].
> - In 9.2, the feature was [renamed to Pipeline Schedule][ce-10853].
-> - Cron notation is parsed by [Rufus-Scheduler](https://github.com/jmettraux/rufus-scheduler).
+> - Cron notation is parsed by [Fugit](https://github.com/floraison/fugit).
Pipeline schedules can be used to run a pipeline at specific intervals, for example every
month on the 22nd for a certain branch.
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 15eacc48dfe..bb9b4238ee9 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -1,7 +1,7 @@
# Pipelines settings
To reach the pipelines settings navigate to your project's
-**Settings ➔ CI/CD**.
+**Settings > CI/CD**.
The following settings can be configured per project.
@@ -10,14 +10,14 @@ The following settings can be configured per project.
With Git strategy, you can choose the default way your repository is fetched
from GitLab in a job.
-There are two options:
+There are two options. Using:
-- Using `git clone` which is slower since it clones the repository from scratch
+- `git clone`, which is slower since it clones the repository from scratch
for every job, ensuring that the project workspace is always pristine.
-- Using `git fetch` which is faster as it re-uses the project workspace (falling
+- `git fetch`, which is faster as it re-uses the project workspace (falling
back to clone if it doesn't exist).
-The default Git strategy can be overridden by the [GIT_STRATEGY variable][var]
+The default Git strategy can be overridden by the [GIT_STRATEGY variable](../../../ci/yaml/README.md#git-strategy)
in `.gitlab-ci.yml`.
## Timeout
@@ -29,14 +29,14 @@ if the job surpasses the threshold, it is marked as failed.
### Timeout overriding on Runner level
-> - [Introduced][ce-17221] in GitLab 10.7.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17221) in GitLab 10.7.
Project defined timeout (either specific timeout set by user or the default
-60 minutes timeout) may be [overridden on Runner level][timeout overriding].
+60 minutes timeout) may be [overridden on Runner level](../../../ci/runners/README.html#setting-maximum-job-timeout-for-a-runner).
## Custom CI config path
-> - [Introduced][ce-12509] in GitLab 9.4.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12509) in GitLab 9.4.
By default we look for the `.gitlab-ci.yml` file in the project's root
directory. If you require a different location **within** the repository,
@@ -45,10 +45,10 @@ this filepath should be **relative** to the root.
Here are some valid examples:
-> * .gitlab-ci.yml
-> * .my-custom-file.yml
-> * my/path/.gitlab-ci.yml
-> * my/path/.my-custom-file.yml
+- `.gitlab-ci.yml`
+- `.my-custom-file.yml`
+- `my/path/.gitlab-ci.yml`
+- `my/path/.my-custom-file.yml`
## Test coverage parsing
@@ -59,7 +59,7 @@ job log using a regular expression. In the pipelines settings, search for the
![Pipelines settings test coverage](img/pipelines_settings_test_coverage.png)
Leave blank if you want to disable it or enter a ruby regular expression. You
-can use http://rubular.com to test your regex.
+can use <http://rubular.com> to test your regex.
If the pipeline succeeds, the coverage is shown in the merge request widget and
in the jobs table.
@@ -79,28 +79,28 @@ project setting under your project's **Settings > CI/CD > General pipelines sett
If **Public pipelines** is enabled (default):
-- for **public** projects, anyone can view the pipelines and access the job details
- (output logs and artifacts)
-- for **internal** projects, any logged in user can view the pipelines
+- For **public** projects, anyone can view the pipelines and access the job details
+ (output logs and artifacts).
+- For **internal** projects, any logged in user can view the pipelines
and access the job details
- (output logs and artifacts)
-- for **private** projects, any member (guest or higher) can view the pipelines
+ (output logs and artifacts).
+- For **private** projects, any member (guest or higher) can view the pipelines
and access the job details
- (output logs and artifacts)
+ (output logs and artifacts).
If **Public pipelines** is disabled:
-- for **public** projects, anyone can view the pipelines, but only members
- (reporter or higher) can access the job details (output logs and artifacts)
-- for **internal** projects, any logged in user can view the pipelines,
- but only members (reporter or higher) can access the job details (output logs
- and artifacts)
-- for **private** projects, only members (reporter or higher)
- can view the pipelines and access the job details (output logs and artifacts)
+- For **public** projects, anyone can view the pipelines, but only members
+ (reporter or higher) can access the job details (output logs and artifacts).
+- For **internal** projects, any logged in user can view the pipelines.
+ However, only members (reporter or higher) can access the job details (output logs
+ and artifacts).
+- For **private** projects, only members (reporter or higher)
+ can view the pipelines and access the job details (output logs and artifacts).
## Auto-cancel pending pipelines
-> [Introduced][ce-9362] in GitLab 9.1.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9362) in GitLab 9.1.
If you want to auto-cancel all pending non-HEAD pipelines on branch, when
new pipeline will be created (after your git push or manually from UI),
@@ -132,19 +132,19 @@ Depending on the status of your job, a badge can have the following values:
You can access a pipeline status badge image using the following link:
-```
+```text
https://example.gitlab.com/<namespace>/<project>/badges/<branch>/build.svg
```
### Test coverage report badge
-GitLab makes it possible to define the regular expression for [coverage report],
+GitLab makes it possible to define the regular expression for [coverage report](#test-coverage-parsing),
that each job log will be matched against. This means that each job in the
pipeline can have the test coverage percentage value defined.
The test coverage badge can be accessed using following link:
-```
+```text
https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg
```
@@ -157,13 +157,28 @@ into your `README.md`:
![coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)
```
-### Environment Variables
+### Badge styles
-[Environment variables](../../../ci/variables/README.html#variables) can be set in an environment to be available to a runner.
+Pipeline badges can be rendered in different styles by adding the `style=style_name` parameter to the URL. Currently two styles are available:
+
+#### Flat (default)
+
+```text
+https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat
+```
+
+![Badge flat style](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage&style=flat)
-[var]: ../../../ci/yaml/README.md#git-strategy
-[coverage report]: #test-coverage-parsing
-[timeout overriding]: ../../../ci/runners/README.html#setting-maximum-job-timeout-for-a-runner
-[ce-9362]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9362
-[ce-12509]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12509
-[ce-17221]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17221
+#### Flat square
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30120) in GitLab 11.8.
+
+```text
+https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?style=flat-square
+```
+
+![Badge flat square style](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage&style=flat-square)
+
+## Environment Variables
+
+[Environment variables](../../../ci/variables/README.html#variables) can be set in an environment to be available to a runner.
diff --git a/doc/user/project/releases.md b/doc/user/project/releases.md
index 8dad4240c91..737842962a9 100644
--- a/doc/user/project/releases.md
+++ b/doc/user/project/releases.md
@@ -1,12 +1 @@
-# Releases
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
-
-Releases mark specific points in a project's development history, communicate
-information about the type of change, and deliver on prepared, often compiled,
-versions of the software to be reused elsewhere. Currently, releases can only be
-created through the API.
-
-Navigate to **Project > Releases** in order to see the list of releases of a project.
-
-![Releases list](img/releases.png)
+This document was moved to [another location](releases/index.md).
diff --git a/doc/user/project/releases/img/releases.png b/doc/user/project/releases/img/releases.png
new file mode 100644
index 00000000000..f8b1b7305ad
--- /dev/null
+++ b/doc/user/project/releases/img/releases.png
Binary files differ
diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md
new file mode 100644
index 00000000000..00a4f6c6a6b
--- /dev/null
+++ b/doc/user/project/releases/index.md
@@ -0,0 +1,62 @@
+# Releases
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
+
+It's typical to create a [Git tag](../../../university/training/topics/tags.md) at
+the moment of release to introduce a checkpoint in your source code
+history, but in most cases your users will need compiled objects or other
+assets output by your CI system to use them, not just the raw source
+code.
+
+GitLab's **Releases** are a way to track deliverables in your project. Consider them
+a snapshot in time of the source, build output, and other metadata or artifacts
+associated with a released version of your code.
+
+At the moment, you can create Release entries via the [Releases API](../../../api/releases/index.md);
+we recommend doing this as one of the last steps in your CI/CD release pipeline.
+
+## Getting started with Releases
+
+Start by giving a [description](#release-description) to the Release and
+including its [assets](#release-assets), as follows.
+
+### Release description
+
+Every Release has a description. You can add any text you like, but we recommend
+including a changelog to describe the content of your release. This will allow
+your users to quickly scan the differences between each one you publish.
+
+NOTE: **Note:**
+[Git's tagging messages](https://git-scm.com/book/en/v2/Git-Basics-Tagging) and
+Release descriptions are unrelated. Description supports [markdown](../../markdown.md).
+
+### Release assets
+
+You can currently add the following types of assets to each Release:
+
+- [Source code](#source-code): state of the repo at the time of the Release
+- [Links](#links): to content such as built binaries or documentation
+
+GitLab will support more asset types in the future, including objects such
+as pre-built packages, compliance/security evidence, or container images.
+
+#### Source code
+
+GitLab automatically generate `zip`, `tar.gz`, `tar.bz2` and `tar`
+archived source code from the given Git tag. These are read-only assets.
+
+#### Links
+
+A link is any URL which can point to whatever you like; documentation, built
+binaries, or other related materials. These can be both internal or external
+links from your GitLab instance.
+
+NOTE: **NOTE**
+You can manipulate links of each release entry with [Release Links API](../../../api/releases/links.md)
+
+## Releases list
+
+Navigate to **Project > Releases** in order to see the list of releases for a given
+project.
+
+![Releases list](img/releases.png)
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index 783081cec26..f05554ffc5b 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -2,16 +2,17 @@
Read through GiLab's branching documentation:
-- [Create a branch](../web_editor.md#create-a-new-branch)
-- [Default branch](#default-branch)
-- [Protected branches](../../protected_branches.md#protected-branches)
-- [Delete merged branches](#delete-merged-branches)
-- [Branch filter search box](#branch-filter-search-box)
+- [Create a branch](../web_editor.md#create-a-new-branch).
+- [Default branch](#default-branch).
+- [Protected branches](../../protected_branches.md#protected-branches).
+- [Delete merged branches](#delete-merged-branches).
+- [Branch filter search box](#branch-filter-search-box).
See also:
-- [GitLab Flow](../../../../university/training/gitlab_flow.md#gitlab-flow): use the best of GitLab for your branching strategies
-- [Getting started with Git](../../../../topics/git/index.md) and GitLab
+- [Branches API](../../../../api/branches.md), for information on operating on repository branches using the GitLab API.
+- [GitLab Flow](../../../../university/training/gitlab_flow.md#gitlab-flow). Use the best of GitLab for your branching strategies.
+- [Getting started with Git](../../../../topics/git/index.md) and GitLab.
## Default branch
@@ -41,7 +42,6 @@ this operation.
It's particularly useful to clean up old branches that were not deleted
automatically when a merge request was merged.
-
## Branch filter search box
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22166) in GitLab 11.5.
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index c6239c8e41c..c7e20f01a75 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -4,7 +4,7 @@ NOTE: **Note:**
The term GPG is used for all OpenPGP/PGP/GPG related material and
implementations.
-> - [Introduced][ce-9546] in GitLab 9.5.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9546) in GitLab 9.5.
> - Subkeys support was added in GitLab 10.1.
GitLab can show whether a commit is verified or not when signed with a GPG key.
@@ -36,30 +36,22 @@ to be met:
## Generating a GPG key
-> **Notes:**
-> - If your Operating System has `gpg2` installed, replace `gpg` with `gpg2` in
-> the following commands.
-> - If Git is using `gpg` and you get errors like `secret key not available` or
-> `gpg: signing failed: secret key not available`, run the following command to
-> change to `gpg2`:
->
-> ```
-> git config --global gpg.program gpg2
-> ```
-
If you don't already have a GPG key, the following steps will help you get
started:
-1. [Install GPG](https://www.gnupg.org/download/index.html) for your operating system
-1. Generate the private/public key pair with the following command:
+1. [Install GPG](https://www.gnupg.org/download/index.html) for your operating system.
+ If your Operating System has `gpg2` installed, replace `gpg` with `gpg2` in
+ the following commands.
+1. Generate the private/public key pair with the following command, which will
+ spawn a series of questions:
```sh
gpg --full-gen-key
```
-
- _NOTE: In some cases like Gpg4win on Windows and other Mac OS versions the command here may be ` gpg --gen-key`_
- This will spawn a series of questions.
+ NOTE: **Note:**
+ In some cases like Gpg4win on Windows and other macOS versions, the command
+ here may be `gpg --gen-key`.
1. The first question is which algorithm can be used. Select the kind you want
or press <kbd>Enter</kbd> to choose the default (RSA and RSA):
@@ -109,10 +101,10 @@ started:
GnuPG needs to construct a user ID to identify your key.
Real name: Mr. Robot
- Email address: mr@robot.sh
+ Email address: <your_email>
Comment:
You selected this USER-ID:
- "Mr. Robot <mr@robot.sh>"
+ "Mr. Robot <your_email>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
```
@@ -121,10 +113,10 @@ started:
1. Use the following command to list the private GPG key you just created:
```
- gpg --list-secret-keys --keyid-format LONG mr@robot.sh
+ gpg --list-secret-keys --keyid-format LONG <your_email>
```
- Replace `mr@robot.sh` with the email address you entered above.
+ Replace `<your_email>` with the email address you entered above.
1. Copy the GPG key ID that starts with `sec`. In the following example, that's
`30F2B65B9246B6CA`:
@@ -132,7 +124,7 @@ started:
```
sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC]
D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA
- uid [ultimate] Mr. Robot <mr@robot.sh>
+ uid [ultimate] Mr. Robot <your_email>
ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
```
@@ -146,7 +138,7 @@ started:
## Adding a GPG key to your account
->**Note:**
+NOTE: **Note:**
Once you add a key, you cannot edit it, only remove it. In case the paste
didn't work, you'll have to remove the offending key and re-add it.
@@ -174,11 +166,11 @@ key to use.
1. Use the following command to list the private GPG key you just created:
- ```
- gpg --list-secret-keys --keyid-format LONG mr@robot.sh
+ ```sh
+ gpg --list-secret-keys --keyid-format LONG <your_email>
```
- Replace `mr@robot.sh` with the email address you entered above.
+ Replace `<your_email>` with the email address you entered above.
1. Copy the GPG key ID that starts with `sec`. In the following example, that's
`30F2B65B9246B6CA`:
@@ -186,18 +178,27 @@ key to use.
```
sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC]
D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA
- uid [ultimate] Mr. Robot <mr@robot.sh>
+ uid [ultimate] Mr. Robot <your_email>
ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
```
1. Tell Git to use that key to sign the commits:
- ```
+ ```sh
git config --global user.signingkey 30F2B65B9246B6CA
```
Replace `30F2B65B9246B6CA` with your GPG key ID.
+
+1. (Optional) If Git is using `gpg` and you get errors like `secret key not available`
+ or `gpg: signing failed: secret key not available`, run the following command to
+ change to `gpg2`:
+
+ ```sh
+ git config --global gpg.program gpg2
+ ```
+
## Signing commits
After you have [created your GPG key](#generating-a-gpg-key) and [added it to
@@ -261,4 +262,7 @@ To remove a GPG key from your account:
1. Navigate to the **GPG keys** tab.
1. Click on the trash icon besides the GPG key you want to delete.
-[ce-9546]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9546
+## Rejecting commits that are not signed **[PREMIUM]**
+
+You can configure your project to reject commits that aren't GPG-signed
+via [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html).
diff --git a/doc/user/project/settings/img/general_settings.png b/doc/user/project/settings/img/general_settings.png
index 96f5b84871f..4ff6fff5ca3 100644
--- a/doc/user/project/settings/img/general_settings.png
+++ b/doc/user/project/settings/img/general_settings.png
Binary files differ
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 3bbfa74f4b7..89008fd15b9 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -61,7 +61,7 @@ The following items will be exported:
- Project and wiki repositories
- Project uploads
-- Project configuration including web hooks and services
+- Project configuration, including services
- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets,
and other project entities
- LFS objects
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index d6754372816..b09a3f927d1 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -14,7 +14,7 @@ functionality of a project.
### General project settings
-Adjust your project's name, description, avatar, [default branch](../repository/branches/index.md#default-branch), and tags:
+Adjust your project's name, description, avatar, [default branch](../repository/branches/index.md#default-branch), and topics:
![general project settings](img/general_settings.png)
@@ -122,3 +122,9 @@ GitLab administrators can use the admin interface to move any project to any
namespace if needed.
[permissions]: ../../permissions.md##project-members-permissions
+
+## Operations settings
+
+### Error Tracking
+
+Configure Error Tracking to discover and view [Sentry errors within GitLab](../operations/error_tracking.md).
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 78c1294346b..770cef42995 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -31,7 +31,7 @@ on the search field on the top-right of your screen:
If you want to search for issues present in a specific project, navigate to
a project's **Issues** tab, and click on the field **Search or filter results...**. It will
-display a dropdown menu, from which you can add filters per author, assignee, milestone,
+display a dropdown menu, from which you can add filters per author, assignee, milestone,
label, weight, and 'my-reaction' (based on your emoji votes). When done, press **Enter** on your keyboard to filter the issues.
![filter issues in a project](img/issue_search_filter.png)
@@ -54,12 +54,12 @@ Selecting **Any** does the opposite. It returns results that have a non-empty va
You can filter issues and merge requests by specific terms included in titles or descriptions.
-* Syntax
- * Searches look for all the words in a query, in any order. E.g.: searching
+- Syntax
+ - Searches look for all the words in a query, in any order. E.g.: searching
issues for `display bug` will return all issues matching both those words, in any order.
- * To find the exact term, use double quotes: `"display bug"`
-* Limitation
- * For performance reasons, terms shorter than 3 chars are ignored. E.g.: searching
+ - To find the exact term, use double quotes: `"display bug"`
+- Limitation
+ - For performance reasons, terms shorter than 3 chars are ignored. E.g.: searching
issues for `included in titles` is same as `included titles`
![filter issues by specific terms](img/issue_search_by_term.png)
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index ec5943fd51b..3fb553280d0 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -4,9 +4,9 @@ Documentation on how to use Git LFS are under [Managing large binary files with
## Requirements
-* Git LFS is supported in GitLab starting with version 8.2.
-* Support for object storage, such as AWS S3, was introduced in 10.0.
-* Users need to install [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up.
+- Git LFS is supported in GitLab starting with version 8.2.
+- Support for object storage, such as AWS S3, was introduced in 10.0.
+- Users need to install [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up.
## Configuration
@@ -15,9 +15,9 @@ GitLab is installed on.
There are various configuration options to help GitLab server administrators:
-* Enabling/disabling Git LFS support
-* Changing the location of LFS object storage
-* Setting up object storage supported by [Fog](http://fog.io/about/provider_documentation.html)
+- Enabling/disabling Git LFS support
+- Changing the location of LFS object storage
+- Setting up object storage supported by [Fog](http://fog.io/about/provider_documentation.html)
### Configuration for Omnibus installations
@@ -229,11 +229,11 @@ See more information in [!19581](https://gitlab.com/gitlab-org/gitlab-ce/merge_r
## Known limitations
-* Support for removing unreferenced LFS objects was added in 8.14 onwards.
-* LFS authentications via SSH was added with GitLab 8.12
-* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
-* The storage statistics currently count each LFS object multiple times for
- every project linking to it
+- Support for removing unreferenced LFS objects was added in 8.14 onwards.
+- LFS authentications via SSH was added with GitLab 8.12.
+- Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
+- The storage statistics currently count each LFS object multiple times for
+ every project linking to it.
[reconfigure gitlab]: ../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
[restart gitlab]: ../../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index d02e921fa84..da0243705aa 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -21,18 +21,18 @@ Documentation for GitLab instance administrators is under [LFS administration do
## Requirements
-* Git LFS is supported in GitLab starting with version 8.2
-* Git LFS must be enabled under project settings
-* [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up
+- Git LFS is supported in GitLab starting with version 8.2
+- Git LFS must be enabled under project settings
+- [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up
## Known limitations
-* Git LFS v1 original API is not supported since it was deprecated early in LFS
+- Git LFS v1 original API is not supported since it was deprecated early in LFS
development
-* When SSH is set as a remote, Git LFS objects still go through HTTPS
-* Any Git LFS request will ask for HTTPS credentials to be provided so a good Git
+- When SSH is set as a remote, Git LFS objects still go through HTTPS
+- Any Git LFS request will ask for HTTPS credentials to be provided so a good Git
credentials store is recommended
-* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
+- Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
to add the URL to Git config manually (see [troubleshooting](#troubleshooting))
>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
@@ -158,16 +158,16 @@ git lfs unlock --id=123 --force
There are a couple of reasons why this error can occur:
-* You don't have permissions to access certain LFS object
+- You don't have permissions to access certain LFS object
Check if you have permissions to push to the project or fetch from the project.
-* Project is not allowed to access the LFS object
+- Project is not allowed to access the LFS object
LFS object you are trying to push to the project or fetch from the project is not
available to the project anymore. Probably the object was removed from the server.
-* Local git repository is using deprecated LFS API
+- Local git repository is using deprecated LFS API
### Invalid status for `<url>` : 501
@@ -180,15 +180,15 @@ git lfs logs last
If the status `error 501` is shown, it is because:
-* Git LFS is not enabled in project settings. Check your project settings and
+- Git LFS is not enabled in project settings. Check your project settings and
enable Git LFS.
-* Git LFS support is not enabled on the GitLab server. Check with your GitLab
+- Git LFS support is not enabled on the GitLab server. Check with your GitLab
administrator why Git LFS is not enabled on the server. See
[LFS administration documentation](lfs_administration.md) for instructions
on how to enable LFS support.
-* Git LFS client version is not supported by GitLab server. Check your Git LFS
+- Git LFS client version is not supported by GitLab server. Check your Git LFS
version with `git lfs version`. Check the Git config of the project for traces
of deprecated API with `git lfs -l`. If `batch = false` is set in the config,
remove the line and try to update your Git LFS client. Only version 1.0.1 and
diff --git a/doc/workflow/releases.md b/doc/workflow/releases.md
index 6176784fc57..00cddda24a4 100644
--- a/doc/workflow/releases.md
+++ b/doc/workflow/releases.md
@@ -1,14 +1,17 @@
# Releases
-You can turn any git tag into a release, by adding a note to it.
-Release notes behave like any other markdown form in GitLab so you can write text and drag-n-drop files to it.
-Release notes are stored in the database of GitLab.
+NOTE: In GitLab 11.7, we introduced the full fledged [Releases](../user/project/releases/index.md)
+feature. You can still create release notes on this page, but the new method is preferred.
-There are several ways to add release notes:
+You can add release notes to any git tag using the notes feature. Release notes
+behave like any other markdown form in GitLab so you can write text and
+drag-n-drop files to it. Release notes are stored in GitLab's database.
-* In the interface, when you create a new git tag with GitLab
-* In the interface, by adding a note to an existing git tag
-* with the GitLab API
+There are several ways to add release notes:
+
+- In the interface, when you create a new git tag
+- In the interface, by adding a note to an existing git tag
+- Using the GitLab API
## New tag page with release notes text area
@@ -17,4 +20,3 @@ There are several ways to add release notes:
## Tags page with button to add or edit release notes for existing git tag
![tags](releases/tags.png)
-
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index e259e6fe50c..ac26aeab137 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -80,10 +80,13 @@ mirror.
To set up a mirror from GitLab to GitHub, you need to follow these steps:
1. Create a [GitHub personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/) with the `public_repo` box checked.
-1. Fill in the **Git repository URL** field, with the personal access token instead of a password.
- For example: `https://<GitHubUsername>:<GitHubPersonalAccessToken>@github.com/group/project.git`.
+1. Fill in the **Git repository URL** field using this format: `https://<your_github_username>@github.com/<your_github_group>/<your_github_project>.git`.
+1. Fill in **Password** field with your GitHub personal access token.
1. Click the **Mirror repository** button.
-1. Wait, or click the update button.
+
+The mirrored repository will be listed. For example, `https://*****:*****@github.com/<your_github_group>/<your_github_project>.git`.
+
+The repository will push soon. To force a push, click the appropriate button.
## Pulling from a remote repository **[STARTER]**
@@ -137,8 +140,8 @@ increased each time it fails, up to a maximum amount of time.
### SSH authentication
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551) for Push mirroring in [GitLab Starter](https://about.gitlab.com/pricing/) 9.5.
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22982) for Pull mirroring in [GitLab Core](https://about.gitlab.com/pricing/) 11.6
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551) for Pull mirroring in [GitLab Starter](https://about.gitlab.com/pricing/) 9.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22982) for Push mirroring in [GitLab Core](https://about.gitlab.com/pricing/) 11.6
SSH authentication is mutual:
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index 8a75687e4f5..2bb140b99d0 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -8,8 +8,9 @@ requests within GitLab.
## Overview
Time Tracking lets you:
-* record the time spent working on an issue or a merge request,
-* add an estimate of the amount of time needed to complete an issue or a merge
+
+- Record the time spent working on an issue or a merge request.
+- Add an estimate of the amount of time needed to complete an issue or a merge
request.
You don't have to indicate an estimate to enter the time spent, and vice versa.
@@ -62,11 +63,12 @@ To remove all the time spent at once, use `/remove_time_spent`.
## Configuration
The following time units are available:
-* months (mo)
-* weeks (w)
-* days (d)
-* hours (h)
-* minutes (m)
+
+- months (mo)
+- weeks (w)
+- days (d)
+- hours (h)
+- minutes (m)
Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 19da0b2c434..2b42e377c74 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -100,6 +100,7 @@ module API
mount ::API::CircuitBreakers
mount ::API::Commits
mount ::API::CommitStatuses
+ mount ::API::ContainerRegistry
mount ::API::DeployKeys
mount ::API::Deployments
mount ::API::Environments
@@ -110,6 +111,7 @@ module API
mount ::API::GroupMilestones
mount ::API::Groups
mount ::API::GroupVariables
+ mount ::API::ImportGithub
mount ::API::Internal
mount ::API::Issues
mount ::API::JobArtifacts
@@ -129,6 +131,7 @@ module API
mount ::API::PagesDomains
mount ::API::Pipelines
mount ::API::PipelineSchedules
+ mount ::API::ProjectClusters
mount ::API::ProjectExport
mount ::API::ProjectImport
mount ::API::ProjectHooks
@@ -140,6 +143,7 @@ module API
mount ::API::ProtectedBranches
mount ::API::ProtectedTags
mount ::API::Releases
+ mount ::API::Release::Links
mount ::API::Repositories
mount ::API::Runner
mount ::API::Runners
diff --git a/lib/api/container_registry.rb b/lib/api/container_registry.rb
new file mode 100644
index 00000000000..e4493910196
--- /dev/null
+++ b/lib/api/container_registry.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+module API
+ class ContainerRegistry < Grape::API
+ include PaginationParams
+
+ REGISTRY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
+ tag_name: API::NO_SLASH_URL_PART_REGEX)
+
+ before { error!('404 Not Found', 404) unless Feature.enabled?(:container_registry_api, user_project, default_enabled: true) }
+ before { authorize_read_container_images! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get a project container repositories' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ success Entities::ContainerRegistry::Repository
+ end
+ params do
+ use :pagination
+ end
+ get ':id/registry/repositories' do
+ repositories = user_project.container_repositories.ordered
+
+ present paginate(repositories), with: Entities::ContainerRegistry::Repository
+ end
+
+ desc 'Delete repository' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ end
+ delete ':id/registry/repositories/:repository_id', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ authorize_admin_container_image!
+
+ DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id)
+
+ status :accepted
+ end
+
+ desc 'Get a list of repositories tags' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ success Entities::ContainerRegistry::Tag
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ use :pagination
+ end
+ get ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ authorize_read_container_image!
+
+ tags = Kaminari.paginate_array(repository.tags)
+ present paginate(tags), with: Entities::ContainerRegistry::Tag
+ end
+
+ desc 'Delete repository tags (in bulk)' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ requires :name_regex, type: String, desc: 'The tag name regexp to delete, specify .* to delete all'
+ optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name'
+ optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month'
+ end
+ delete ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ authorize_admin_container_image!
+
+ CleanupContainerRepositoryWorker.perform_async(current_user.id, repository.id,
+ declared_params.except(:repository_id)) # rubocop: disable CodeReuse/ActiveRecord
+
+ status :accepted
+ end
+
+ desc 'Get a details about repository tag' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ success Entities::ContainerRegistry::TagDetails
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ requires :tag_name, type: String, desc: 'The name of the tag'
+ end
+ get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ authorize_read_container_image!
+ validate_tag!
+
+ present tag, with: Entities::ContainerRegistry::TagDetails
+ end
+
+ desc 'Delete repository tag' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ requires :tag_name, type: String, desc: 'The name of the tag'
+ end
+ delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
+ authorize_destroy_container_image!
+ validate_tag!
+
+ tag.delete
+
+ status :ok
+ end
+ end
+
+ helpers do
+ def authorize_read_container_images!
+ authorize! :read_container_image, user_project
+ end
+
+ def authorize_read_container_image!
+ authorize! :read_container_image, repository
+ end
+
+ def authorize_update_container_image!
+ authorize! :update_container_image, repository
+ end
+
+ def authorize_destroy_container_image!
+ authorize! :admin_container_image, repository
+ end
+
+ def authorize_admin_container_image!
+ authorize! :admin_container_image, repository
+ end
+
+ def repository
+ @repository ||= user_project.container_repositories.find(params[:repository_id])
+ end
+
+ def tag
+ @tag ||= repository.tag(params[:tag_name])
+ end
+
+ def validate_tag!
+ not_found!('Tag') unless tag.valid?
+ end
+ end
+ end
+end
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index 8706a971a1a..eb45df31ff9 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -33,7 +33,7 @@ module API
success Entities::Deployment
end
params do
- requires :deployment_id, type: Integer, desc: 'The deployment ID'
+ requires :deployment_id, type: Integer, desc: 'The deployment ID'
end
get ':id/deployments/:deployment_id' do
authorize! :read_deployment, user_project
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 7116ab2882b..4edec631e8d 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -115,6 +115,9 @@ module API
expose :group_name do |group_link, options|
group_link.group.name
end
+ expose :group_full_path do |group_link, options|
+ group_link.group.full_path
+ end
expose :group_access, as: :group_access_level
expose :expires_at
end
@@ -187,7 +190,7 @@ module API
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
# rubocop: disable CodeReuse/ActiveRecord
- def self.preload_relation(projects_relation, options = {})
+ def self.preload_relation(projects_relation, options = {})
# Preloading tags, should be done with using only `:tags`,
# as `:tags` are defined as: `has_many :tags, through: :taggings`
# N+1 is solved then by using `subject.tags.map(&:name)`
@@ -271,13 +274,13 @@ module API
expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
# rubocop: disable CodeReuse/ActiveRecord
- def self.preload_relation(projects_relation, options = {})
+ def self.preload_relation(projects_relation, options = {})
# Preloading tags, should be done with using only `:tags`,
# as `:tags` are defined as: `has_many :tags, through: :taggings`
# N+1 is solved then by using `subject.tags.map(&:name)`
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
super(projects_relation).preload(:group)
- .preload(project_group_links: :group,
+ .preload(project_group_links: { group: :route },
fork_network: :root_project,
fork_network_member: :forked_from_project,
forked_from_project: [:route, :forks, :tags, namespace: :route])
@@ -341,19 +344,23 @@ module API
class GroupDetail < Group
expose :projects, using: Entities::Project do |group, options|
- GroupProjectsFinder.new(
+ projects = GroupProjectsFinder.new(
group: group,
current_user: options[:current_user],
options: { only_owned: true }
).execute
+
+ Entities::Project.prepare_relation(projects)
end
expose :shared_projects, using: Entities::Project do |group, options|
- GroupProjectsFinder.new(
+ projects = GroupProjectsFinder.new(
group: group,
current_user: options[:current_user],
options: { only_shared: true }
).execute
+
+ Entities::Project.prepare_relation(projects)
end
end
@@ -961,7 +968,7 @@ module API
if options[:group_members]
options[:group_members].find { |member| member.source_id == project.namespace_id }
else
- project.group.group_member(options[:current_user])
+ project.group.highest_group_member(options[:current_user])
end
end
end
@@ -1093,12 +1100,34 @@ module API
expose :description
end
+ module Releases
+ class Link < Grape::Entity
+ expose :id
+ expose :name
+ expose :url
+ expose :external?, as: :external
+ end
+
+ class Source < Grape::Entity
+ expose :format
+ expose :url
+ end
+ end
+
class Release < TagRelease
expose :name
expose :description_html
expose :created_at
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
expose :commit, using: Entities::Commit
+
+ expose :assets do
+ expose :assets_count, as: :count
+ expose :sources, using: Entities::Releases::Source
+ expose :links, using: Entities::Releases::Link do |release, options|
+ release.links.sorted
+ end
+ end
end
class Tag < Grape::Entity
@@ -1516,5 +1545,38 @@ module API
expose :from_content
expose :to_content
end
+
+ module Platform
+ class Kubernetes < Grape::Entity
+ expose :api_url
+ expose :namespace
+ expose :authorization_type
+ expose :ca_cert
+ end
+ end
+
+ module Provider
+ class Gcp < Grape::Entity
+ expose :cluster_id
+ expose :status_name
+ expose :gcp_project_id
+ expose :zone
+ expose :machine_type
+ expose :num_nodes
+ expose :endpoint
+ end
+ end
+
+ class Cluster < Grape::Entity
+ expose :id, :name, :created_at
+ expose :provider_type, :platform_type, :environment_scope, :cluster_type
+ expose :user, using: Entities::UserBasic
+ expose :platform_kubernetes, using: Entities::Platform::Kubernetes
+ expose :provider_gcp, using: Entities::Provider::Gcp
+ end
+
+ class ClusterProject < Cluster
+ expose :project, using: Entities::BasicProjectDetails
+ end
end
end
diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb
new file mode 100644
index 00000000000..00833ca7480
--- /dev/null
+++ b/lib/api/entities/container_registry.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module ContainerRegistry
+ class Repository < Grape::Entity
+ expose :id
+ expose :name
+ expose :path
+ expose :location
+ expose :created_at
+ end
+
+ class Tag < Grape::Entity
+ expose :name
+ expose :path
+ expose :location
+ end
+
+ class TagDetails < Tag
+ expose :revision
+ expose :short_revision
+ expose :digest
+ expose :created_at
+ expose :total_size
+ end
+ end
+ end
+end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 633f24d3c9a..0278c6c54a5 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -74,7 +74,7 @@ module API
success Entities::Environment
end
params do
- requires :environment_id, type: Integer, desc: 'The environment ID'
+ requires :environment_id, type: Integer, desc: 'The environment ID'
end
delete ':id/environments/:environment_id' do
authorize! :update_environment, user_project
@@ -88,7 +88,7 @@ module API
success Entities::Environment
end
params do
- requires :environment_id, type: Integer, desc: 'The environment ID'
+ requires :environment_id, type: Integer, desc: 'The environment ID'
end
post ':id/environments/:environment_id/stop' do
authorize! :read_environment, user_project
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 1331248699f..835aac05905 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -16,15 +16,13 @@ module API
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def gate_targets(params)
- targets = []
- targets << Feature.group(params[:feature_group]) if params[:feature_group]
- targets << UserFinder.new(params[:user]).find_by_username if params[:user]
+ Feature::Target.new(params).targets
+ end
- targets
+ def gate_specified?(params)
+ Feature::Target.new(params).gate_specified?
end
- # rubocop: enable CodeReuse/ActiveRecord
end
resource :features do
@@ -44,6 +42,7 @@ module API
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
optional :feature_group, type: String, desc: 'A Feature group name'
optional :user, type: String, desc: 'A GitLab username'
+ optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce'
end
post ':name' do
feature = Feature.get(params[:name])
@@ -52,13 +51,13 @@ module API
case value
when true
- if targets.present?
+ if gate_specified?(params)
targets.each { |target| feature.enable(target) }
else
feature.enable
end
when false
- if targets.present?
+ if gate_specified?(params)
targets.each { |target| feature.disable(target) }
else
feature.disable
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 6c1a730935a..fa6c9777824 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -235,8 +235,8 @@ module API
forbidden! unless current_user.admin?
end
- def authorize!(action, subject = :global)
- forbidden! unless can?(current_user, action, subject)
+ def authorize!(action, subject = :global, reason = nil)
+ forbidden!(reason) unless can?(current_user, action, subject)
end
def authorize_push_project
@@ -496,7 +496,11 @@ module API
def send_git_blob(repository, blob)
env['api.format'] = :txt
content_type 'text/plain'
- header['Content-Disposition'] = content_disposition('attachment', blob.name)
+ header['Content-Disposition'] = content_disposition('inline', blob.name)
+
+ # Let Workhorse examine the content and determine the better content disposition
+ header[Gitlab::Workhorse::DETECT_HEADER] = "true"
+
header(*Gitlab::Workhorse.send_git_blob(repository, blob))
end
@@ -512,7 +516,7 @@ module API
# `request`. We workaround this by defining methods that returns the right
# values.
def define_params_for_grape_middleware
- self.define_singleton_method(:request) { Rack::Request.new(env) }
+ self.define_singleton_method(:request) { ActionDispatch::Request.new(env) }
self.define_singleton_method(:params) { request.params.symbolize_keys }
end
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index d311cbb5f7e..de59c915d66 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -146,7 +146,7 @@ module API
end
def add_default_pagination_headers
- header 'X-Per-Page', per_page.to_s
+ header 'X-Per-Page', per_page.to_s
end
def add_navigation_links(next_page_params)
@@ -178,15 +178,26 @@ module API
end
def paginate(relation)
- relation = add_default_order(relation)
-
- relation.page(params[:page]).per(params[:per_page]).tap do |data|
+ paginate_with_limit_optimization(add_default_order(relation)).tap do |data|
add_pagination_headers(data)
end
end
private
+ def paginate_with_limit_optimization(relation)
+ pagination_data = relation.page(params[:page]).per(params[:per_page])
+ return pagination_data unless pagination_data.is_a?(ActiveRecord::Relation)
+ return pagination_data unless Feature.enabled?(:api_kaminari_count_with_limit)
+
+ limited_total_count = pagination_data.total_count_with_limit
+ if limited_total_count > Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT
+ pagination_data.without_count
+ else
+ pagination_data
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def add_default_order(relation)
if relation.is_a?(ActiveRecord::Relation) && relation.order_values.empty?
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 45d0343bc89..16df8e830e1 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -26,7 +26,7 @@ module API
end
def get_runner_ip
- { ip_address: request.ip }
+ { ip_address: request.env["HTTP_X_FORWARDED_FOR"] || request.ip }
end
def current_runner
diff --git a/lib/api/helpers/version.rb b/lib/api/helpers/version.rb
new file mode 100644
index 00000000000..7f53094e90c
--- /dev/null
+++ b/lib/api/helpers/version.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ class Version
+ include Helpers::RelatedResourcesHelpers
+
+ def initialize(version)
+ @version = version.to_s
+
+ unless API.versions.include?(version)
+ raise ArgumentError, 'Unknown API version!'
+ end
+ end
+
+ def root_path
+ File.join('/', API.prefix.to_s, @version)
+ end
+
+ def root_url
+ @root_url ||= expose_url(root_path)
+ end
+
+ def to_s
+ @version
+ end
+ end
+ end
+end
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
new file mode 100644
index 00000000000..bb4e536cf57
--- /dev/null
+++ b/lib/api/import_github.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module API
+ class ImportGithub < Grape::API
+ rescue_from Octokit::Unauthorized, with: :provider_unauthorized
+
+ helpers do
+ def client
+ @client ||= Gitlab::LegacyGithubImport::Client.new(params[:personal_access_token], client_options)
+ end
+
+ def access_params
+ { github_access_token: params[:personal_access_token] }
+ end
+
+ def client_options
+ {}
+ end
+
+ def provider
+ :github
+ end
+ end
+
+ desc 'Import a GitHub project' do
+ detail 'This feature was introduced in GitLab 11.3.4.'
+ success Entities::ProjectEntity
+ end
+ params do
+ requires :personal_access_token, type: String, desc: 'GitHub personal access token'
+ requires :repo_id, type: Integer, desc: 'GitHub repository ID'
+ optional :new_name, type: String, desc: 'New repo name'
+ requires :target_namespace, type: String, desc: 'Namespace to import repo into'
+ end
+ post 'import/github' do
+ result = Import::GithubService.new(client, current_user, params).execute(access_params, provider)
+
+ if result[:status] == :success
+ present ProjectSerializer.new.represent(result[:project])
+ else
+ status result[:http_status]
+ { errors: result[:message] }
+ end
+ end
+ end
+end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index dac700482b4..afa3ac80121 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -294,7 +294,7 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
- desc 'List merge requests that are related to the issue' do
+ desc 'List merge requests that are related to the issue' do
success Entities::MergeRequestBasic
end
params do
@@ -318,7 +318,7 @@ module API
present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
end
- desc 'List merge requests closing issue' do
+ desc 'List merge requests closing issue' do
success Entities::MergeRequestBasic
end
params do
@@ -335,7 +335,7 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
- desc 'List participants for an issue' do
+ desc 'List participants for an issue' do
success Entities::UserBasic
end
params do
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index a4068a200b3..933bd067e26 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -23,17 +23,14 @@ module API
requires :job, type: String, desc: 'The name for the job'
end
route_setting :authentication, job_token_allowed: true
- # rubocop: disable CodeReuse/ActiveRecord
get ':id/jobs/artifacts/:ref_name/download',
requirements: { ref_name: /.+/ } do
authorize_download_artifacts!
- builds = user_project.latest_successful_builds_for(params[:ref_name])
- latest_build = builds.find_by!(name: params[:job])
+ latest_build = user_project.latest_successful_build_for!(params[:job], params[:ref_name])
present_carrierwave_file!(latest_build.artifacts_file)
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Download a specific file from artifacts archive from a ref' do
detail 'This feature was introduced in GitLab 11.5'
@@ -48,7 +45,7 @@ module API
requirements: { ref_name: /.+/ } do
authorize_download_artifacts!
- build = user_project.latest_successful_build_for(params[:job], params[:ref_name])
+ build = user_project.latest_successful_build_for!(params[:job], params[:ref_name])
path = Gitlab::Ci::Build::Artifacts::Path
.new(params[:artifact_path])
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 45c694b6448..59f0dbe8a9b 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -52,7 +52,7 @@ module API
success Entities::Job
end
params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
use :optional_scope
use :pagination
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 2e676b0aa6b..d5eb2b94669 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -69,7 +69,7 @@ module API
success Entities::Label
end
params do
- requires :name, type: String, desc: 'The name of the label to be updated'
+ requires :name, type: String, desc: 'The name of the label to be updated'
optional :new_name, type: String, desc: 'The new name of the label'
optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names"
optional :description, type: String, desc: 'The new description of label'
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index 0342a4b6654..a7672021db0 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -8,7 +8,8 @@ module API
requires :content, type: String, desc: 'Content of .gitlab-ci.yml'
end
post '/lint' do
- error = Gitlab::Ci::YamlProcessor.validation_message(params[:content])
+ error = Gitlab::Ci::YamlProcessor.validation_message(params[:content],
+ user: current_user)
status 200
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 8c1951cc535..132b19164d0 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -179,7 +179,7 @@ module API
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request'
optional :labels, type: String, desc: 'Comma-separated list of label names'
- optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging'
+ optional :remove_source_branch, type: Boolean, desc: 'Delete source branch when merging'
optional :allow_collaboration, type: Boolean, desc: 'Allow commits from members who can merge to the target branch'
optional :allow_maintainer_to_push, type: Boolean, as: :allow_collaboration, desc: '[deprecated] See allow_collaboration'
optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index 47b711917e2..c86b50d3736 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -32,7 +32,7 @@ module API
success Entities::PipelineScheduleDetails
end
params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
get ':id/pipeline_schedules/:pipeline_schedule_id' do
present pipeline_schedule, with: Entities::PipelineScheduleDetails
@@ -87,7 +87,7 @@ module API
success Entities::PipelineScheduleDetails
end
params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
authorize! :update_pipeline_schedule, pipeline_schedule
@@ -103,7 +103,7 @@ module API
success Entities::PipelineScheduleDetails
end
params do
- requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
+ requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
delete ':id/pipeline_schedules/:pipeline_schedule_id' do
authorize! :admin_pipeline_schedule, pipeline_schedule
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 7a7b23d2bbb..1f59b27f685 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -42,7 +42,7 @@ module API
success Entities::Pipeline
end
params do
- requires :ref, type: String, desc: 'Reference'
+ requires :ref, type: String, desc: 'Reference'
optional :variables, Array, desc: 'Array of variables available in the pipeline'
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -101,7 +101,7 @@ module API
success Entities::Pipeline
end
params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
post ':id/pipelines/:pipeline_id/retry' do
authorize! :update_pipeline, user_project
@@ -116,7 +116,7 @@ module API
success Entities::Pipeline
end
params do
- requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
+ requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
post ':id/pipelines/:pipeline_id/cancel' do
authorize! :update_pipeline, user_project
diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb
new file mode 100644
index 00000000000..c96261a7b57
--- /dev/null
+++ b/lib/api/project_clusters.rb
@@ -0,0 +1,142 @@
+# frozen_string_literal: true
+
+module API
+ class ProjectClusters < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ # EE::API::ProjectClusters will
+ # override these methods
+ helpers do
+ params :create_params_ee do
+ end
+
+ params :update_params_ee do
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of the project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get all clusters from the project' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::Cluster
+ end
+ params do
+ use :pagination
+ end
+ get ':id/clusters' do
+ authorize! :read_cluster, user_project
+
+ present paginate(clusters_for_current_user), with: Entities::Cluster
+ end
+
+ desc 'Get specific cluster for the project' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::ClusterProject
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The cluster ID'
+ end
+ get ':id/clusters/:cluster_id' do
+ authorize! :read_cluster, cluster
+
+ present cluster, with: Entities::ClusterProject
+ end
+
+ desc 'Adds an existing cluster' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::ClusterProject
+ end
+ params do
+ requires :name, type: String, desc: 'Cluster name'
+ optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true'
+ requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
+ requires :api_url, type: String, allow_blank: false, desc: 'URL to access the Kubernetes API'
+ requires :token, type: String, desc: 'Token to authenticate against Kubernetes'
+ optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
+ optional :namespace, type: String, desc: 'Unique namespace related to Project'
+ optional :authorization_type, type: String, values: Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
+ end
+ use :create_params_ee
+ end
+ post ':id/clusters/user' do
+ authorize! :add_cluster, user_project, 'Instance does not support multiple Kubernetes clusters'
+
+ user_cluster = ::Clusters::CreateService
+ .new(current_user, create_cluster_user_params)
+ .execute
+
+ if user_cluster.persisted?
+ present user_cluster, with: Entities::ClusterProject
+ else
+ render_validation_error!(user_cluster)
+ end
+ end
+
+ desc 'Update an existing cluster' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::ClusterProject
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The cluster ID'
+ optional :name, type: String, desc: 'Cluster name'
+ optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
+ optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
+ optional :token, type: String, desc: 'Token to authenticate against Kubernetes'
+ optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
+ optional :namespace, type: String, desc: 'Unique namespace related to Project'
+ end
+ use :update_params_ee
+ end
+ put ':id/clusters/:cluster_id' do
+ authorize! :update_cluster, cluster
+
+ update_service = Clusters::UpdateService.new(current_user, update_cluster_params)
+
+ if update_service.execute(cluster)
+ present cluster, with: Entities::ClusterProject
+ else
+ render_validation_error!(cluster)
+ end
+ end
+
+ desc 'Remove a cluster' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::ClusterProject
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The Cluster ID'
+ end
+ delete ':id/clusters/:cluster_id' do
+ authorize! :admin_cluster, cluster
+
+ destroy_conditionally!(cluster)
+ end
+ end
+
+ helpers do
+ def clusters_for_current_user
+ @clusters_for_current_user ||= ClustersFinder.new(user_project, current_user, :all).execute
+ end
+
+ def cluster
+ @cluster ||= clusters_for_current_user.find(params[:cluster_id])
+ end
+
+ def create_cluster_user_params
+ declared_params.merge({
+ provider_type: :user,
+ platform_type: :kubernetes,
+ clusterable: user_project
+ })
+ end
+
+ def update_cluster_params
+ declared_params(include_missing: false).without(:cluster_id)
+ end
+ end
+ end
+end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index f5d21d8923f..9f3a1699146 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -475,7 +475,7 @@ module API
requires :file, type: File, desc: 'The file to be uploaded'
end
post ":id/uploads" do
- UploadService.new(user_project, params[:file]).execute
+ UploadService.new(user_project, params[:file]).execute.to_h
end
desc 'Get the users list of a project' do
diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb
index 8edcfea7c93..263468c9aa6 100644
--- a/lib/api/projects_relation_builder.rb
+++ b/lib/api/projects_relation_builder.rb
@@ -11,7 +11,7 @@ module API
projects_relation
end
- def preload_relation(projects_relation, options = {})
+ def preload_relation(projects_relation, options = {})
projects_relation
end
diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb
new file mode 100644
index 00000000000..e3072684ef7
--- /dev/null
+++ b/lib/api/release/links.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+module API
+ module Release
+ class Links < Grape::API
+ include PaginationParams
+
+ RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
+ .merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ params do
+ requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
+ end
+ resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
+ resource :assets do
+ desc 'Get a list of links of a release' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::Releases::Link
+ end
+ params do
+ use :pagination
+ end
+ get 'links' do
+ authorize! :read_release, release
+
+ present paginate(release.links.sorted), with: Entities::Releases::Link
+ end
+
+ desc 'Create a link of a release' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::Releases::Link
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the link'
+ requires :url, type: String, desc: 'The URL of the link'
+ end
+ post 'links' do
+ authorize! :create_release, release
+
+ new_link = release.links.create(declared_params(include_missing: false))
+
+ if new_link.persisted?
+ present new_link, with: Entities::Releases::Link
+ else
+ render_api_error!(new_link.errors.messages, 400)
+ end
+ end
+
+ params do
+ requires :link_id, type: String, desc: 'The id of the link'
+ end
+ resource 'links/:link_id' do
+ desc 'Get a link detail of a release' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::Releases::Link
+ end
+ get do
+ authorize! :read_release, release
+
+ present link, with: Entities::Releases::Link
+ end
+
+ desc 'Update a link of a release' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::Releases::Link
+ end
+ params do
+ optional :name, type: String, desc: 'The name of the link'
+ optional :url, type: String, desc: 'The URL of the link'
+ at_least_one_of :name, :url
+ end
+ put do
+ authorize! :update_release, release
+
+ if link.update(declared_params(include_missing: false))
+ present link, with: Entities::Releases::Link
+ else
+ render_api_error!(link.errors.messages, 400)
+ end
+ end
+
+ desc 'Delete a link of a release' do
+ detail 'This feature was introduced in GitLab 11.7.'
+ success Entities::Releases::Link
+ end
+ delete do
+ authorize! :destroy_release, release
+
+ if link.destroy
+ present link, with: Entities::Releases::Link
+ else
+ render_api_error!(link.errors.messages, 400)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ helpers do
+ def release
+ @release ||= user_project.releases.find_by_tag!(params[:tag])
+ end
+
+ def link
+ @link ||= release.links.find(params[:link_id])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 37d06988e64..cb85028f22c 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -7,7 +7,6 @@ module API
RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
- before { error!('404 Not Found', 404) unless Feature.enabled?(:releases_page, user_project) }
before { authorize_read_releases! }
params do
@@ -49,6 +48,12 @@ module API
requires :name, type: String, desc: 'The name of the release'
requires :description, type: String, desc: 'The release notes'
optional :ref, type: String, desc: 'The commit sha or branch name'
+ optional :assets, type: Hash do
+ optional :links, type: Array do
+ requires :name, type: String
+ requires :url, type: String
+ end
+ end
end
post ':id/releases' do
authorize_create_release!
@@ -92,7 +97,7 @@ module API
success Entities::Release
end
params do
- requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
+ requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
end
delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
authorize_destroy_release!
diff --git a/lib/api/services.rb b/lib/api/services.rb
index d60f0f5f08d..637b5a8a89a 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -763,7 +763,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authenticate! }
before { authorize_admin_project }
@@ -842,7 +842,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc "Trigger a slash command for #{service_slug}" do
detail 'Added in GitLab 8.13'
end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index f53ba0ab761..95371961398 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -35,7 +35,7 @@ module API
end
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
- optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master'
+ optional :default_branch_protection, type: Integer, values: Gitlab::Access.protection_values, desc: 'Determine if developers can push to master'
optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index aacdca3871a..f5359fd316c 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -20,12 +20,15 @@ module API
desc: 'Return tags sorted in updated by `asc` or `desc` order.'
optional :order_by, type: String, values: %w[name updated], default: 'updated',
desc: 'Return tags ordered by `name` or `updated` fields.'
+ optional :search, type: String, desc: 'Return list of tags matching the search criteria'
use :pagination
end
get ':id/repository/tags' do
- tags = ::Kaminari.paginate_array(::TagsFinder.new(user_project.repository, sort: "#{params[:order_by]}_#{params[:sort]}").execute)
+ tags = ::TagsFinder.new(user_project.repository,
+ sort: "#{params[:order_by]}_#{params[:sort]}",
+ search: params[:search]).execute
- present paginate(tags), with: Entities::Tag, project: user_project
+ present paginate(::Kaminari.paginate_array(tags)), with: Entities::Tag, project: user_project
end
desc 'Get a single repository tag' do
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index d2c8cf7c1aa..64ac8ece56c 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -14,7 +14,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
ISSUABLE_TYPES.each do |type, finder|
type_id_str = "#{type.singularize}_iid".to_sym
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 3ce1529f259..604f989d8b3 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -7,7 +7,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Trigger a GitLab project pipeline' do
success Entities::Pipeline
end
@@ -59,7 +59,7 @@ module API
success Entities::Trigger
end
params do
- requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
get ':id/triggers/:trigger_id' do
authenticate!
@@ -75,7 +75,7 @@ module API
success Entities::Trigger
end
params do
- requires :description, type: String, desc: 'The trigger description'
+ requires :description, type: String, desc: 'The trigger description'
end
post ':id/triggers' do
authenticate!
@@ -116,7 +116,7 @@ module API
success Entities::Trigger
end
params do
- requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
post ':id/triggers/:trigger_id/take_ownership' do
authenticate!
@@ -137,7 +137,7 @@ module API
success Entities::Trigger
end
params do
- requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
delete ':id/triggers/:trigger_id' do
authenticate!
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index f7cae2251c2..148deb86c4c 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -11,7 +11,7 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get project variables' do
success Entities::Variable
end
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 302b2797a34..ef0e3decc2c 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -22,7 +22,9 @@ module API
end
end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ WIKI_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(slug: API::NO_SLASH_URL_PART_REGEX)
+
+ resource :projects, requirements: WIKI_ENDPOINT_REQUIREMENTS do
desc 'Get a list of wiki pages' do
success Entities::WikiPageBasic
end
@@ -103,7 +105,7 @@ module API
requires :file, type: ::API::Validations::Types::SafeFile, desc: 'The attachment file to be uploaded'
optional :branch, type: String, desc: 'The name of the branch'
end
- post ":id/wikis/attachments", requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ post ":id/wikis/attachments" do
authorize! :create_wiki, user_project
result = ::Wikis::CreateAttachmentService.new(user_project,
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index 0032ae8f84b..2bac84846c5 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -71,8 +71,14 @@ module Backup
end
def run_pipeline!(cmd_list, options = {})
- status_list = Open3.pipeline(*cmd_list, options)
- raise Backup::Error, 'Backup failed' unless status_list.compact.all?(&:success?)
+ err_r, err_w = IO.pipe
+ options[:err] = err_w
+ status = Open3.pipeline(*cmd_list, options)
+ err_w.close
+ return if status.compact.all?(&:success?)
+
+ regex = /^g?tar: \.: Cannot mkdir: No such file or directory$/
+ raise Backup::Error, 'Backup failed' unless err_r.read =~ regex
end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 0add2b3f875..06b0338b1ed 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -50,6 +50,7 @@ module Backup
if directory.files.create(key: remote_target, body: File.open(tar_file), public: false,
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
encryption: Gitlab.config.backup.upload.encryption,
+ encryption_key: Gitlab.config.backup.upload.encryption_key,
storage_class: Gitlab.config.backup.upload.storage_class)
progress.puts "done".color(:green)
else
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index c87948a30bf..fa1690f73ad 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/emoji.js
module Banzai
module Filter
# HTML filter that replaces :emoji: and unicode with images.
diff --git a/lib/banzai/filter/footnote_filter.rb b/lib/banzai/filter/footnote_filter.rb
new file mode 100644
index 00000000000..97527976437
--- /dev/null
+++ b/lib/banzai/filter/footnote_filter.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML Filter for footnotes
+ #
+ # Footnotes are supported in CommonMark. However we were stripping
+ # the ids during sanitization. Those are now allowed.
+ #
+ # Footnotes are numbered the same - the first one has `id=fn1`, the
+ # second is `id=fn2`, etc. In order to allow footnotes when rendering
+ # multiple markdown blocks on a page, we need to make each footnote
+ # reference unique.
+ #
+ # This filter adds a random number to each footnote (the same number
+ # can be used for a single render). So you get `id=fn1-4335` and `id=fn2-4335`.
+ #
+ class FootnoteFilter < HTML::Pipeline::Filter
+ INTEGER_PATTERN = /\A\d+\z/.freeze
+ FOOTNOTE_ID_PREFIX = 'fn'.freeze
+ FOOTNOTE_LINK_ID_PREFIX = 'fnref'.freeze
+ FOOTNOTE_LI_REFERENCE_PATTERN = /\A#{FOOTNOTE_ID_PREFIX}\d+\z/.freeze
+ FOOTNOTE_LINK_REFERENCE_PATTERN = /\A#{FOOTNOTE_LINK_ID_PREFIX}\d+\z/.freeze
+ FOOTNOTE_START_NUMBER = 1
+
+ def call
+ return doc unless first_footnote = doc.at_css("ol > li[id=#{fn_id(FOOTNOTE_START_NUMBER)}]")
+
+ # Sanitization stripped off the section wrapper - add it back in
+ first_footnote.parent.wrap('<section class="footnotes">')
+ rand_suffix = "-#{random_number}"
+
+ doc.css('sup > a[id]').each do |link_node|
+ ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
+ footnote_node = doc.at_css("li[id=#{fn_id(ref_num)}]")
+ backref_node = footnote_node.at_css("a[href=\"##{fnref_id(ref_num)}\"]")
+
+ if ref_num =~ INTEGER_PATTERN && footnote_node && backref_node
+ link_node[:href] += rand_suffix
+ link_node[:id] += rand_suffix
+ footnote_node[:id] += rand_suffix
+ backref_node[:href] += rand_suffix
+
+ # Sanitization stripped off class - add it back in
+ link_node.parent.append_class('footnote-ref')
+ backref_node.append_class('footnote-backref')
+ end
+ end
+
+ doc
+ end
+
+ private
+
+ def random_number
+ @random_number ||= rand(10000)
+ end
+
+ def fn_id(num)
+ "#{FOOTNOTE_ID_PREFIX}#{num}"
+ end
+
+ def fnref_id(num)
+ "#{FOOTNOTE_LINK_ID_PREFIX}#{num}"
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/image_lazy_load_filter.rb b/lib/banzai/filter/image_lazy_load_filter.rb
index afaee70f351..d8b9eb29cf5 100644
--- a/lib/banzai/filter/image_lazy_load_filter.rb
+++ b/lib/banzai/filter/image_lazy_load_filter.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/image.js
module Banzai
module Filter
# HTML filter that moves the value of image `src` attributes to `data-src`
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
index 884a94fb761..01237303c27 100644
--- a/lib/banzai/filter/image_link_filter.rb
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/image.js
module Banzai
module Filter
# HTML filter that wraps links around inline images.
diff --git a/lib/banzai/filter/inline_diff_filter.rb b/lib/banzai/filter/inline_diff_filter.rb
index e9ddc6e0e3d..5a1c0bee32d 100644
--- a/lib/banzai/filter/inline_diff_filter.rb
+++ b/lib/banzai/filter/inline_diff_filter.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/marks/inline_diff.js
module Banzai
module Filter
class InlineDiffFilter < HTML::Pipeline::Filter
diff --git a/lib/banzai/filter/math_filter.rb b/lib/banzai/filter/math_filter.rb
index 9d1bc3cf60c..8dd5a8979c8 100644
--- a/lib/banzai/filter/math_filter.rb
+++ b/lib/banzai/filter/math_filter.rb
@@ -2,6 +2,9 @@
require 'uri'
+# Generated HTML is transformed back to GFM by:
+# - app/assets/javascripts/behaviors/markdown/marks/math.js
+# - app/assets/javascripts/behaviors/markdown/nodes/code_block.js
module Banzai
module Filter
# HTML filter that adds class="code math" and removes the dollar sign in $`2+2`$.
diff --git a/lib/banzai/filter/mermaid_filter.rb b/lib/banzai/filter/mermaid_filter.rb
index 7c8b165a330..f0adb83af8a 100644
--- a/lib/banzai/filter/mermaid_filter.rb
+++ b/lib/banzai/filter/mermaid_filter.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/code_block.js
module Banzai
module Filter
class MermaidFilter < HTML::Pipeline::Filter
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index c70c3f0c04e..fce042e8946 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -101,9 +101,9 @@ module Banzai
def self_and_ancestors_ids(parent)
if group_context?(parent)
- parent.self_and_ancestors_ids
+ parent.self_and_ancestors.select(:id)
elsif project_context?(parent)
- parent.group&.self_and_ancestors_ids
+ parent.group&.self_and_ancestors&.select(:id)
end
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index e5164e7f72a..42f9b3a689c 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/reference.js
module Banzai
module Filter
# Base class for GitLab Flavored Markdown reference filters.
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 7acbc933adc..93e6d6470f1 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -58,6 +58,8 @@ module Banzai
path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
elsif project
path_parts.unshift(relative_url_root, project.full_path)
+ else
+ path_parts.unshift(relative_url_root)
end
begin
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 8ba09290e6d..edc053638a8 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -8,8 +8,8 @@ module Banzai
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
include Gitlab::Utils::StrongMemoize
- UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
- TABLE_ALIGNMENT_PATTERN = /text-align: (?<alignment>center|left|right)/
+ UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
+ TABLE_ALIGNMENT_PATTERN = /text-align: (?<alignment>center|left|right)/.freeze
def whitelist
strong_memoize(:whitelist) do
@@ -45,10 +45,9 @@ module Banzai
whitelist[:attributes][:all].delete('name')
whitelist[:attributes]['a'].push('name')
- # Allow any protocol in `a` elements...
+ # Allow any protocol in `a` elements
+ # and then remove links with unsafe protocols
whitelist[:protocols].delete('a')
-
- # ...but then remove links with unsafe protocols
whitelist[:transformers].push(self.class.remove_unsafe_links)
# Remove `rel` attribute from `a` elements
@@ -57,6 +56,12 @@ module Banzai
# Remove any `style` properties not required for table alignment
whitelist[:transformers].push(self.class.remove_unsafe_table_style)
+ # Allow `id` in a and li elements for footnotes
+ # and remove any `id` properties not matching for footnotes
+ whitelist[:attributes]['a'].push('id')
+ whitelist[:attributes]['li'] = %w(id)
+ whitelist[:transformers].push(self.class.remove_non_footnote_ids)
+
whitelist
end
@@ -112,6 +117,20 @@ module Banzai
end
end
end
+
+ def remove_non_footnote_ids
+ lambda do |env|
+ node = env[:node]
+
+ return unless node.name == 'a' || node.name == 'li'
+ return unless node.has_attribute?('id')
+
+ return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN
+ return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN
+
+ node.remove_attribute('id')
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 18e5e9185de..bcf77861f10 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -3,6 +3,7 @@
require 'rouge/plugins/common_mark'
require 'rouge/plugins/redcarpet'
+# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/code_block.js
module Banzai
module Filter
# HTML Filter to highlight fenced code blocks
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index c6d1e028eaa..f2ae17b44fa 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/table_of_contents.js
module Banzai
module Filter
# HTML filter that adds an anchor child element to all Headers in a
diff --git a/lib/banzai/filter/task_list_filter.rb b/lib/banzai/filter/task_list_filter.rb
index ef35a49edcb..c6b402575cb 100644
--- a/lib/banzai/filter/task_list_filter.rb
+++ b/lib/banzai/filter/task_list_filter.rb
@@ -2,6 +2,10 @@
require 'task_list/filter'
+# Generated HTML is transformed back to GFM by:
+# - app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js
+# - app/assets/javascripts/behaviors/markdown/nodes/task_list.js
+# - app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js
module Banzai
module Filter
class TaskListFilter < TaskList::Filter
diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb
index 0fb59c914c3..0fff104cf91 100644
--- a/lib/banzai/filter/video_link_filter.rb
+++ b/lib/banzai/filter/video_link_filter.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/video.js
module Banzai
module Filter
# Find every image that isn't already wrapped in an `a` tag, and that has
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 5f13a6d6cde..30cafd11834 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -3,11 +3,11 @@
module Banzai
module Pipeline
class GfmPipeline < BasePipeline
- # These filters convert GitLab Flavored Markdown (GFM) to HTML.
- # The handlers defined in app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
- # consequently convert that same HTML to GFM to be copied to the clipboard.
- # Every filter that generates HTML from GFM should have a handler in
- # app/assets/javascripts/behaviors/markdown/copy_as_gfm.js, in reverse order.
+ # These filters transform GitLab Flavored Markdown (GFM) to HTML.
+ # The nodes and marks referenced in app/assets/javascripts/behaviors/markdown/editor_extensions.js
+ # consequently transform that same HTML to GFM to be copied to the clipboard.
+ # Every filter that generates HTML from GFM should have a node or mark in
+ # app/assets/javascripts/behaviors/markdown/editor_extensions.js.
# The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
def self.filters
@filters ||= FilterArray[
@@ -30,6 +30,7 @@ module Banzai
Filter::AutolinkFilter,
Filter::ExternalLinkFilter,
Filter::SuggestionFilter,
+ Filter::FootnoteFilter,
*reference_filters,
diff --git a/lib/bitbucket_server/client.rb b/lib/bitbucket_server/client.rb
index 83e8808db07..6a608058813 100644
--- a/lib/bitbucket_server/client.rb
+++ b/lib/bitbucket_server/client.rb
@@ -4,18 +4,6 @@ module BitbucketServer
class Client
attr_reader :connection
- ServerError = Class.new(StandardError)
-
- SERVER_ERRORS = [SocketError,
- OpenSSL::SSL::SSLError,
- Errno::ECONNRESET,
- Errno::ECONNREFUSED,
- Errno::EHOSTUNREACH,
- Net::OpenTimeout,
- Net::ReadTimeout,
- Gitlab::HTTP::BlockedUrlError,
- BitbucketServer::Connection::ConnectionError].freeze
-
def initialize(options = {})
@connection = Connection.new(options)
end
@@ -64,8 +52,6 @@ module BitbucketServer
def get_collection(path, type, page_offset: 0, limit: nil)
paginator = BitbucketServer::Paginator.new(connection, Addressable::URI.escape(path), type, page_offset: page_offset, limit: limit)
BitbucketServer::Collection.new(paginator)
- rescue *SERVER_ERRORS => e
- raise ServerError, e
end
end
end
diff --git a/lib/bitbucket_server/connection.rb b/lib/bitbucket_server/connection.rb
index 7efcdcf8619..9c14b26c65a 100644
--- a/lib/bitbucket_server/connection.rb
+++ b/lib/bitbucket_server/connection.rb
@@ -7,6 +7,17 @@ module BitbucketServer
DEFAULT_API_VERSION = '1.0'
SEPARATOR = '/'
+ NETWORK_ERRORS = [
+ SocketError,
+ OpenSSL::SSL::SSLError,
+ Errno::ECONNRESET,
+ Errno::ECONNREFUSED,
+ Errno::EHOSTUNREACH,
+ Net::OpenTimeout,
+ Net::ReadTimeout,
+ Gitlab::HTTP::BlockedUrlError
+ ].freeze
+
attr_reader :api_version, :base_uri, :username, :token
ConnectionError = Class.new(StandardError)
@@ -27,6 +38,8 @@ module BitbucketServer
check_errors!(response)
response.parsed_response
+ rescue *NETWORK_ERRORS => e
+ raise ConnectionError, e
end
def post(path, body)
@@ -38,6 +51,8 @@ module BitbucketServer
check_errors!(response)
response.parsed_response
+ rescue *NETWORK_ERRORS => e
+ raise ConnectionError, e
end
# We need to support two different APIs for deletion:
@@ -55,6 +70,8 @@ module BitbucketServer
check_errors!(response)
response.parsed_response
+ rescue *NETWORK_ERRORS => e
+ raise ConnectionError, e
end
private
diff --git a/lib/bitbucket_server/paginator.rb b/lib/bitbucket_server/paginator.rb
index aa5f84f44b3..9eda1c921b2 100644
--- a/lib/bitbucket_server/paginator.rb
+++ b/lib/bitbucket_server/paginator.rb
@@ -12,7 +12,7 @@ module BitbucketServer
@url = url
@page = nil
@page_offset = page_offset
- @limit = limit || PAGE_LENGTH
+ @limit = limit
@total = 0
end
@@ -34,6 +34,8 @@ module BitbucketServer
attr_reader :connection, :page, :url, :type, :limit
def over_limit?
+ return false unless @limit
+
@limit.positive? && @total >= @limit
end
@@ -42,11 +44,15 @@ module BitbucketServer
end
def starting_offset
- [0, page_offset - 1].max * limit
+ [0, page_offset - 1].max * max_per_page
+ end
+
+ def max_per_page
+ limit || PAGE_LENGTH
end
def fetch_next_page
- parsed_response = connection.get(@url, start: next_offset, limit: @limit)
+ parsed_response = connection.get(@url, start: next_offset, limit: max_per_page)
Page.new(parsed_response, type)
end
end
diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb
index 8633e764f90..ef41dc560c9 100644
--- a/lib/container_registry/tag.rb
+++ b/lib/container_registry/tag.rb
@@ -2,6 +2,8 @@
module ContainerRegistry
class Tag
+ include Gitlab::Utils::StrongMemoize
+
attr_reader :repository, :name
delegate :registry, :client, to: :repository
@@ -15,6 +17,10 @@ module ContainerRegistry
manifest.present?
end
+ def latest?
+ name == "latest"
+ end
+
def v1?
manifest && manifest['schemaVersion'] == 1
end
@@ -24,7 +30,9 @@ module ContainerRegistry
end
def manifest
- @manifest ||= client.repository_manifest(repository.path, name)
+ strong_memoize(:manifest) do
+ client.repository_manifest(repository.path, name)
+ end
end
def path
@@ -42,36 +50,44 @@ module ContainerRegistry
end
def digest
- @digest ||= client.repository_tag_digest(repository.path, name)
+ strong_memoize(:digest) do
+ client.repository_tag_digest(repository.path, name)
+ end
end
def config_blob
- return @config_blob if defined?(@config_blob)
return unless manifest && manifest['config']
- @config_blob = repository.blob(manifest['config'])
+ strong_memoize(:config_blob) do
+ repository.blob(manifest['config'])
+ end
end
def config
- return unless config_blob
+ return unless config_blob&.data
- @config ||= ContainerRegistry::Config.new(self, config_blob) if config_blob.data
+ strong_memoize(:config) do
+ ContainerRegistry::Config.new(self, config_blob)
+ end
end
def created_at
return unless config
- @created_at ||= DateTime.rfc3339(config['created'])
+ strong_memoize(:created_at) do
+ DateTime.rfc3339(config['created'])
+ end
end
def layers
- return @layers if defined?(@layers)
return unless manifest
- layers = manifest['layers'] || manifest['fsLayers']
+ strong_memoize(:layers) do
+ layers = manifest['layers'] || manifest['fsLayers']
- @layers = layers.map do |layer|
- repository.blob(layer)
+ layers.map do |layer|
+ repository.blob(layer)
+ end
end
end
diff --git a/lib/feature.rb b/lib/feature.rb
index e048a443abc..e59cd70f822 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -102,4 +102,42 @@ class Feature
expires_in: 1.hour)
end
end
+
+ class Target
+ attr_reader :params
+
+ def initialize(params)
+ @params = params
+ end
+
+ def gate_specified?
+ %i(user project feature_group).any? { |key| params.key?(key) }
+ end
+
+ def targets
+ [feature_group, user, project].compact
+ end
+
+ private
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def feature_group
+ return unless params.key?(:feature_group)
+
+ Feature.group(params[:feature_group])
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def user
+ return unless params.key?(:user)
+
+ UserFinder.new(params[:user]).find_by_username!
+ end
+
+ def project
+ return unless params.key?(:project)
+
+ Project.find_by_full_path(params[:project])
+ end
+ end
end
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index 2ef54658a11..b91394f7f58 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -27,12 +27,52 @@ module Gitlab
end
end
+ def self.version_info
+ Gitlab::VersionInfo.parse(Gitlab::VERSION)
+ end
+
COM_URL = 'https://gitlab.com'.freeze
APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}
SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}
VERSION = File.read(root.join("VERSION")).strip.freeze
INSTALLATION_TYPE = File.read(root.join("INSTALLATION_TYPE")).strip.freeze
+ def self.pre_release?
+ VERSION.include?('pre')
+ end
+
+ def self.final_release?
+ !VERSION.include?('rc') && !pre_release?
+ end
+
+ def self.minor_release
+ "#{version_info.major}.#{version_info.minor}"
+ end
+
+ def self.prev_minor_release
+ "#{version_info.major}.#{version_info.minor - 1}"
+ end
+
+ def self.prev_major_release
+ "#{version_info.major.to_i - 1}"
+ end
+
+ def self.new_major_release?
+ version_info.minor.to_i.zero?
+ end
+
+ def self.previous_release
+ if version_info.minor_version?
+ if version_info.patch_version?
+ minor_release
+ else
+ prev_minor_release
+ end
+ else
+ prev_major_release
+ end
+ end
+
def self.com?
# Check `gl_subdomain?` as well to keep parity with gitlab.com
Gitlab.config.gitlab.url == COM_URL || gl_subdomain?
@@ -50,11 +90,11 @@ module Gitlab
Rails.env.development? || org? || com?
end
- def self.pre_release?
- VERSION.include?('pre')
- end
+ def self.process_name
+ return 'sidekiq' if Sidekiq.server?
+ return 'console' if defined?(Rails::Console)
+ return 'test' if Rails.env.test?
- def self.version_info
- Gitlab::VersionInfo.parse(Gitlab::VERSION)
+ 'web'
end
end
diff --git a/lib/gitlab/access/branch_protection.rb b/lib/gitlab/access/branch_protection.rb
new file mode 100644
index 00000000000..f039e5c011f
--- /dev/null
+++ b/lib/gitlab/access/branch_protection.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Access
+ # A wrapper around Integer based branch protection levels.
+ #
+ # This wrapper can be used to work with branch protection levels without
+ # having to directly refer to the constants. For example, instead of this:
+ #
+ # if access_level == Gitlab::Access::PROTECTION_DEV_CAN_PUSH
+ # ...
+ # end
+ #
+ # You can write this instead:
+ #
+ # protection = BranchProtection.new(access_level)
+ #
+ # if protection.developer_can_push?
+ # ...
+ # end
+ class BranchProtection
+ attr_reader :level
+
+ # @param [Integer] level The branch protection level as an Integer.
+ def initialize(level)
+ @level = level
+ end
+
+ def any?
+ level != PROTECTION_NONE
+ end
+
+ def developer_can_push?
+ level == PROTECTION_DEV_CAN_PUSH
+ end
+
+ def developer_can_merge?
+ level == PROTECTION_DEV_CAN_MERGE
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index a0244a3cea1..48d134f91b0 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -98,9 +98,7 @@ module Gitlab
private
- def entry
- @entry
- end
+ attr_reader :entry
def config
@config ||= Gitlab::Auth::LDAP::Config.new(provider)
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index a4e8a41b246..f38c5d57c44 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -46,7 +46,7 @@ module Gitlab
gl_user.block if block_after_save
- log.info "(#{provider}) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
+ log.info "(#{provider}) saving user #{auth_hash.email} from login with admin => #{gl_user.admin}, extern_uid => #{auth_hash.uid}"
gl_user
rescue ActiveRecord::RecordInvalid => e
log.info "(#{provider}) Error saving user #{auth_hash.uid} (#{auth_hash.email}): #{gl_user.errors.full_messages}"
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index d72befce571..6cf40e2d4ca 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -51,6 +51,19 @@ module Gitlab
migration_class_for(class_name).new.perform(*arguments)
end
+ def self.exists?(migration_class)
+ enqueued = Sidekiq::Queue.new(self.queue)
+ scheduled = Sidekiq::ScheduledSet.new
+
+ [enqueued, scheduled].each do |queue|
+ queue.each do |job|
+ return true if job.queue == self.queue && job.args.first == migration_class
+ end
+ end
+
+ false
+ end
+
def self.migration_class_for(class_name)
const_get(class_name)
end
diff --git a/lib/gitlab/background_migration/backfill_project_repositories.rb b/lib/gitlab/background_migration/backfill_project_repositories.rb
index aaf520d70f6..c8d83cc1803 100644
--- a/lib/gitlab/background_migration/backfill_project_repositories.rb
+++ b/lib/gitlab/background_migration/backfill_project_repositories.rb
@@ -83,7 +83,7 @@ module Gitlab
extend ActiveSupport::Concern
def full_path
- @full_path ||= build_full_path
+ route&.path || build_full_path
end
def build_full_path
@@ -99,7 +99,12 @@ module Gitlab
end
end
- # Namespace model.
+ # Route model
+ class Route < ActiveRecord::Base
+ belongs_to :source, inverse_of: :route, polymorphic: true
+ end
+
+ # Namespace model
class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
self.inheritance_column = nil
@@ -108,6 +113,8 @@ module Gitlab
belongs_to :parent, class_name: 'Namespace', inverse_of: 'namespaces'
+ has_one :route, -> { where(source_type: 'Namespace') }, inverse_of: :source, foreign_key: :source_id
+
has_many :projects, inverse_of: :parent
has_many :namespaces, inverse_of: :parent
end
@@ -134,6 +141,7 @@ module Gitlab
belongs_to :parent, class_name: 'Namespace', foreign_key: :namespace_id, inverse_of: 'projects'
+ has_one :route, -> { where(source_type: 'Project') }, inverse_of: :source, foreign_key: :source_id
has_one :project_repository, inverse_of: :project
delegate :disk_path, to: :storage
@@ -194,6 +202,8 @@ module Gitlab
def project_repositories(start_id, stop_id)
projects
.without_project_repository
+ .includes(:route, parent: [:route]).references(:routes)
+ .includes(:parent).references(:namespaces)
.where(id: start_id..stop_id)
.map { |project| build_attributes_for_project(project) }
.compact
diff --git a/lib/gitlab/background_migration/migrate_stage_status.rb b/lib/gitlab/background_migration/migrate_stage_status.rb
index 0e5c7f092f2..6a29a632577 100644
--- a/lib/gitlab/background_migration/migrate_stage_status.rb
+++ b/lib/gitlab/background_migration/migrate_stage_status.rb
@@ -16,10 +16,10 @@ module Gitlab
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
scope :success, -> { where(status: 'success') }
- scope :failed, -> { where(status: 'failed') }
- scope :canceled, -> { where(status: 'canceled') }
- scope :skipped, -> { where(status: 'skipped') }
- scope :manual, -> { where(status: 'manual') }
+ scope :failed, -> { where(status: 'failed') }
+ scope :canceled, -> { where(status: 'canceled') }
+ scope :skipped, -> { where(status: 'skipped') }
+ scope :manual, -> { where(status: 'manual') }
scope :failed_but_allowed, -> do
where(allow_failure: true, status: [:failed, :canceled])
diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb
index 28cfb46e2d4..dbbedd5dcbe 100644
--- a/lib/gitlab/bitbucket_server_import/importer.rb
+++ b/lib/gitlab/bitbucket_server_import/importer.rb
@@ -132,7 +132,7 @@ module Gitlab
project.repository.fetch_as_mirror(project.import_url, refmap: self.class.refmap, remote_name: REMOTE_NAME)
log_info(stage: 'import_repository', message: 'finished import')
- rescue Gitlab::Shell::Error, Gitlab::Git::RepositoryMirroring::RemoteError => e
+ rescue Gitlab::Shell::Error => e
log_error(stage: 'import_repository', message: 'failed import', error: e.message)
# Expire cache to prevent scenarios such as:
@@ -140,7 +140,7 @@ module Gitlab
# 2. Retried import, repo is broken or not imported but +exists?+ still returns true
project.repository.expire_content_cache if project.repository_exists?
- raise e.message
+ raise
end
# Bitbucket Server keeps tracks of references for open pull requests in
diff --git a/lib/gitlab/checks/base_checker.rb b/lib/gitlab/checks/base_checker.rb
index 7fbcf6a4ff4..09b17b5b76b 100644
--- a/lib/gitlab/checks/base_checker.rb
+++ b/lib/gitlab/checks/base_checker.rb
@@ -18,12 +18,16 @@ module Gitlab
private
+ def creation?
+ Gitlab::Git.blank_ref?(oldrev)
+ end
+
def deletion?
Gitlab::Git.blank_ref?(newrev)
end
def update?
- !Gitlab::Git.blank_ref?(oldrev) && !deletion?
+ !creation? && !deletion?
end
def updated_from_web?
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 7778d3068cc..8a57a3a6d9a 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -10,7 +10,7 @@ module Gitlab
attr_reader(*ATTRIBUTES)
def initialize(
- change, user_access:, project:, skip_authorization: false,
+ change, user_access:, project:,
skip_lfs_integrity_check: false, protocol:, logger:
)
@oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
@@ -18,7 +18,6 @@ module Gitlab
@tag_name = Gitlab::Git.tag_name(@ref)
@user_access = user_access
@project = project
- @skip_authorization = skip_authorization
@skip_lfs_integrity_check = skip_lfs_integrity_check
@protocol = protocol
@@ -27,8 +26,6 @@ module Gitlab
end
def exec
- return true if skip_authorization
-
ref_level_checks
# Check of commits should happen as the last step
# given they're expensive in terms of performance
diff --git a/lib/gitlab/checks/diff_check.rb b/lib/gitlab/checks/diff_check.rb
index 63da9a3d6b5..ea0d8c85a66 100644
--- a/lib/gitlab/checks/diff_check.rb
+++ b/lib/gitlab/checks/diff_check.rb
@@ -11,7 +11,7 @@ module Gitlab
}.freeze
def validate!
- return if deletion? || newrev.nil?
+ return if deletion?
return unless should_run_diff_validations?
return if commits.empty?
diff --git a/lib/gitlab/checks/push_check.rb b/lib/gitlab/checks/push_check.rb
index f3a52f09868..91f8d0bdbc8 100644
--- a/lib/gitlab/checks/push_check.rb
+++ b/lib/gitlab/checks/push_check.rb
@@ -6,7 +6,7 @@ module Gitlab
def validate!
logger.log_timed("Checking if you are allowed to push...") do
unless can_push?
- raise GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.'
+ raise GitAccess::UnauthorizedError, GitAccess::ERROR_MESSAGES[:push_code]
end
end
end
@@ -15,7 +15,7 @@ module Gitlab
def can_push?
user_access.can_do_action?(:push_code) ||
- user_access.can_push_to_branch?(branch_name)
+ project.branch_allows_collaboration?(user_access.user, branch_name)
end
end
end
diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb
index d587c896712..7fcabc035ac 100644
--- a/lib/gitlab/ci/build/step.rb
+++ b/lib/gitlab/ci/build/step.rb
@@ -15,7 +15,6 @@ module Gitlab
def from_commands(job)
self.new(:script).tap do |step|
step.script = job.options[:before_script].to_a + job.options[:script].to_a
- step.script = job.commands.split("\n") if step.script.empty?
step.timeout = job.metadata_timeout
step.when = WHEN_ON_SUCCESS
end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index 6333799a491..5875479183e 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -8,9 +8,9 @@ module Gitlab
class Config
ConfigError = Class.new(StandardError)
- def initialize(config, opts = {})
+ def initialize(config, project: nil, sha: nil, user: nil)
@config = Config::Extendable
- .new(build_config(config, opts))
+ .new(build_config(config, project: project, sha: sha, user: user))
.to_hash
@global = Entry::Global.new(@config)
@@ -70,20 +70,21 @@ module Gitlab
private
- def build_config(config, opts = {})
+ def build_config(config, project:, sha:, user:)
initial_config = Gitlab::Config::Loader::Yaml.new(config).load!
- project = opts.fetch(:project, nil)
if project
- process_external_files(initial_config, project, opts)
+ process_external_files(initial_config, project: project, sha: sha, user: user)
else
initial_config
end
end
- def process_external_files(config, project, opts)
- sha = opts.fetch(:sha) { project.repository.root_ref_sha }
- Config::External::Processor.new(config, project, sha).perform
+ def process_external_files(config, project:, sha:, user:)
+ Config::External::Processor.new(config,
+ project: project,
+ sha: sha || project.repository.root_ref_sha,
+ user: user).perform
end
end
end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 50942fbdb40..290c9591b98 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -16,13 +16,6 @@ module Gitlab
dependencies before_script after_script variables
environment coverage retry parallel extends].freeze
- DEFAULT_ONLY_POLICY = {
- refs: %w(branches tags)
- }.freeze
-
- DEFAULT_EXCEPT_POLICY = {
- }.freeze
-
validations do
validates :config, allowed_keys: ALLOWED_KEYS
validates :config, presence: true
@@ -73,7 +66,8 @@ module Gitlab
description: 'Services that will be used to execute this job.'
entry :only, Entry::Policy,
- description: 'Refs policy this job will be executed for.'
+ description: 'Refs policy this job will be executed for.',
+ default: Entry::Policy::DEFAULT_ONLY
entry :except, Entry::Policy,
description: 'Refs policy this job will be executed for.'
@@ -95,7 +89,7 @@ module Gitlab
helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables,
- :artifacts, :commands, :environment, :coverage, :retry,
+ :artifacts, :environment, :coverage, :retry,
:parallel
attributes :script, :tags, :allow_failure, :when, :dependencies,
@@ -121,10 +115,6 @@ module Gitlab
@config.merge(to_hash.compact)
end
- def commands
- (before_script_value.to_a + script_value.to_a).join("\n")
- end
-
def manual_action?
self.when == 'manual'
end
@@ -156,13 +146,12 @@ module Gitlab
{ name: name,
before_script: before_script_value,
script: script_value,
- commands: commands,
image: image_value,
services: services_value,
stage: stage_value,
cache: cache_value,
- only: DEFAULT_ONLY_POLICY.deep_merge(only_value.to_h),
- except: DEFAULT_EXCEPT_POLICY.deep_merge(except_value.to_h),
+ only: only_value,
+ except: except_value,
variables: variables_defined? ? variables_value : nil,
environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil,
diff --git a/lib/gitlab/ci/config/entry/jobs.rb b/lib/gitlab/ci/config/entry/jobs.rb
index 82b72e40404..9845c4af655 100644
--- a/lib/gitlab/ci/config/entry/jobs.rb
+++ b/lib/gitlab/ci/config/entry/jobs.rb
@@ -28,11 +28,15 @@ module Gitlab
name.to_s.start_with?('.')
end
+ def node_type(name)
+ hidden?(name) ? Entry::Hidden : Entry::Job
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def compose!(deps = nil)
super do
@config.each do |name, config|
- node = hidden?(name) ? Entry::Hidden : Entry::Job
+ node = node_type(name)
factory = ::Gitlab::Config::Entry::Factory.new(node)
.value(config || {})
diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb
index 998da1f6837..adc3660d950 100644
--- a/lib/gitlab/ci/config/entry/policy.rb
+++ b/lib/gitlab/ci/config/entry/policy.rb
@@ -11,6 +11,8 @@ module Gitlab
strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) }
strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) }
+ DEFAULT_ONLY = { refs: %w[branches tags] }.freeze
+
class RefsPolicy < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
@@ -64,7 +66,8 @@ module Gitlab
end
end
- def self.default
+ def value
+ default.to_h.deep_merge(subject.value.to_h)
end
end
end
diff --git a/lib/gitlab/ci/config/entry/retry.rb b/lib/gitlab/ci/config/entry/retry.rb
index eaf8b38aa3c..e9cbcb31e21 100644
--- a/lib/gitlab/ci/config/entry/retry.rb
+++ b/lib/gitlab/ci/config/entry/retry.rb
@@ -82,9 +82,6 @@ module Gitlab
'retry config'
end
end
-
- def self.default
- end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/variables.rb b/lib/gitlab/ci/config/entry/variables.rb
index 89d790ebfa6..c9d0c7cb568 100644
--- a/lib/gitlab/ci/config/entry/variables.rb
+++ b/lib/gitlab/ci/config/entry/variables.rb
@@ -14,7 +14,7 @@ module Gitlab
validates :config, variables: true
end
- def self.default
+ def self.default(**)
{}
end
diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb
index ee4ea9bbb1d..a747886093c 100644
--- a/lib/gitlab/ci/config/external/file/base.rb
+++ b/lib/gitlab/ci/config/external/file/base.rb
@@ -8,20 +8,26 @@ module Gitlab
class Base
include Gitlab::Utils::StrongMemoize
- attr_reader :location, :opts, :errors
+ attr_reader :location, :params, :context, :errors
YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze
- def initialize(location, opts = {})
- @location = location
- @opts = opts
+ Context = Struct.new(:project, :sha, :user)
+
+ def initialize(params, context)
+ @params = params
+ @context = context
@errors = []
validate!
end
+ def matching?
+ location.present?
+ end
+
def invalid_extension?
- !::File.basename(location).match(YAML_WHITELIST_EXTENSION)
+ location.nil? || !::File.basename(location).match?(YAML_WHITELIST_EXTENSION)
end
def valid?
diff --git a/lib/gitlab/ci/config/external/file/local.rb b/lib/gitlab/ci/config/external/file/local.rb
index 2a256aff65c..2535d178ba8 100644
--- a/lib/gitlab/ci/config/external/file/local.rb
+++ b/lib/gitlab/ci/config/external/file/local.rb
@@ -8,11 +8,8 @@ module Gitlab
class Local < Base
include Gitlab::Utils::StrongMemoize
- attr_reader :project, :sha
-
- def initialize(location, opts = {})
- @project = opts.fetch(:project)
- @sha = opts.fetch(:sha)
+ def initialize(params, context)
+ @location = params[:local]
super
end
@@ -32,7 +29,7 @@ module Gitlab
end
def fetch_local_content
- project.repository.blob_data_at(sha, location)
+ context.project.repository.blob_data_at(context.sha, location)
end
end
end
diff --git a/lib/gitlab/ci/config/external/file/project.rb b/lib/gitlab/ci/config/external/file/project.rb
new file mode 100644
index 00000000000..e75540dbe5a
--- /dev/null
+++ b/lib/gitlab/ci/config/external/file/project.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ module File
+ class Project < Base
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :project_name, :ref_name
+
+ def initialize(params, context = {})
+ @location = params[:file]
+ @project_name = params[:project]
+ @ref_name = params[:ref] || 'HEAD'
+
+ super
+ end
+
+ def matching?
+ super && project_name.present?
+ end
+
+ def content
+ strong_memoize(:content) { fetch_local_content }
+ end
+
+ private
+
+ def validate_content!
+ if !can_access_local_content?
+ errors.push("Project `#{project_name}` not found or access denied!")
+ elsif sha.nil?
+ errors.push("Project `#{project_name}` reference `#{ref_name}` does not exist!")
+ elsif content.nil?
+ errors.push("Project `#{project_name}` file `#{location}` does not exist!")
+ elsif content.blank?
+ errors.push("Project `#{project_name}` file `#{location}` is empty!")
+ end
+ end
+
+ def project
+ strong_memoize(:project) do
+ ::Project.find_by_full_path(project_name)
+ end
+ end
+
+ def can_access_local_content?
+ Ability.allowed?(context.user, :download_code, project)
+ end
+
+ def fetch_local_content
+ return unless can_access_local_content?
+ return unless sha
+
+ project.repository.blob_data_at(sha, location)
+ rescue GRPC::NotFound, GRPC::Internal
+ nil
+ end
+
+ def sha
+ strong_memoize(:sha) do
+ project.commit(ref_name).try(:sha)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/file/remote.rb b/lib/gitlab/ci/config/external/file/remote.rb
index 86fa5ad8800..567a86c47e5 100644
--- a/lib/gitlab/ci/config/external/file/remote.rb
+++ b/lib/gitlab/ci/config/external/file/remote.rb
@@ -8,6 +8,12 @@ module Gitlab
class Remote < Base
include Gitlab::Utils::StrongMemoize
+ def initialize(params, context)
+ @location = params[:remote]
+
+ super
+ end
+
def content
strong_memoize(:content) { fetch_remote_content }
end
diff --git a/lib/gitlab/ci/config/external/file/template.rb b/lib/gitlab/ci/config/external/file/template.rb
new file mode 100644
index 00000000000..54f4cf74c4d
--- /dev/null
+++ b/lib/gitlab/ci/config/external/file/template.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ module File
+ class Template < Base
+ attr_reader :location, :project
+
+ SUFFIX = '.gitlab-ci.yml'.freeze
+
+ def initialize(params, context)
+ @location = params[:template]
+
+ super
+ end
+
+ def content
+ strong_memoize(:content) { fetch_template_content }
+ end
+
+ private
+
+ def validate_location!
+ super
+
+ unless template_name_valid?
+ errors.push("Template file `#{location}` is not a valid location!")
+ end
+ end
+
+ def template_name
+ return unless template_name_valid?
+
+ location.first(-SUFFIX.length)
+ end
+
+ def template_name_valid?
+ location.to_s.end_with?(SUFFIX)
+ end
+
+ def fetch_template_content
+ Gitlab::Template::GitlabCiYmlTemplate.find(template_name, project)&.content
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb
index def3563e505..108bfd5eb43 100644
--- a/lib/gitlab/ci/config/external/mapper.rb
+++ b/lib/gitlab/ci/config/external/mapper.rb
@@ -5,25 +5,65 @@ module Gitlab
class Config
module External
class Mapper
- def initialize(values, project, sha)
- @locations = Array(values.fetch(:include, []))
+ include Gitlab::Utils::StrongMemoize
+
+ FILE_CLASSES = [
+ External::File::Remote,
+ External::File::Template,
+ External::File::Local,
+ External::File::Project
+ ].freeze
+
+ AmbigiousSpecificationError = Class.new(StandardError)
+
+ def initialize(values, project:, sha:, user:)
+ @locations = Array.wrap(values.fetch(:include, []))
@project = project
@sha = sha
+ @user = user
end
def process
- locations.map { |location| build_external_file(location) }
+ locations
+ .compact
+ .map(&method(:normalize_location))
+ .map(&method(:select_first_matching))
end
private
- attr_reader :locations, :project, :sha
+ attr_reader :locations, :project, :sha, :user
+
+ # convert location if String to canonical form
+ def normalize_location(location)
+ if location.is_a?(String)
+ normalize_location_string(location)
+ else
+ location.deep_symbolize_keys
+ end
+ end
- def build_external_file(location)
+ def normalize_location_string(location)
if ::Gitlab::UrlSanitizer.valid?(location)
- External::File::Remote.new(location)
+ { remote: location }
else
- External::File::Local.new(location, project: project, sha: sha)
+ { local: location }
+ end
+ end
+
+ def select_first_matching(location)
+ matching = FILE_CLASSES.map do |file_class|
+ file_class.new(location, context)
+ end.select(&:matching?)
+
+ raise AmbigiousSpecificationError, "Include `#{location.to_json}` needs to match exactly one accessor!" unless matching.one?
+
+ matching.first
+ end
+
+ def context
+ strong_memoize(:context) do
+ External::File::Base::Context.new(project, sha, user)
end
end
end
diff --git a/lib/gitlab/ci/config/external/processor.rb b/lib/gitlab/ci/config/external/processor.rb
index eae0bdeb644..69bc164a039 100644
--- a/lib/gitlab/ci/config/external/processor.rb
+++ b/lib/gitlab/ci/config/external/processor.rb
@@ -7,10 +7,12 @@ module Gitlab
class Processor
IncludeError = Class.new(StandardError)
- def initialize(values, project, sha)
+ def initialize(values, project:, sha:, user:)
@values = values
- @external_files = External::Mapper.new(values, project, sha).process
+ @external_files = External::Mapper.new(values, project: project, sha: sha, user: user).process
@content = {}
+ rescue External::Mapper::AmbigiousSpecificationError => e
+ raise IncludeError, e.message
end
def perform
diff --git a/lib/gitlab/ci/config/normalizer.rb b/lib/gitlab/ci/config/normalizer.rb
index b7743bd2090..191f5d09645 100644
--- a/lib/gitlab/ci/config/normalizer.rb
+++ b/lib/gitlab/ci/config/normalizer.rb
@@ -46,7 +46,8 @@ module Gitlab
parallelized_job_names = @parallelized_jobs.keys.map(&:to_s)
parallelized_config.each_with_object({}) do |(job_name, config), hash|
if config[:dependencies] && (intersection = config[:dependencies] & parallelized_job_names).any?
- deps = intersection.map { |dep| @parallelized_jobs[dep.to_sym].map(&:first) }.flatten
+ parallelized_deps = intersection.map { |dep| @parallelized_jobs[dep.to_sym].map(&:first) }.flatten
+ deps = config[:dependencies] - intersection + parallelized_deps
hash[job_name] = config.merge(dependencies: deps)
else
hash[job_name] = config
diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb
index b1db9084662..94f4a4e36c9 100644
--- a/lib/gitlab/ci/cron_parser.rb
+++ b/lib/gitlab/ci/cron_parser.rb
@@ -35,7 +35,7 @@ module Gitlab
# NOTE:
# cron_timezone can only accept timezones listed in TZInfo::Timezone.
# Aliases of Timezones from ActiveSupport::TimeZone are NOT accepted,
- # because Rufus::Scheduler only supports TZInfo::Timezone.
+ # because Fugit::Cron only supports TZInfo::Timezone.
#
# For example, those codes have the same effect.
# Time.zone = 'Pacific Time (US & Canada)' (ActiveSupport::TimeZone)
@@ -47,10 +47,7 @@ module Gitlab
# If you want to know more, please take a look
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb
def try_parse_cron(cron, cron_timezone)
- cron_line = Rufus::Scheduler.parse("#{cron} #{cron_timezone}")
- cron_line if cron_line.is_a?(Rufus::Scheduler::CronLine)
- rescue
- # noop
+ Fugit::Cron.parse("#{cron} #{cron_timezone}")
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index d33d1edfe35..41632211374 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -17,7 +17,6 @@ module Gitlab
user: @command.current_user,
pipeline_schedule: @command.schedule,
merge_request: @command.merge_request,
- protected: @command.protected_ref?,
variables_attributes: Array(@command.variables_attributes)
)
diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb
index 633d3cd4f6b..0405292a25b 100644
--- a/lib/gitlab/ci/pipeline/chain/populate.rb
+++ b/lib/gitlab/ci/pipeline/chain/populate.rb
@@ -13,6 +13,10 @@ module Gitlab
# Allocate next IID. This operation must be outside of transactions of pipeline creations.
pipeline.ensure_project_iid!
+ # Protect the pipeline. This is assigned in Populate instead of
+ # Build to prevent erroring out on ambiguous refs.
+ pipeline.protected = @command.protected_ref?
+
##
# Populate pipeline with block argument of CreatePipelineService#execute.
#
diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb
index ef738a93bfe..d8296940a04 100644
--- a/lib/gitlab/ci/pipeline/seed/build.rb
+++ b/lib/gitlab/ci/pipeline/seed/build.rb
@@ -38,9 +38,17 @@ module Gitlab
)
end
+ def bridge?
+ @attributes.to_h.dig(:options, :trigger).present?
+ end
+
def to_resource
strong_memoize(:resource) do
- ::Ci::Build.new(attributes)
+ if bridge?
+ ::Ci::Bridge.new(attributes)
+ else
+ ::Ci::Build.new(attributes)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/seed/stage.rb b/lib/gitlab/ci/pipeline/seed/stage.rb
index 4775ff15581..9c15064756a 100644
--- a/lib/gitlab/ci/pipeline/seed/stage.rb
+++ b/lib/gitlab/ci/pipeline/seed/stage.rb
@@ -39,7 +39,13 @@ module Gitlab
def to_resource
strong_memoize(:stage) do
::Ci::Stage.new(attributes).tap do |stage|
- seeds.each { |seed| stage.builds << seed.to_resource }
+ seeds.each do |seed|
+ if seed.bridge?
+ stage.bridges << seed.to_resource
+ else
+ stage.builds << seed.to_resource
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/status/bridge/common.rb b/lib/gitlab/ci/status/bridge/common.rb
index c6cb620f7a0..4746195c618 100644
--- a/lib/gitlab/ci/status/bridge/common.rb
+++ b/lib/gitlab/ci/status/bridge/common.rb
@@ -18,7 +18,6 @@ module Gitlab
end
def details_path
- raise NotImplementedError
end
end
end
diff --git a/lib/gitlab/ci/status/external/common.rb b/lib/gitlab/ci/status/external/common.rb
index 4169f5b3210..cd772819293 100644
--- a/lib/gitlab/ci/status/external/common.rb
+++ b/lib/gitlab/ci/status/external/common.rb
@@ -6,7 +6,7 @@ module Gitlab
module External
module Common
def label
- subject.description
+ subject.description.presence || super
end
def has_details?
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 8f6cf8d2d03..47e3e8cd271 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -49,8 +49,8 @@ variables:
POSTGRES_ENABLED: "true"
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
- KUBERNETES_VERSION: 1.10.9
- HELM_VERSION: 2.11.0
+ KUBERNETES_VERSION: 1.11.6
+ HELM_VERSION: 2.12.2
DOCKER_DRIVER: overlay2
@@ -596,10 +596,55 @@ rollout 100%:
fi
}
+ # Extracts variables prefixed with K8S_SECRET_
+ # and creates a Kubernetes secret.
+ #
+ # e.g. If we have the following environment variables:
+ # K8S_SECRET_A=value1
+ # K8S_SECRET_B=multi\ word\ value
+ #
+ # Then we will create a secret with the following key-value pairs:
+ # data:
+ # A: dmFsdWUxCg==
+ # B: bXVsdGkgd29yZCB2YWx1ZQo=
+ function create_application_secret() {
+ track="${1-stable}"
+ export APPLICATION_SECRET_NAME=$(application_secret_name "$track")
+
+ env | sed -n "s/^K8S_SECRET_\(.*\)$/\1/p" > k8s_prefixed_variables
+
+ kubectl create secret \
+ -n "$KUBE_NAMESPACE" generic "$APPLICATION_SECRET_NAME" \
+ --from-env-file k8s_prefixed_variables -o yaml --dry-run |
+ kubectl replace -n "$KUBE_NAMESPACE" --force -f -
+
+ export APPLICATION_SECRET_CHECKSUM=$(cat k8s_prefixed_variables | sha256sum | cut -d ' ' -f 1)
+
+ rm k8s_prefixed_variables
+ }
+
+ function deploy_name() {
+ name="$CI_ENVIRONMENT_SLUG"
+ track="${1-stable}"
+
+ if [[ "$track" != "stable" ]]; then
+ name="$name-$track"
+ fi
+
+ echo $name
+ }
+
+ function application_secret_name() {
+ track="${1-stable}"
+ name=$(deploy_name "$track")
+
+ echo "${name}-secret"
+ }
+
function deploy() {
track="${1-stable}"
percentage="${2:-100}"
- name="$CI_ENVIRONMENT_SLUG"
+ name=$(deploy_name "$track")
replicas="1"
service_enabled="true"
@@ -608,7 +653,6 @@ rollout 100%:
# if track is different than stable,
# re-use all attached resources
if [[ "$track" != "stable" ]]; then
- name="$name-$track"
service_enabled="false"
postgres_enabled="false"
fi
@@ -621,6 +665,16 @@ rollout 100%:
secret_name=''
fi
+ create_application_secret "$track"
+
+ env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]')
+ eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS
+ if [ -n "$env_ADDITIONAL_HOSTS" ]; then
+ additional_hosts="{$env_ADDITIONAL_HOSTS}"
+ elif [ -n "$ADDITIONAL_HOSTS" ]; then
+ additional_hosts="{$ADDITIONAL_HOSTS}"
+ fi
+
if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then
echo "Deploying first release with database initialization..."
helm upgrade --install \
@@ -633,7 +687,10 @@ rollout 100%:
--set image.secrets[0].name="$secret_name" \
--set application.track="$track" \
--set application.database_url="$DATABASE_URL" \
+ --set application.secretName="$APPLICATION_SECRET_NAME" \
+ --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
--set service.url="$CI_ENVIRONMENT_URL" \
+ --set service.additionalHosts="$additional_hosts" \
--set replicaCount="$replicas" \
--set postgresql.enabled="$postgres_enabled" \
--set postgresql.nameOverride="postgres" \
@@ -665,7 +722,10 @@ rollout 100%:
--set image.secrets[0].name="$secret_name" \
--set application.track="$track" \
--set application.database_url="$DATABASE_URL" \
+ --set application.secretName="$APPLICATION_SECRET_NAME" \
+ --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
--set service.url="$CI_ENVIRONMENT_URL" \
+ --set service.additionalHosts="$additional_hosts" \
--set replicaCount="$replicas" \
--set postgresql.enabled="$postgres_enabled" \
--set postgresql.nameOverride="postgres" \
@@ -684,11 +744,7 @@ rollout 100%:
function scale() {
track="${1-stable}"
percentage="${2-100}"
- name="$CI_ENVIRONMENT_SLUG"
-
- if [[ "$track" != "stable" ]]; then
- name="$name-$track"
- fi
+ name=$(deploy_name "$track")
replicas=$(get_replicas "$track" "$percentage")
@@ -749,7 +805,7 @@ rollout 100%:
fi
helm init --client-only
- helm repo add gitlab https://charts.gitlab.io
+ helm repo add gitlab ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io}
if [[ ! -d "$auto_chart" ]]; then
helm fetch ${auto_chart} --untar
fi
@@ -882,15 +938,14 @@ rollout 100%:
function delete() {
track="${1-stable}"
- name="$CI_ENVIRONMENT_SLUG"
-
- if [[ "$track" != "stable" ]]; then
- name="$name-$track"
- fi
+ name=$(deploy_name "$track")
if [[ -n "$(helm ls -q "^$name$")" ]]; then
helm delete --purge "$name"
fi
+
+ secret_name=$(application_secret_name "$track")
+ kubectl delete secret --ignore-not-found -n "$KUBE_NAMESPACE" "$secret_name"
}
before_script:
diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb
index 172926b8ab0..07ba6f83d47 100644
--- a/lib/gitlab/ci/yaml_processor.rb
+++ b/lib/gitlab/ci/yaml_processor.rb
@@ -10,7 +10,7 @@ module Gitlab
attr_reader :cache, :stages, :jobs
def initialize(config, opts = {})
- @ci_config = Gitlab::Ci::Config.new(config, opts)
+ @ci_config = Gitlab::Ci::Config.new(config, **opts)
@config = @ci_config.to_hash
unless @ci_config.valid?
@@ -33,8 +33,7 @@ module Gitlab
{ stage_idx: @stages.index(job[:stage]),
stage: job[:stage],
- commands: job[:commands],
- tag_list: job[:tags] || [],
+ tag_list: job[:tags],
name: job[:name].to_s,
allow_failure: job[:ignore],
when: job[:when] || 'on_success',
@@ -54,8 +53,9 @@ module Gitlab
retry: job[:retry],
parallel: job[:parallel],
instance: job[:instance],
- start_in: job[:start_in]
- }.compact }
+ start_in: job[:start_in],
+ trigger: job[:trigger]
+ }.compact }.compact
end
def stage_builds_attributes(stage)
diff --git a/lib/gitlab/config/entry/configurable.rb b/lib/gitlab/config/entry/configurable.rb
index afdb60b2cd5..37ba16dba25 100644
--- a/lib/gitlab/config/entry/configurable.rb
+++ b/lib/gitlab/config/entry/configurable.rb
@@ -56,6 +56,7 @@ module Gitlab
def entry(key, entry, metadata)
factory = ::Gitlab::Config::Entry::Factory.new(entry)
.with(description: metadata[:description])
+ .with(default: metadata[:default])
(@nodes ||= {}).merge!(key.to_sym => factory)
end
diff --git a/lib/gitlab/config/entry/factory.rb b/lib/gitlab/config/entry/factory.rb
index 30d43c9f9a1..79f9ff32514 100644
--- a/lib/gitlab/config/entry/factory.rb
+++ b/lib/gitlab/config/entry/factory.rb
@@ -12,7 +12,7 @@ module Gitlab
def initialize(entry)
@entry = entry
@metadata = {}
- @attributes = {}
+ @attributes = { default: entry.default }
end
def value(value)
@@ -21,12 +21,12 @@ module Gitlab
end
def metadata(metadata)
- @metadata.merge!(metadata)
+ @metadata.merge!(metadata.compact)
self
end
def with(attributes)
- @attributes.merge!(attributes)
+ @attributes.merge!(attributes.compact)
self
end
@@ -38,9 +38,7 @@ module Gitlab
# See issue #18775.
#
if @value.nil?
- Entry::Unspecified.new(
- fabricate_unspecified
- )
+ Entry::Unspecified.new(fabricate_unspecified)
else
fabricate(@entry, @value)
end
@@ -53,10 +51,12 @@ module Gitlab
# If entry has a default value we fabricate concrete node
# with default value.
#
- if @entry.default.nil?
+ default = @attributes.fetch(:default)
+
+ if default.nil?
fabricate(Entry::Undefined)
else
- fabricate(@entry, @entry.default)
+ fabricate(@entry, default)
end
end
@@ -64,6 +64,7 @@ module Gitlab
entry.new(value, @metadata).tap do |node|
node.key = @attributes[:key]
node.parent = @attributes[:parent]
+ node.default = @attributes[:default]
node.description = @attributes[:description]
end
end
diff --git a/lib/gitlab/config/entry/node.rb b/lib/gitlab/config/entry/node.rb
index 30357b2c95b..9999ab4ff95 100644
--- a/lib/gitlab/config/entry/node.rb
+++ b/lib/gitlab/config/entry/node.rb
@@ -10,7 +10,7 @@ module Gitlab
InvalidError = Class.new(StandardError)
attr_reader :config, :metadata
- attr_accessor :key, :parent, :description
+ attr_accessor :key, :parent, :default, :description
def initialize(config, **metadata)
@config = config
@@ -85,7 +85,7 @@ module Gitlab
"#<#{self.class.name} #{unspecified}{#{key}: #{val.inspect}}>"
end
- def self.default
+ def self.default(**)
end
def self.aspects
diff --git a/lib/gitlab/config/entry/simplifiable.rb b/lib/gitlab/config/entry/simplifiable.rb
index 3e148fe2e91..5fbf7565e2a 100644
--- a/lib/gitlab/config/entry/simplifiable.rb
+++ b/lib/gitlab/config/entry/simplifiable.rb
@@ -6,6 +6,8 @@ module Gitlab
class Simplifiable < SimpleDelegator
EntryStrategy = Struct.new(:name, :condition)
+ attr_reader :subject
+
def initialize(config, **metadata)
unless self.class.const_defined?(:UnknownStrategy)
raise ArgumentError, 'UndefinedStrategy not available!'
@@ -17,7 +19,7 @@ module Gitlab
entry = self.class.entry_class(strategy)
- super(entry.new(config, metadata))
+ super(@subject = entry.new(config, metadata))
end
def self.strategy(name, **opts)
@@ -37,6 +39,9 @@ module Gitlab
self::UnknownStrategy
end
end
+
+ def self.default
+ end
end
end
end
diff --git a/lib/gitlab/diff/lines_unfolder.rb b/lib/gitlab/diff/lines_unfolder.rb
index 9306b7e16a2..6cf904b2b2a 100644
--- a/lib/gitlab/diff/lines_unfolder.rb
+++ b/lib/gitlab/diff/lines_unfolder.rb
@@ -158,9 +158,14 @@ module Gitlab
from = comment_position - UNFOLD_CONTEXT_SIZE
- # There's no line before the match if it's in the top-most
- # position.
- prev_line_number = line_before_unfold_position&.old_pos || 0
+ prev_line_number =
+ if bottom?
+ last_line.old_pos
+ else
+ # There's no line before the match if it's in the top-most
+ # position.
+ line_before_unfold_position&.old_pos || 0
+ end
if from <= prev_line_number + 1
@generate_top_match_line = false
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 5d9ecd651a0..01fd261404b 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -7,7 +7,7 @@ module Gitlab
CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
CHECK_DIR = Rails.root.join('ee_compat_check')
- IGNORED_FILES_REGEX = %r{VERSION|CHANGELOG\.md|db/schema\.rb|locale/gitlab\.pot}i.freeze
+ IGNORED_FILES_REGEX = /VERSION|CHANGELOG\.md/i.freeze
PLEASE_READ_THIS_BANNER = %Q{
============================================================
===================== PLEASE READ THIS =====================
diff --git a/lib/gitlab/email/attachment_uploader.rb b/lib/gitlab/email/attachment_uploader.rb
index a826519b2dd..3323ce60158 100644
--- a/lib/gitlab/email/attachment_uploader.rb
+++ b/lib/gitlab/email/attachment_uploader.rb
@@ -23,8 +23,8 @@ module Gitlab
content_type: attachment.content_type
}
- link = UploadService.new(project, file).execute
- attachments << link if link
+ uploader = UploadService.new(project, file).execute
+ attachments << uploader.to_h if uploader
ensure
tmp.close!
end
diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb
index 35bb49ad19a..f89d1d15010 100644
--- a/lib/gitlab/email/handler/base_handler.rb
+++ b/lib/gitlab/email/handler/base_handler.rb
@@ -6,12 +6,14 @@ module Gitlab
class BaseHandler
attr_reader :mail, :mail_key
+ HANDLER_ACTION_BASE_REGEX ||= /(?<project_slug>.+)-(?<project_id>\d+)/.freeze
+
def initialize(mail, mail_key)
@mail = mail
@mail_key = mail_key
end
- def can_execute?
+ def can_handle?
raise NotImplementedError
end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index 69982efbbe6..78a3a9489ac 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -2,21 +2,33 @@
require 'gitlab/email/handler/base_handler'
+# handles issue creation emails with these formats:
+# incoming+gitlab-org-gitlab-ce-20-Author_Token12345678-issue@incoming.gitlab.com
+# incoming+gitlab-org/gitlab-ce+Author_Token12345678@incoming.gitlab.com (legacy)
module Gitlab
module Email
module Handler
class CreateIssueHandler < BaseHandler
include ReplyProcessing
- attr_reader :project_path, :incoming_email_token
+
+ HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-(?<incoming_email_token>.+)-issue\z/.freeze
+ HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\+(?<incoming_email_token>.*)\z/.freeze
def initialize(mail, mail_key)
super(mail, mail_key)
- @project_path, @incoming_email_token =
- mail_key && mail_key.split('+', 2)
+
+ if !mail_key&.include?('/') && (matched = HANDLER_REGEX.match(mail_key.to_s))
+ @project_slug = matched[:project_slug]
+ @project_id = matched[:project_id]&.to_i
+ @incoming_email_token = matched[:incoming_email_token]
+ elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s)
+ @project_path = matched[:project_path]
+ @incoming_email_token = matched[:incoming_email_token]
+ end
end
def can_handle?
- !incoming_email_token.nil? && !incoming_email_token.include?("+") && !mail_key.include?(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)
+ incoming_email_token && (project_id || can_handle_legacy_format?)
end
def execute
@@ -36,10 +48,6 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- def project
- @project ||= Project.find_by_full_path(project_path)
- end
-
private
def create_issue
@@ -50,6 +58,10 @@ module Gitlab
description: message_including_reply
).execute
end
+
+ def can_handle_legacy_format?
+ project_path && !incoming_email_token.include?('+') && !mail_key.include?(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY)
+ end
end
end
end
diff --git a/lib/gitlab/email/handler/create_merge_request_handler.rb b/lib/gitlab/email/handler/create_merge_request_handler.rb
index 5772727e855..b3b5063f2ca 100644
--- a/lib/gitlab/email/handler/create_merge_request_handler.rb
+++ b/lib/gitlab/email/handler/create_merge_request_handler.rb
@@ -3,23 +3,33 @@
require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
+# handles merge request creation emails with these formats:
+# incoming+gitlab-org-gitlab-ce-20-Author_Token12345678-merge-request@incoming.gitlab.com
+# incoming+gitlab-org/gitlab-ce+merge-request+Author_Token12345678@incoming.gitlab.com (legacy)
module Gitlab
module Email
module Handler
class CreateMergeRequestHandler < BaseHandler
include ReplyProcessing
- attr_reader :project_path, :incoming_email_token
+
+ HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-(?<incoming_email_token>.+)-merge-request\z/.freeze
+ HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\+merge-request\+(?<incoming_email_token>.*)/.freeze
def initialize(mail, mail_key)
super(mail, mail_key)
- if m = /\A([^\+]*)\+merge-request\+(.*)/.match(mail_key.to_s)
- @project_path, @incoming_email_token = m.captures
+ if !mail_key&.include?('/') && (matched = HANDLER_REGEX.match(mail_key.to_s))
+ @project_slug = matched[:project_slug]
+ @project_id = matched[:project_id]&.to_i
+ @incoming_email_token = matched[:incoming_email_token]
+ elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s)
+ @project_path = matched[:project_path]
+ @incoming_email_token = matched[:incoming_email_token]
end
end
def can_handle?
- @project_path && @incoming_email_token
+ incoming_email_token && (project_id || project_path)
end
def execute
@@ -40,10 +50,6 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- def project
- @project ||= Project.find_by_full_path(project_path)
- end
-
def metrics_params
super.merge(includes_patches: patch_attachments.any?)
end
@@ -97,7 +103,7 @@ module Gitlab
def remove_patch_attachments
patch_attachments.each { |patch| mail.parts.delete(patch) }
- # reset the message, so it needs to be reporocessed when the attachments
+ # reset the message, so it needs to be reprocessed when the attachments
# have been modified
@message = nil
end
diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb
index c7c573595fa..b00af15364d 100644
--- a/lib/gitlab/email/handler/create_note_handler.rb
+++ b/lib/gitlab/email/handler/create_note_handler.rb
@@ -3,6 +3,8 @@
require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
+# handles note/reply creation emails with these formats:
+# incoming+1234567890abcdef1234567890abcdef@incoming.gitlab.com
module Gitlab
module Email
module Handler
diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb
index ff6b2c729b2..ba9730d2685 100644
--- a/lib/gitlab/email/handler/reply_processing.rb
+++ b/lib/gitlab/email/handler/reply_processing.rb
@@ -6,13 +6,26 @@ module Gitlab
module ReplyProcessing
private
+ attr_reader :project_id, :project_slug, :project_path, :incoming_email_token
+
def author
raise NotImplementedError
end
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
def project
- raise NotImplementedError
+ return @project if instance_variable_defined?(:@project)
+
+ if project_id
+ @project = Project.find_by_id(project_id)
+ @project = nil unless valid_project_slug?(@project)
+ else
+ @project = Project.find_by_full_path(project_path)
+ end
+
+ @project
end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
def message
@message ||= process_message
@@ -58,6 +71,10 @@ module Gitlab
raise invalid_exception, msg
end
+
+ def valid_project_slug?(found_project)
+ project_slug == found_project.full_path_slug
+ end
end
end
end
diff --git a/lib/gitlab/email/handler/unsubscribe_handler.rb b/lib/gitlab/email/handler/unsubscribe_handler.rb
index d2f617b868a..20e4c125626 100644
--- a/lib/gitlab/email/handler/unsubscribe_handler.rb
+++ b/lib/gitlab/email/handler/unsubscribe_handler.rb
@@ -2,14 +2,28 @@
require 'gitlab/email/handler/base_handler'
+# handles unsubscribe emails with these formats:
+# incoming+1234567890abcdef1234567890abcdef-unsubscribe@incoming.gitlab.com
+# incoming+1234567890abcdef1234567890abcdef+unsubscribe@incoming.gitlab.com (legacy)
module Gitlab
module Email
module Handler
class UnsubscribeHandler < BaseHandler
delegate :project, to: :sent_notification, allow_nil: true
+ HANDLER_REGEX_FOR = -> (suffix) { /\A(?<reply_token>\w+)#{Regexp.escape(suffix)}\z/ }.freeze
+ HANDLER_REGEX = HANDLER_REGEX_FOR.call(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX).freeze
+ HANDLER_REGEX_LEGACY = HANDLER_REGEX_FOR.call(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY).freeze
+
+ def initialize(mail, mail_key)
+ super(mail, mail_key)
+
+ matched = HANDLER_REGEX.match(mail_key.to_s) || HANDLER_REGEX_LEGACY.match(mail_key.to_s)
+ @reply_token = matched[:reply_token] if matched
+ end
+
def can_handle?
- mail_key =~ /\A\w+#{Regexp.escape(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)}\z/
+ reply_token.present?
end
def execute
@@ -24,12 +38,10 @@ module Gitlab
private
- def sent_notification
- @sent_notification ||= SentNotification.for(reply_key)
- end
+ attr_reader :reply_token
- def reply_key
- mail_key.sub(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX, '')
+ def sent_notification
+ @sent_notification ||= SentNotification.for(reply_token)
end
end
end
diff --git a/lib/gitlab/error_tracking/error.rb b/lib/gitlab/error_tracking/error.rb
new file mode 100644
index 00000000000..4af5192aa6a
--- /dev/null
+++ b/lib/gitlab/error_tracking/error.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ErrorTracking
+ class Error
+ include ActiveModel::Model
+
+ attr_accessor :id, :title, :type, :user_count, :count,
+ :first_seen, :last_seen, :message, :culprit,
+ :external_url, :project_id, :project_name, :project_slug,
+ :short_id, :status, :frequency
+ end
+ end
+end
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
index 0341f930b9c..a11d6b66409 100644
--- a/lib/gitlab/etag_caching/middleware.rb
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def call(env)
- request = Rack::Request.new(env)
+ request = ActionDispatch::Request.new(env)
route = Gitlab::EtagCaching::Router.match(request.path_info)
return @app.call(env) unless route
diff --git a/lib/gitlab/git/bundle_file.rb b/lib/gitlab/git/bundle_file.rb
new file mode 100644
index 00000000000..8384a436fcc
--- /dev/null
+++ b/lib/gitlab/git/bundle_file.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ class BundleFile
+ # All git bundle files start with this string
+ #
+ # https://github.com/git/git/blob/v2.20.1/bundle.c#L15
+ MAGIC = "# v2 git bundle\n"
+
+ InvalidBundleError = Class.new(StandardError)
+
+ attr_reader :filename
+
+ def self.check!(filename)
+ new(filename).check!
+ end
+
+ def initialize(filename)
+ @filename = filename
+ end
+
+ def check!
+ data = File.open(filename, 'r') { |f| f.read(MAGIC.size) }
+
+ raise InvalidBundleError, 'Invalid bundle file' unless data == MAGIC
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 5bbedc9d5e3..786c90f9272 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -789,6 +789,11 @@ module Gitlab
end
def create_from_bundle(bundle_path)
+ # It's important to check that the linked-to file is actually a valid
+ # .bundle file as it is passed to `git clone`, which may otherwise
+ # interpret it as a pointer to another repository
+ ::Gitlab::Git::BundleFile.check!(bundle_path)
+
gitaly_repository_client.create_from_bundle(bundle_path)
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 802fa65dd63..010bd0e520c 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -12,6 +12,10 @@ module Gitlab
TimeoutError = Class.new(StandardError)
ProjectMovedError = Class.new(NotFoundError)
+ # Use the magic string '_any' to indicate we do not know what the
+ # changes are. This is also what gitlab-shell does.
+ ANY = '_any'
+
ERROR_MESSAGES = {
upload: 'You are not allowed to upload code for this project.',
download: 'You are not allowed to download code from this project.',
@@ -24,7 +28,8 @@ module Gitlab
upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.',
receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.',
read_only: 'The repository is temporarily read-only. Please try again later.',
- cannot_push_to_read_only: "You can't push code to a read-only GitLab instance."
+ cannot_push_to_read_only: "You can't push code to a read-only GitLab instance.",
+ push_code: 'You are not allowed to push code to this project.'
}.freeze
INTERNAL_TIMEOUT = 50.seconds.freeze
@@ -199,7 +204,7 @@ module Gitlab
def ensure_project_on_push!(cmd, changes)
return if project || deploy_key?
- return unless receive_pack?(cmd) && changes == '_any' && authentication_abilities.include?(:push_code)
+ return unless receive_pack?(cmd) && changes == ANY && authentication_abilities.include?(:push_code)
namespace = Namespace.find_by_full_path(namespace_path)
@@ -256,24 +261,34 @@ module Gitlab
raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
- return if changes.blank? # Allow access this is needed for EE.
-
check_change_access!
end
def check_change_access!
- # If there are worktrees with a HEAD pointing to a non-existent object,
- # calls to `git rev-list --all` will fail in git 2.15+. This should also
- # clear stale lock files.
- project.repository.clean_stale_repository_files
-
- # Iterate over all changes to find if user allowed all of them to be applied
- changes_list.each.with_index do |change, index|
- first_change = index == 0
-
- # If user does not have access to make at least one change, cancel all
- # push by allowing the exception to bubble up
- check_single_change_access(change, skip_lfs_integrity_check: !first_change)
+ # Deploy keys with write access can push anything
+ return if deploy_key?
+
+ if changes == ANY
+ can_push = user_access.can_do_action?(:push_code) ||
+ project.any_branch_allows_collaboration?(user_access.user)
+
+ unless can_push
+ raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code]
+ end
+ else
+ # If there are worktrees with a HEAD pointing to a non-existent object,
+ # calls to `git rev-list --all` will fail in git 2.15+. This should also
+ # clear stale lock files.
+ project.repository.clean_stale_repository_files
+
+ # Iterate over all changes to find if user allowed all of them to be applied
+ changes_list.each.with_index do |change, index|
+ first_change = index == 0
+
+ # If user does not have access to make at least one change, cancel all
+ # push by allowing the exception to bubble up
+ check_single_change_access(change, skip_lfs_integrity_check: !first_change)
+ end
end
end
@@ -282,7 +297,6 @@ module Gitlab
change,
user_access: user_access,
project: project,
- skip_authorization: deploy_key?,
skip_lfs_integrity_check: skip_lfs_integrity_check,
protocol: protocol,
logger: logger
@@ -348,7 +362,7 @@ module Gitlab
protected
def changes_list
- @changes_list ||= Gitlab::ChangesList.new(changes)
+ @changes_list ||= Gitlab::ChangesList.new(changes == ANY ? [] : changes)
end
def user
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 3f24001e4ee..0af91957fa8 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -15,7 +15,7 @@ module Gitlab
authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code)
end
- def check_single_change_access(change, _options = {})
+ def check_change_access!
unless user_access.can_do_action?(:create_wiki)
raise UnauthorizedError, ERROR_MESSAGES[:write_to_wiki]
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index d16cda3dc4a..0ab53f8f706 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -52,11 +52,18 @@ module Gitlab
klass = stub_class(name)
addr = stub_address(storage)
creds = stub_creds(storage)
- klass.new(addr, creds)
+ klass.new(addr, creds, interceptors: interceptors)
end
end
end
+ def self.interceptors
+ return [] unless Gitlab::Tracing.enabled?
+
+ [Gitlab::Tracing::GRPCInterceptor.instance]
+ end
+ private_class_method :interceptors
+
def self.stub_cert_paths
cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"]
cert_paths << OpenSSL::X509::DEFAULT_CERT_FILE if File.exist? OpenSSL::X509::DEFAULT_CERT_FILE
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 15137140639..9b1794eec91 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -8,10 +8,7 @@ module Gitlab
def add_gon_variables
gon.api_version = 'v4'
- gon.default_avatar_url =
- Gitlab::Utils.append_path(
- Gitlab.config.gitlab.url,
- ActionController::Base.helpers.image_path('no_avatar.png'))
+ gon.default_avatar_url = default_avatar_url
gon.max_file_size = Gitlab::CurrentSettings.max_attachment_size
gon.asset_host = ActionController::Base.asset_host
gon.webpack_public_path = webpack_public_path
@@ -50,5 +47,15 @@ module Gitlab
# use this method to push multiple feature flags.
gon.push({ features: { var_name => enabled } }, true)
end
+
+ def default_avatar_url
+ # We can't use ActionController::Base.helpers.image_url because it
+ # doesn't return an actual URL because request is nil for some reason.
+ #
+ # We also can't use Gitlab::Utils.append_path because the image path
+ # may be an absolute URL.
+ URI.join(Gitlab.config.gitlab.url,
+ ActionController::Base.helpers.image_path('no_avatar.png')).to_s
+ end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 25cdd5ab121..7987533978c 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -28,7 +28,8 @@ project_tree:
- notes:
:author
- releases:
- :author
+ - :author
+ - :links
- project_members:
- :user
- merge_requests:
@@ -73,6 +74,7 @@ project_tree:
- :prometheus_metrics
- :project_badges
- :ci_cd_settings
+ - :error_tracking_setting
# Only include the following attributes for the models specified.
included_attributes:
@@ -147,6 +149,7 @@ excluded_attributes:
- :when
- :artifacts_file
- :artifacts_metadata
+ - :commands
push_event_payload:
- :event_id
project_badges:
@@ -160,6 +163,9 @@ excluded_attributes:
- :token_encrypted
services:
- :template
+ error_tracking_setting:
+ - :encrypted_token
+ - :encrypted_token_iv
methods:
labels:
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index a4902e2104f..099b488f68e 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -23,7 +23,9 @@ module Gitlab
custom_attributes: 'ProjectCustomAttribute',
project_badges: 'Badge',
metrics: 'MergeRequest::Metrics',
- ci_cd_settings: 'ProjectCiCdSetting' }.freeze
+ ci_cd_settings: 'ProjectCiCdSetting',
+ error_tracking_setting: 'ErrorTracking::ProjectErrorTrackingSetting',
+ links: 'Releases::Link' }.freeze
USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze
@@ -149,6 +151,7 @@ module Gitlab
if BUILD_MODELS.include?(@relation_name)
@relation_hash.delete('trace') # old export files have trace
@relation_hash.delete('token')
+ @relation_hash.delete('commands')
imported_object
elsif @relation_name == :merge_requests
diff --git a/lib/gitlab/import_export/uploads_manager.rb b/lib/gitlab/import_export/uploads_manager.rb
index 474e9d45566..e232198150a 100644
--- a/lib/gitlab/import_export/uploads_manager.rb
+++ b/lib/gitlab/import_export/uploads_manager.rb
@@ -40,7 +40,7 @@ module Gitlab
def add_upload(upload)
uploader_context = FileUploader.extract_dynamic_path(upload).named_captures.symbolize_keys
- UploadService.new(@project, File.open(upload, 'r'), FileUploader, uploader_context).execute
+ UploadService.new(@project, File.open(upload, 'r'), FileUploader, uploader_context).execute.to_h
end
def copy_project_uploads
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 20fc8226611..cc0c633b943 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -2,8 +2,9 @@
module Gitlab
module IncomingEmail
- UNSUBSCRIBE_SUFFIX = '+unsubscribe'.freeze
- WILDCARD_PLACEHOLDER = '%{key}'.freeze
+ UNSUBSCRIBE_SUFFIX = '-unsubscribe'.freeze
+ UNSUBSCRIBE_SUFFIX_LEGACY = '+unsubscribe'.freeze
+ WILDCARD_PLACEHOLDER = '%{key}'.freeze
class << self
def enabled?
@@ -22,6 +23,7 @@ module Gitlab
config.address.sub(WILDCARD_PLACEHOLDER, key)
end
+ # example: incoming+1234567890abcdef1234567890abcdef-unsubscribe@incoming.gitlab.com
def unsubscribe_address(key)
config.address.sub(WILDCARD_PLACEHOLDER, "#{key}#{UNSUBSCRIBE_SUFFIX}")
end
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index 03d38ec78fd..bbac15c7710 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -3,7 +3,7 @@
module Gitlab
module Kubernetes
module Helm
- HELM_VERSION = '2.11.0'.freeze
+ HELM_VERSION = '2.12.2'.freeze
KUBECTL_VERSION = '1.11.0'.freeze
NAMESPACE = 'gitlab-managed-apps'.freeze
SERVICE_ACCOUNT = 'tiller'.freeze
diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb
index fe839940f74..624c2c67551 100644
--- a/lib/gitlab/kubernetes/kube_client.rb
+++ b/lib/gitlab/kubernetes/kube_client.rb
@@ -76,9 +76,12 @@ module Gitlab
attr_reader :api_prefix, :kubeclient_options
+ # We disable redirects through 'http_max_redirects: 0',
+ # so that KubeClient does not follow redirects and
+ # expose internal services.
def initialize(api_prefix, **kubeclient_options)
@api_prefix = api_prefix
- @kubeclient_options = kubeclient_options
+ @kubeclient_options = kubeclient_options.merge(http_max_redirects: 0)
end
def create_or_update_cluster_role_binding(resource)
diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb
index c09d3ebc7be..26b81847d37 100644
--- a/lib/gitlab/lfs_token.rb
+++ b/lib/gitlab/lfs_token.rb
@@ -47,7 +47,7 @@ module Gitlab
user? ? :lfs_token : :lfs_deploy_token
end
- private # rubocop:disable Lint/UselessAccessModifier
+ private # rubocop:disable Lint/UselessAccessModifier
class HMACToken
include LfsTokenHelper
@@ -100,7 +100,7 @@ module Gitlab
#
class LegacyRedisDeviseToken
TOKEN_LENGTH = 50
- DEFAULT_EXPIRY_TIME = 1800 * 1000 # 30 mins
+ DEFAULT_EXPIRY_TIME = 1800 * 1000 # 30 mins
def initialize(actor)
@actor = actor
diff --git a/lib/gitlab/loop_helpers.rb b/lib/gitlab/loop_helpers.rb
new file mode 100644
index 00000000000..3873156a3b0
--- /dev/null
+++ b/lib/gitlab/loop_helpers.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module LoopHelpers
+ ##
+ # This helper method repeats the same task until it's expired.
+ #
+ # Note: ExpiredLoopError does not happen until the given block finished.
+ # Please do not use this method for heavy or asynchronous operations.
+ def loop_until(timeout: nil, limit: 1_000_000)
+ raise ArgumentError unless limit
+
+ start = Time.now
+
+ limit.times do
+ return true unless yield
+
+ return false if timeout && (Time.now - start) > timeout
+ end
+
+ false
+ end
+ end
+end
diff --git a/lib/gitlab/middleware/basic_health_check.rb b/lib/gitlab/middleware/basic_health_check.rb
index f2a03217098..acf8c301b8f 100644
--- a/lib/gitlab/middleware/basic_health_check.rb
+++ b/lib/gitlab/middleware/basic_health_check.rb
@@ -24,7 +24,7 @@ module Gitlab
def call(env)
return @app.call(env) unless env['PATH_INFO'] == HEALTH_PATH
- request = Rack::Request.new(env)
+ request = ActionDispatch::Request.new(env)
return OK_RESPONSE if client_ip_whitelisted?(request)
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index 72a788022ef..f9efef38825 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -111,7 +111,7 @@ module Gitlab
def project_for_paths(paths, request)
project = Project.where_full_path_in(paths).first
- return unless Ability.allowed?(current_user(request, project), :read_project, project)
+ return unless Ability.allowed?(current_user(request, project), :read_project, project)
project
end
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index 84c2f0d5720..433151b80e7 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -32,7 +32,7 @@ module Gitlab
class Handler
def initialize(env, message)
- @request = Rack::Request.new(env)
+ @request = ActionDispatch::Request.new(env)
@rewritten_fields = message['rewritten_fields']
@open_files = []
end
diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb
index 89941a9efa0..817db12ac55 100644
--- a/lib/gitlab/middleware/read_only/controller.rb
+++ b/lib/gitlab/middleware/read_only/controller.rb
@@ -60,7 +60,7 @@ module Gitlab
end
def request
- @env['rack.request'] ||= Rack::Request.new(@env)
+ @env['actionpack.request'] ||= ActionDispatch::Request.new(@env)
end
def last_visited_url
@@ -71,12 +71,16 @@ module Gitlab
@route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {}
end
+ def relative_url
+ File.join('', Gitlab.config.gitlab.relative_url_root).chomp('/')
+ end
+
# Overridden in EE module
def whitelisted_routes
- grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route
+ grack_route? || internal_route? || lfs_route? || sidekiq_route?
end
- def grack_route
+ def grack_route?
# Calling route_hash may be expensive. Only do it if we think there's a possible match
return false unless
request.path.end_with?('.git/git-upload-pack', '.git/git-receive-pack')
@@ -84,7 +88,11 @@ module Gitlab
WHITELISTED_GIT_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
- def lfs_route
+ def internal_route?
+ ReadOnly.internal_routes.any? { |path| request.path.include?(path) }
+ end
+
+ def lfs_route?
# Calling route_hash may be expensive. Only do it if we think there's a possible match
unless request.path.end_with?('/info/lfs/objects/batch',
'/info/lfs/locks', '/info/lfs/locks/verify') ||
@@ -95,8 +103,8 @@ module Gitlab
WHITELISTED_GIT_LFS_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
- def sidekiq_route
- request.path.start_with?('/admin/sidekiq')
+ def sidekiq_route?
+ request.path.start_with?("#{relative_url}/admin/sidekiq")
end
end
end
diff --git a/lib/gitlab/pages_client.rb b/lib/gitlab/pages_client.rb
index 3626e53f84c..d74fdba2241 100644
--- a/lib/gitlab/pages_client.rb
+++ b/lib/gitlab/pages_client.rb
@@ -103,7 +103,7 @@ module Gitlab
end
def write_token(new_token)
- Tempfile.open(File.basename(token_path), File.dirname(token_path), encoding: 'ascii-8bit') do |f|
+ Tempfile.open(File.basename(token_path), File.dirname(token_path), encoding: 'ascii-8bit') do |f|
f.write(new_token)
f.close
File.link(f.path, token_path)
diff --git a/lib/gitlab/release_blog_post.rb b/lib/gitlab/release_blog_post.rb
new file mode 100644
index 00000000000..639aee61464
--- /dev/null
+++ b/lib/gitlab/release_blog_post.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+require 'singleton'
+
+module Gitlab
+ class ReleaseBlogPost
+ include Singleton
+
+ RELEASE_RSS_URL = 'https://about.gitlab.com/releases.xml'
+
+ def blog_post_url
+ @url ||= fetch_blog_post_url
+ end
+
+ private
+
+ def fetch_blog_post_url
+ installed_version = Gitlab.final_release? ? Gitlab.minor_release : Gitlab.previous_release
+ response = Gitlab::HTTP.get(RELEASE_RSS_URL, verify: false)
+
+ return unless response.code == 200
+
+ blog_entry = find_installed_blog_entry(response, installed_version)
+ blog_entry['id'] if blog_entry
+ end
+
+ def find_installed_blog_entry(response, installed_version)
+ response['feed']['entry'].find do |entry|
+ entry['release'] == installed_version || matches_previous_release_post(entry['release'], installed_version)
+ end
+ end
+
+ def should_match_previous_release_post?
+ Gitlab.new_major_release? && !Gitlab.final_release?
+ end
+
+ def matches_previous_release_post(rss_release_version, installed_version)
+ should_match_previous_release_post? && rss_release_version[/\d+/] == installed_version
+ end
+ end
+end
diff --git a/lib/gitlab/repository_cache.rb b/lib/gitlab/repository_cache.rb
index 6b0808f5304..56007574b1b 100644
--- a/lib/gitlab/repository_cache.rb
+++ b/lib/gitlab/repository_cache.rb
@@ -7,13 +7,13 @@ module Gitlab
def initialize(repository, extra_namespace: nil, backend: Rails.cache)
@repository = repository
- @namespace = "project:#{repository.project.id}"
+ @namespace = "#{repository.full_path}:#{repository.project.id}"
@namespace = "#{@namespace}:#{extra_namespace}" if extra_namespace
@backend = backend
end
def cache_key(type)
- "#{namespace}:#{type}"
+ "#{type}:#{namespace}"
end
def expire(key)
diff --git a/lib/gitlab/request_context.rb b/lib/gitlab/request_context.rb
index f8f8ec789ce..d9811e036d3 100644
--- a/lib/gitlab/request_context.rb
+++ b/lib/gitlab/request_context.rb
@@ -13,7 +13,7 @@ module Gitlab
end
def call(env)
- req = Rack::Request.new(env)
+ req = ActionDispatch::Request.new(env)
Gitlab::SafeRequestStore[:client_ip] = req.ip
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index 84a51773276..8e2f16271eb 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -26,6 +26,19 @@ module Gitlab
puts "\nOK".color(:green)
end
+ def self.without_gitaly_timeout
+ # Remove Gitaly timeout
+ old_timeout = Gitlab::CurrentSettings.current_application_settings.gitaly_timeout_default
+ Gitlab::CurrentSettings.current_application_settings.update_columns(gitaly_timeout_default: 0)
+ # Otherwise we still see the default value when running seed_fu
+ ApplicationSetting.expire
+
+ yield
+ ensure
+ Gitlab::CurrentSettings.current_application_settings.update_columns(gitaly_timeout_default: old_timeout)
+ ApplicationSetting.expire
+ end
+
def self.mute_notifications
NotificationService.prepend(MuteNotifications)
end
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 46d01964eac..956c16117f5 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -52,14 +52,6 @@ module Gitlab
end
end
- def self.program_context
- if Sidekiq.server?
- 'sidekiq'
- else
- 'rails'
- end
- end
-
def self.should_raise_for_dev?
Rails.env.development? || Rails.env.test?
end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index c6a6fb9b5ce..bdf21cf3134 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -289,10 +289,12 @@ module Gitlab
#
def mv_namespace(storage, old_name, new_name)
Gitlab::GitalyClient::NamespaceService.new(storage).rename(old_name, new_name)
- rescue GRPC::InvalidArgument
+ rescue GRPC::InvalidArgument => e
+ Gitlab::Sentry.track_acceptable_exception(e, extra: { old_name: old_name, new_name: new_name, storage: storage })
+
false
end
- alias_method :mv_directory, :mv_namespace
+ alias_method :mv_directory, :mv_namespace # Note: ShellWorker uses this alias
def url_to_repo(path)
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
diff --git a/lib/gitlab/sidekiq_logging/structured_logger.rb b/lib/gitlab/sidekiq_logging/structured_logger.rb
index e86db8db3a1..fdc0d518c59 100644
--- a/lib/gitlab/sidekiq_logging/structured_logger.rb
+++ b/lib/gitlab/sidekiq_logging/structured_logger.rb
@@ -5,6 +5,7 @@ module Gitlab
class StructuredLogger
START_TIMESTAMP_FIELDS = %w[created_at enqueued_at].freeze
DONE_TIMESTAMP_FIELDS = %w[started_at retried_at failed_at completed_at].freeze
+ MAXIMUM_JOB_ARGUMENTS_LENGTH = 10.kilobytes
def call(job, queue)
started_at = current_time
@@ -64,6 +65,7 @@ module Gitlab
job['pid'] = ::Process.pid
job.delete('args') unless ENV['SIDEKIQ_LOG_ARGUMENTS']
+ job['args'] = limited_job_args(job['args']) if job['args']
convert_to_iso8601(job, START_TIMESTAMP_FIELDS)
@@ -93,6 +95,21 @@ module Gitlab
Time.at(timestamp).utc.iso8601(3)
end
+
+ def limited_job_args(args)
+ return unless args.is_a?(Array)
+
+ total_length = 0
+ limited_args = args.take_while do |arg|
+ total_length += arg.to_json.length
+
+ total_length <= MAXIMUM_JOB_ARGUMENTS_LENGTH
+ end
+
+ limited_args.push('...') if total_length > MAXIMUM_JOB_ARGUMENTS_LENGTH
+
+ limited_args
+ end
end
end
end
diff --git a/lib/gitlab/sql/union.rb b/lib/gitlab/sql/union.rb
index d24d5116167..f05592fc3a3 100644
--- a/lib/gitlab/sql/union.rb
+++ b/lib/gitlab/sql/union.rb
@@ -9,7 +9,7 @@ module Gitlab
#
# Example usage:
#
- # union = Gitlab::SQL::Union.new(user.personal_projects, user.projects)
+ # union = Gitlab::SQL::Union.new([user.personal_projects, user.projects])
# sql = union.to_sql
#
# Project.where("id IN (#{sql})")
diff --git a/lib/gitlab/tracing.rb b/lib/gitlab/tracing.rb
new file mode 100644
index 00000000000..3c4db42ac06
--- /dev/null
+++ b/lib/gitlab/tracing.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Tracing
+ # Only enable tracing when the `GITLAB_TRACING` env var is configured. Note that we avoid using ApplicationSettings since
+ # the same environment variable needs to be configured for Workhorse, Gitaly and any other components which
+ # emit tracing. Since other components may start before Rails, and may not have access to ApplicationSettings,
+ # an env var makes more sense.
+ def self.enabled?
+ connection_string.present?
+ end
+
+ def self.connection_string
+ ENV['GITLAB_TRACING']
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/common.rb b/lib/gitlab/tracing/common.rb
new file mode 100644
index 00000000000..3a08ede8138
--- /dev/null
+++ b/lib/gitlab/tracing/common.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'opentracing'
+
+module Gitlab
+ module Tracing
+ module Common
+ def tracer
+ OpenTracing.global_tracer
+ end
+
+ # Convience method for running a block with a span
+ def in_tracing_span(operation_name:, tags:, child_of: nil)
+ scope = tracer.start_active_span(
+ operation_name,
+ child_of: child_of,
+ tags: tags
+ )
+ span = scope.span
+
+ # Add correlation details to the span if we have them
+ correlation_id = Gitlab::CorrelationId.current_id
+ if correlation_id
+ span.set_tag('correlation_id', correlation_id)
+ end
+
+ begin
+ yield span
+ rescue => e
+ log_exception_on_span(span, e)
+ raise e
+ ensure
+ scope.close
+ end
+ end
+
+ def postnotify_span(operation_name, start_time, end_time, tags: nil, child_of: nil, exception: nil)
+ span = OpenTracing.start_span(operation_name, start_time: start_time, tags: tags, child_of: child_of)
+
+ log_exception_on_span(span, exception) if exception
+
+ span.finish(end_time: end_time)
+ end
+
+ def log_exception_on_span(span, exception)
+ span.set_tag('error', true)
+ span.log_kv(kv_tags_for_exception(exception))
+ end
+
+ def kv_tags_for_exception(exception)
+ case exception
+ when Exception
+ {
+ 'event': 'error',
+ 'error.kind': exception.class.to_s,
+ 'message': Gitlab::UrlSanitizer.sanitize(exception.message),
+ 'stack': exception.backtrace&.join("\n")
+ }
+ else
+ {
+ 'event': 'error',
+ 'error.kind': exception.class.to_s,
+ 'error.object': Gitlab::UrlSanitizer.sanitize(exception.to_s)
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/factory.rb b/lib/gitlab/tracing/factory.rb
new file mode 100644
index 00000000000..fc714164353
--- /dev/null
+++ b/lib/gitlab/tracing/factory.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require "cgi"
+
+module Gitlab
+ module Tracing
+ class Factory
+ OPENTRACING_SCHEME = "opentracing"
+
+ def self.create_tracer(service_name, connection_string)
+ return unless connection_string.present?
+
+ begin
+ opentracing_details = parse_connection_string(connection_string)
+ driver_name = opentracing_details[:driver_name]
+
+ case driver_name
+ when "jaeger"
+ JaegerFactory.create_tracer(service_name, opentracing_details[:options])
+ else
+ raise "Unknown driver: #{driver_name}"
+ end
+ rescue => e
+ # Can't create the tracer? Warn and continue sans tracer
+ warn "Unable to instantiate tracer: #{e}"
+ nil
+ end
+ end
+
+ def self.parse_connection_string(connection_string)
+ parsed = URI.parse(connection_string)
+
+ unless valid_uri?(parsed)
+ raise "Invalid tracing connection string"
+ end
+
+ {
+ driver_name: parsed.host,
+ options: parse_query(parsed.query)
+ }
+ end
+ private_class_method :parse_connection_string
+
+ def self.parse_query(query)
+ return {} unless query
+
+ CGI.parse(query).symbolize_keys.transform_values(&:first)
+ end
+ private_class_method :parse_query
+
+ def self.valid_uri?(uri)
+ return false unless uri
+
+ uri.scheme == OPENTRACING_SCHEME &&
+ uri.host.to_s =~ /^[a-z0-9_]+$/ &&
+ uri.path.empty?
+ end
+ private_class_method :valid_uri?
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/grpc_interceptor.rb b/lib/gitlab/tracing/grpc_interceptor.rb
new file mode 100644
index 00000000000..6c2aab73125
--- /dev/null
+++ b/lib/gitlab/tracing/grpc_interceptor.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'opentracing'
+require 'grpc'
+
+module Gitlab
+ module Tracing
+ class GRPCInterceptor < GRPC::ClientInterceptor
+ include Common
+ include Singleton
+
+ def request_response(request:, call:, method:, metadata:)
+ wrap_with_tracing(method, 'unary', metadata) do
+ yield
+ end
+ end
+
+ def client_streamer(requests:, call:, method:, metadata:)
+ wrap_with_tracing(method, 'client_stream', metadata) do
+ yield
+ end
+ end
+
+ def server_streamer(request:, call:, method:, metadata:)
+ wrap_with_tracing(method, 'server_stream', metadata) do
+ yield
+ end
+ end
+
+ def bidi_streamer(requests:, call:, method:, metadata:)
+ wrap_with_tracing(method, 'bidi_stream', metadata) do
+ yield
+ end
+ end
+
+ private
+
+ def wrap_with_tracing(method, grpc_type, metadata)
+ tags = {
+ 'component' => 'grpc',
+ 'span.kind' => 'client',
+ 'grpc.method' => method,
+ 'grpc.type' => grpc_type
+ }
+
+ in_tracing_span(operation_name: "grpc:#{method}", tags: tags) do |span|
+ OpenTracing.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, metadata)
+
+ yield
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/jaeger_factory.rb b/lib/gitlab/tracing/jaeger_factory.rb
new file mode 100644
index 00000000000..2682007302a
--- /dev/null
+++ b/lib/gitlab/tracing/jaeger_factory.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'jaeger/client'
+
+module Gitlab
+ module Tracing
+ class JaegerFactory
+ # When the probabilistic sampler is used, by default 0.1% of requests will be traced
+ DEFAULT_PROBABILISTIC_RATE = 0.001
+
+ # The default port for the Jaeger agent UDP listener
+ DEFAULT_UDP_PORT = 6831
+
+ # Reduce this from default of 10 seconds as the Ruby jaeger
+ # client doesn't have overflow control, leading to very large
+ # messages which fail to send over UDP (max packet = 64k)
+ # Flush more often, with smaller packets
+ FLUSH_INTERVAL = 5
+
+ def self.create_tracer(service_name, options)
+ kwargs = {
+ service_name: service_name,
+ sampler: get_sampler(options[:sampler], options[:sampler_param]),
+ reporter: get_reporter(service_name, options[:http_endpoint], options[:udp_endpoint])
+ }.compact
+
+ extra_params = options.except(:sampler, :sampler_param, :http_endpoint, :udp_endpoint, :strict_parsing, :debug) # rubocop: disable CodeReuse/ActiveRecord
+ if extra_params.present?
+ message = "jaeger tracer: invalid option: #{extra_params.keys.join(", ")}"
+
+ if options[:strict_parsing]
+ raise message
+ else
+ warn message
+ end
+ end
+
+ Jaeger::Client.build(kwargs)
+ end
+
+ def self.get_sampler(sampler_type, sampler_param)
+ case sampler_type
+ when "probabilistic"
+ sampler_rate = sampler_param ? sampler_param.to_f : DEFAULT_PROBABILISTIC_RATE
+ Jaeger::Samplers::Probabilistic.new(rate: sampler_rate)
+ when "const"
+ const_value = sampler_param == "1"
+ Jaeger::Samplers::Const.new(const_value)
+ else
+ nil
+ end
+ end
+ private_class_method :get_sampler
+
+ def self.get_reporter(service_name, http_endpoint, udp_endpoint)
+ encoder = Jaeger::Encoders::ThriftEncoder.new(service_name: service_name)
+
+ if http_endpoint.present?
+ sender = get_http_sender(encoder, http_endpoint)
+ elsif udp_endpoint.present?
+ sender = get_udp_sender(encoder, udp_endpoint)
+ else
+ return nil
+ end
+
+ Jaeger::Reporters::RemoteReporter.new(
+ sender: sender,
+ flush_interval: FLUSH_INTERVAL
+ )
+ end
+ private_class_method :get_reporter
+
+ def self.get_http_sender(encoder, address)
+ Jaeger::HttpSender.new(
+ url: address,
+ encoder: encoder,
+ logger: Logger.new(STDOUT)
+ )
+ end
+ private_class_method :get_http_sender
+
+ def self.get_udp_sender(encoder, address)
+ pair = address.split(":", 2)
+ host = pair[0]
+ port = pair[1] ? pair[1].to_i : DEFAULT_UDP_PORT
+
+ Jaeger::UdpSender.new(
+ host: host,
+ port: port,
+ encoder: encoder,
+ logger: Logger.new(STDOUT)
+ )
+ end
+ private_class_method :get_udp_sender
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/rack_middleware.rb b/lib/gitlab/tracing/rack_middleware.rb
new file mode 100644
index 00000000000..e6a31293f7b
--- /dev/null
+++ b/lib/gitlab/tracing/rack_middleware.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'opentracing'
+
+module Gitlab
+ module Tracing
+ class RackMiddleware
+ include Common
+
+ REQUEST_METHOD = 'REQUEST_METHOD'
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ method = env[REQUEST_METHOD]
+
+ context = tracer.extract(OpenTracing::FORMAT_RACK, env)
+ tags = {
+ 'component' => 'rack',
+ 'span.kind' => 'server',
+ 'http.method' => method,
+ 'http.url' => self.class.build_sanitized_url_from_env(env)
+ }
+
+ in_tracing_span(operation_name: "http:#{method}", child_of: context, tags: tags) do |span|
+ @app.call(env).tap do |status_code, _headers, _body|
+ span.set_tag('http.status_code', status_code)
+ end
+ end
+ end
+
+ # Generate a sanitized (safe) request URL from the rack environment
+ def self.build_sanitized_url_from_env(env)
+ request = ActionDispatch::Request.new(env)
+
+ original_url = request.original_url
+ uri = URI.parse(original_url)
+ uri.query = request.filtered_parameters.to_query if uri.query.present?
+
+ uri.to_s
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/rails/active_record_subscriber.rb b/lib/gitlab/tracing/rails/active_record_subscriber.rb
new file mode 100644
index 00000000000..214eac47e14
--- /dev/null
+++ b/lib/gitlab/tracing/rails/active_record_subscriber.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Tracing
+ module Rails
+ class ActiveRecordSubscriber
+ include Gitlab::Tracing::Common
+
+ ACTIVE_RECORD_NOTIFICATION_TOPIC = 'sql.active_record'
+ DEFAULT_OPERATION_NAME = "sqlquery"
+
+ def self.instrument
+ subscriber = new
+
+ ActiveSupport::Notifications.subscribe(ACTIVE_RECORD_NOTIFICATION_TOPIC) do |_, start, finish, _, payload|
+ subscriber.notify(start, finish, payload)
+ end
+ end
+
+ # For more information on the payloads: https://guides.rubyonrails.org/active_support_instrumentation.html
+ def notify(start, finish, payload)
+ operation_name = payload[:name].presence || DEFAULT_OPERATION_NAME
+ exception = payload[:exception]
+ tags = {
+ 'component' => 'ActiveRecord',
+ 'span.kind' => 'client',
+ 'db.type' => 'sql',
+ 'db.connection_id' => payload[:connection_id],
+ 'db.cached' => payload[:cached] || false,
+ 'db.statement' => payload[:sql]
+ }
+
+ postnotify_span("active_record:#{operation_name}", start, finish, tags: tags, exception: exception)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/sidekiq/client_middleware.rb b/lib/gitlab/tracing/sidekiq/client_middleware.rb
new file mode 100644
index 00000000000..2b71c1ea21e
--- /dev/null
+++ b/lib/gitlab/tracing/sidekiq/client_middleware.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'opentracing'
+
+module Gitlab
+ module Tracing
+ module Sidekiq
+ class ClientMiddleware
+ include SidekiqCommon
+
+ SPAN_KIND = 'client'
+
+ def call(worker_class, job, queue, redis_pool)
+ in_tracing_span(
+ operation_name: "sidekiq:#{job['class']}",
+ tags: tags_from_job(job, SPAN_KIND)) do |span|
+ # Inject the details directly into the job
+ tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, job)
+
+ yield
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/sidekiq/server_middleware.rb b/lib/gitlab/tracing/sidekiq/server_middleware.rb
new file mode 100644
index 00000000000..5b43c4310e6
--- /dev/null
+++ b/lib/gitlab/tracing/sidekiq/server_middleware.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'opentracing'
+
+module Gitlab
+ module Tracing
+ module Sidekiq
+ class ServerMiddleware
+ include SidekiqCommon
+
+ SPAN_KIND = 'server'
+
+ def call(worker, job, queue)
+ context = tracer.extract(OpenTracing::FORMAT_TEXT_MAP, job)
+
+ in_tracing_span(
+ operation_name: "sidekiq:#{job['class']}",
+ child_of: context,
+ tags: tags_from_job(job, SPAN_KIND)) do |span|
+ yield
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/sidekiq/sidekiq_common.rb b/lib/gitlab/tracing/sidekiq/sidekiq_common.rb
new file mode 100644
index 00000000000..a911a29d773
--- /dev/null
+++ b/lib/gitlab/tracing/sidekiq/sidekiq_common.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Tracing
+ module Sidekiq
+ module SidekiqCommon
+ include Gitlab::Tracing::Common
+
+ def tags_from_job(job, kind)
+ {
+ 'component' => 'sidekiq',
+ 'span.kind' => kind,
+ 'sidekiq.queue' => job['queue'],
+ 'sidekiq.jid' => job['jid'],
+ 'sidekiq.retry' => job['retry'].to_s,
+ 'sidekiq.args' => job['args']&.join(", ")
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 44c71f8431d..9b7b0db9525 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -8,16 +8,18 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
class << self
- def validate!(url, ports: [], protocols: [], allow_localhost: false, allow_local_network: true, ascii_only: false, enforce_user: false)
+ def validate!(url, ports: [], protocols: [], allow_localhost: false, allow_local_network: true, ascii_only: false, enforce_user: false, enforce_sanitization: false)
return true 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)
- port = uri.port || uri.default_port
+ port = get_port(uri)
validate_protocol!(uri.scheme, protocols)
validate_port!(port, ports) if ports.any?
validate_user!(uri.user) if enforce_user
@@ -50,6 +52,18 @@ module Gitlab
private
+ def get_port(uri)
+ uri.port || uri.default_port
+ end
+
+ def validate_html_tags!(uri)
+ uri_str = uri.to_s
+ sanitized_uri = ActionController::Base.helpers.sanitize(uri_str, tags: [])
+ if sanitized_uri != uri_str
+ raise BlockedUrlError, 'HTML/CSS/JS tags are not allowed'
+ end
+ end
+
def parse_url(url)
raise Addressable::URI::InvalidURIError if multiline?(url)
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index a81cee0d6d2..99fa65e0e90 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -115,5 +115,15 @@ module Gitlab
string_or_array.split(',').map(&:strip)
end
+
+ def deep_indifferent_access(data)
+ if data.is_a?(Array)
+ data.map(&method(:deep_indifferent_access))
+ elsif data.is_a?(Hash)
+ data.with_indifferent_access
+ else
+ data
+ end
+ end
end
end
diff --git a/lib/gitlab/version_info.rb b/lib/gitlab/version_info.rb
index aa6d5310161..142ead12c08 100644
--- a/lib/gitlab/version_info.rb
+++ b/lib/gitlab/version_info.rb
@@ -20,6 +20,14 @@ module Gitlab
@patch = patch
end
+ def minor_version?
+ minor.to_i > 0
+ end
+
+ def patch_version?
+ patch.to_i > 0
+ end
+
def <=>(other)
return unless other.is_a? VersionInfo
return unless valid? && other.valid?
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
new file mode 100644
index 00000000000..343f2c49a7f
--- /dev/null
+++ b/lib/sentry/client.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+module Sentry
+ class Client
+ Error = Class.new(StandardError)
+
+ attr_accessor :url, :token
+
+ def initialize(api_url, token)
+ @url = api_url
+ @token = token
+ end
+
+ def list_issues(issue_status:, limit:)
+ issues = get_issues(issue_status: issue_status, limit: limit)
+ map_to_errors(issues)
+ end
+
+ private
+
+ def request_params
+ {
+ headers: {
+ 'Authorization' => "Bearer #{@token}"
+ },
+ follow_redirects: false
+ }
+ end
+
+ def get_issues(issue_status:, limit:)
+ resp = Gitlab::HTTP.get(
+ issues_api_url,
+ **request_params.merge(query: {
+ query: "is:#{issue_status}",
+ limit: limit
+ })
+ )
+
+ handle_response(resp)
+ end
+
+ def handle_response(response)
+ unless response.code == 200
+ raise Client::Error, "Sentry response error: #{response.code}"
+ end
+
+ response.as_json
+ end
+
+ def issues_api_url
+ issues_url = URI(@url + '/issues/')
+ issues_url.path.squeeze!('/')
+
+ issues_url
+ end
+
+ def map_to_errors(issues)
+ issues.map do |issue|
+ map_to_error(issue)
+ end
+ end
+
+ def issue_url(id)
+ issues_url = @url + "/issues/#{id}"
+ issues_url = ErrorTracking::ProjectErrorTrackingSetting.extract_sentry_external_url(issues_url)
+
+ uri = URI(issues_url)
+ uri.path.squeeze!('/')
+
+ uri.to_s
+ end
+
+ def map_to_error(issue)
+ id = issue.fetch('id')
+ project = issue.fetch('project')
+
+ count = issue.fetch('count', nil)
+
+ frequency = issue.dig('stats', '24h')
+ message = issue.dig('metadata', 'value')
+
+ external_url = issue_url(id)
+
+ Gitlab::ErrorTracking::Error.new(
+ id: id,
+ first_seen: issue.fetch('firstSeen', nil),
+ last_seen: issue.fetch('lastSeen', nil),
+ title: issue.fetch('title', nil),
+ type: issue.fetch('type', nil),
+ user_count: issue.fetch('userCount', nil),
+ count: count,
+ message: message,
+ culprit: issue.fetch('culprit', nil),
+ external_url: external_url,
+ short_id: issue.fetch('shortId', nil),
+ status: issue.fetch('status', nil),
+ frequency: frequency,
+ project_id: project.fetch('id'),
+ project_name: project.fetch('name', nil),
+ project_slug: project.fetch('slug', nil)
+ )
+ end
+ end
+end
diff --git a/lib/serializers/json.rb b/lib/serializers/json.rb
new file mode 100644
index 00000000000..93cb192087a
--- /dev/null
+++ b/lib/serializers/json.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Serializers
+ # This serializer exports data as JSON,
+ # it is designed to be used with interwork compatibility between MySQL and PostgreSQL
+ # implementations, as used version of MySQL does not support native json type
+ #
+ # Secondly, the loader makes the resulting hash to have deep indifferent access
+ class JSON
+ class << self
+ def dump(obj)
+ # MySQL stores data as text
+ # look at ./config/initializers/ar_mysql_jsonb_support.rb
+ if Gitlab::Database.mysql?
+ obj = ActiveSupport::JSON.encode(obj)
+ end
+
+ obj
+ end
+
+ def load(data)
+ return if data.nil?
+
+ # On MySQL we store data as text
+ # look at ./config/initializers/ar_mysql_jsonb_support.rb
+ if Gitlab::Database.mysql?
+ data = ActiveSupport::JSON.decode(data)
+ end
+
+ Gitlab::Utils.deep_indifferent_access(data)
+ end
+ end
+ end
+end
diff --git a/lib/system_check/base_check.rb b/lib/system_check/base_check.rb
index e06245294c4..46aad8aa885 100644
--- a/lib/system_check/base_check.rb
+++ b/lib/system_check/base_check.rb
@@ -70,18 +70,14 @@ module SystemCheck
# multiple reasons why a check can fail
#
# @param [String] reason to be displayed
- def skip_reason=(reason)
- @skip_reason = reason
- end
+ attr_writer :skip_reason
# Skip reason defined during runtime
#
# This value have precedence over the one defined in the subclass
#
# @return [String] the reason
- def skip_reason
- @skip_reason
- end
+ attr_reader :skip_reason
# Does the check support automatically repair routine?
#
diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake
index 26cbf0740b6..c0d6cc8ca8e 100644
--- a/lib/tasks/gitlab/bulk_add_permission.rake
+++ b/lib/tasks/gitlab/bulk_add_permission.rake
@@ -14,7 +14,7 @@ namespace :gitlab do
end
desc "GitLab | Add a specific user to all projects (as a developer)"
- task :user_to_projects, [:email] => :environment do |t, args|
+ task :user_to_projects, [:email] => :environment do |t, args|
user = User.find_by(email: args.email)
project_ids = Project.pluck(:id)
puts "Importing #{user.email} users into #{project_ids.size} projects"
@@ -22,7 +22,7 @@ namespace :gitlab do
end
desc "GitLab | Add all users to all groups (admin users are added as owners)"
- task all_users_to_all_groups: :environment do |t, args|
+ task all_users_to_all_groups: :environment do |t, args|
user_ids = User.where(admin: false).pluck(:id)
admin_ids = User.where(admin: true).pluck(:id)
groups = Group.all
@@ -36,7 +36,7 @@ namespace :gitlab do
end
desc "GitLab | Add a specific user to all groups (as a developer)"
- task :user_to_groups, [:email] => :environment do |t, args|
+ task :user_to_groups, [:email] => :environment do |t, args|
user = User.find_by_email args.email
groups = Group.all
puts "Importing #{user.email} users into #{groups.size} groups"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ddfd5599883..8f793d7d576 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -106,6 +106,9 @@ msgstr ""
msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)"
msgstr ""
+msgid "%{count} more"
+msgstr ""
+
msgid "%{count} more assignees"
msgstr ""
@@ -126,6 +129,9 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr ""
+
msgid "%{loadingIcon} Started"
msgstr ""
@@ -182,6 +188,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "(external source)"
+msgstr ""
+
msgid "+ %{count} more"
msgstr ""
@@ -282,7 +291,7 @@ msgstr ""
msgid "<strong>%{group_name}</strong> group members"
msgstr ""
-msgid "<strong>Removes</strong> source branch"
+msgid "<strong>Deletes</strong> source branch"
msgstr ""
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
@@ -369,6 +378,9 @@ msgstr ""
msgid "Add README"
msgstr ""
+msgid "Add a general comment to this %{noteable_name}."
+msgstr ""
+
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
@@ -390,6 +402,9 @@ msgstr ""
msgid "Add reaction"
msgstr ""
+msgid "Add to project"
+msgstr ""
+
msgid "Add todo"
msgstr ""
@@ -441,6 +456,9 @@ msgstr ""
msgid "AdminProjects|Delete project"
msgstr ""
+msgid "AdminSettings|Auto DevOps domain"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -510,6 +528,9 @@ msgstr ""
msgid "Allow users to request access if visibility is public or internal."
msgstr ""
+msgid "Allowed to fail"
+msgstr ""
+
msgid "Allows you to add and manage Kubernetes clusters."
msgstr ""
@@ -702,6 +723,9 @@ msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
+msgid "Are you sure you want to remove the attachment?"
+msgstr ""
+
msgid "Are you sure you want to remove this identity?"
msgstr ""
@@ -714,6 +738,9 @@ msgstr ""
msgid "Are you sure you want to stop this environment?"
msgstr ""
+msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
+msgstr ""
+
msgid "Are you sure?"
msgstr ""
@@ -756,6 +783,9 @@ msgstr ""
msgid "Assignee(s)"
msgstr ""
+msgid "Attach a file"
+msgstr ""
+
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
@@ -765,6 +795,9 @@ msgstr ""
msgid "August"
msgstr ""
+msgid "Auth Token"
+msgstr ""
+
msgid "Authentication Log"
msgstr ""
@@ -855,10 +888,7 @@ msgstr ""
msgid "Available"
msgstr ""
-msgid "Available group Runners : %{runners}"
-msgstr ""
-
-msgid "Available group Runners : %{runners}."
+msgid "Available group Runners: %{runners}"
msgstr ""
msgid "Available shared Runners:"
@@ -1152,6 +1182,9 @@ msgstr ""
msgid "CI / CD Settings"
msgstr ""
+msgid "CI Lint"
+msgstr ""
+
msgid "CI/CD"
msgstr ""
@@ -1260,6 +1293,9 @@ msgstr ""
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
+msgid "Check your .gitlab-ci.yml"
+msgstr ""
+
msgid "Checking %{text} availability…"
msgstr ""
@@ -1287,6 +1323,9 @@ msgstr ""
msgid "Choose a file"
msgstr ""
+msgid "Choose a role permission"
+msgstr ""
+
msgid "Choose a template..."
msgstr ""
@@ -1398,6 +1437,9 @@ msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Clear"
+msgstr ""
+
msgid "Clear search"
msgstr ""
@@ -1446,6 +1488,9 @@ msgstr ""
msgid "Closed"
msgstr ""
+msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
+msgstr ""
+
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
@@ -1842,6 +1887,9 @@ msgstr ""
msgid "ClusterIntegration|You must first install Helm Tiller before installing the applications below"
msgstr ""
+msgid "ClusterIntegration|You must have an RBAC-enabled cluster to install Knative."
+msgstr ""
+
msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}"
msgstr ""
@@ -1869,6 +1917,9 @@ msgstr ""
msgid "ClusterIntegration|sign up"
msgstr ""
+msgid "Code"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
@@ -1884,6 +1935,12 @@ msgstr ""
msgid "Comment"
msgstr ""
+msgid "Comment & close %{noteable_name}"
+msgstr ""
+
+msgid "Comment & reopen %{noteable_name}"
+msgstr ""
+
msgid "Comment & resolve discussion"
msgstr ""
@@ -1901,9 +1958,15 @@ msgid_plural "Commits"
msgstr[0] ""
msgstr[1] ""
+msgid "Commit %{commit_id}"
+msgstr ""
+
msgid "Commit Message"
msgstr ""
+msgid "Commit deleted"
+msgstr ""
+
msgid "Commit duration in minutes for last 30 commits"
msgstr ""
@@ -1967,6 +2030,9 @@ msgstr ""
msgid "Compare Revisions"
msgstr ""
+msgid "Compare changes"
+msgstr ""
+
msgid "Compare changes with the last commit"
msgstr ""
@@ -2066,6 +2132,9 @@ msgstr ""
msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access to the registry images."
msgstr ""
+msgid "Contents of .gitlab-ci.yml"
+msgstr ""
+
msgid "Continue"
msgstr ""
@@ -2381,6 +2450,9 @@ msgstr ""
msgid "Delete list"
msgstr ""
+msgid "Delete this attachment"
+msgstr ""
+
msgid "Deleted"
msgstr ""
@@ -2593,6 +2665,12 @@ msgstr ""
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr ""
+msgid "Discuss a specific suggestion or question"
+msgstr ""
+
+msgid "Discuss a specific suggestion or question that needs to be resolved"
+msgstr ""
+
msgid "Dismiss"
msgstr ""
@@ -2614,6 +2692,9 @@ msgstr ""
msgid "Download"
msgstr ""
+msgid "Download artifacts"
+msgstr ""
+
msgid "Download asset"
msgstr ""
@@ -2680,6 +2761,9 @@ msgstr ""
msgid "Edit identity for %{user_name}"
msgstr ""
+msgid "Edit issues"
+msgstr ""
+
msgid "Email"
msgstr ""
@@ -2710,6 +2794,9 @@ msgstr ""
msgid "Enable and configure Prometheus metrics."
msgstr ""
+msgid "Enable error tracking"
+msgstr ""
+
msgid "Enable for this project"
msgstr ""
@@ -2728,6 +2815,9 @@ msgstr ""
msgid "Enable the Performance Bar for a given group."
msgstr ""
+msgid "Enable two-factor authentication"
+msgstr ""
+
msgid "Enable usage ping"
msgstr ""
@@ -2755,6 +2845,9 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
+msgid "Enter your Sentry API URL"
+msgstr ""
+
msgid "Environment variables"
msgstr ""
@@ -2764,6 +2857,9 @@ msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
msgstr ""
+msgid "Environment:"
+msgstr ""
+
msgid "Environments"
msgstr ""
@@ -2857,6 +2953,9 @@ msgstr ""
msgid "Error Reporting and Logging"
msgstr ""
+msgid "Error Tracking"
+msgstr ""
+
msgid "Error fetching contributors data."
msgstr ""
@@ -2899,6 +2998,9 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error rendering markdown preview"
+msgstr ""
+
msgid "Error saving label update."
msgstr ""
@@ -2911,6 +3013,12 @@ msgstr ""
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error:"
+msgstr ""
+
+msgid "Errors"
+msgstr ""
+
msgid "Estimated"
msgstr ""
@@ -2932,6 +3040,9 @@ msgstr ""
msgid "EventFilterBy|Filter by team"
msgstr ""
+msgid "Events"
+msgstr ""
+
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
msgstr ""
@@ -2950,12 +3061,18 @@ msgstr ""
msgid "Everyone can contribute"
msgstr ""
+msgid "Except policy:"
+msgstr ""
+
msgid "Existing Git repository"
msgstr ""
msgid "Existing folder"
msgstr ""
+msgid "Existing members and groups"
+msgstr ""
+
msgid "Expand"
msgstr ""
@@ -3019,6 +3136,9 @@ msgstr ""
msgid "Failed to load emoji list."
msgstr ""
+msgid "Failed to load errors from Sentry"
+msgstr ""
+
msgid "Failed to remove issue from board, please try again."
msgstr ""
@@ -3052,6 +3172,9 @@ msgstr ""
msgid "File added"
msgstr ""
+msgid "File browser"
+msgstr ""
+
msgid "File deleted"
msgstr ""
@@ -3064,6 +3187,9 @@ msgstr ""
msgid "File templates"
msgstr ""
+msgid "File upload error."
+msgstr ""
+
msgid "Files"
msgstr ""
@@ -3073,15 +3199,30 @@ msgstr ""
msgid "Filter by commit message"
msgstr ""
+msgid "Filter by milestone name"
+msgstr ""
+
msgid "Filter by two-factor authentication"
msgstr ""
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
msgid "Filter..."
msgstr ""
+msgid "Find and manage Auth Tokens in your Sentry account settings page."
+msgstr ""
+
msgid "Find by path"
msgstr ""
+msgid "Find existing members by name"
+msgstr ""
+
msgid "Find file"
msgstr ""
@@ -3094,6 +3235,9 @@ msgstr ""
msgid "Fingerprints"
msgstr ""
+msgid "Finish editing this message first!"
+msgstr ""
+
msgid "Finished"
msgstr ""
@@ -3193,6 +3337,9 @@ msgstr ""
msgid "Geo"
msgstr ""
+msgid "Get started with error tracking"
+msgstr ""
+
msgid "Getting started with releases"
msgstr ""
@@ -3328,6 +3475,9 @@ msgstr ""
msgid "Group name"
msgstr ""
+msgid "Group:"
+msgstr ""
+
msgid "Group: %{group_name}"
msgstr ""
@@ -3367,6 +3517,9 @@ msgstr ""
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
+msgid "Groups with access to <strong>%{project_name}</strong>"
+msgstr ""
+
msgid "GroupsDropdown|Frequently visited"
msgstr ""
@@ -3460,6 +3613,9 @@ msgstr ""
msgid "Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation."
msgstr ""
+msgid "Hide file browser"
+msgstr ""
+
msgid "Hide host keys manual input"
msgstr ""
@@ -3474,9 +3630,6 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
-msgid "Hide whitespace changes"
-msgstr ""
-
msgid "History"
msgstr ""
@@ -3570,6 +3723,9 @@ msgstr ""
msgid "Import"
msgstr ""
+msgid "Import CSV"
+msgstr ""
+
msgid "Import Projects from Gitea"
msgstr ""
@@ -3588,12 +3744,24 @@ msgstr ""
msgid "Import in progress"
msgstr ""
+msgid "Import issues"
+msgstr ""
+
+msgid "Import members"
+msgstr ""
+
+msgid "Import members from another project"
+msgstr ""
+
msgid "Import multiple repositories by uploading a manifest file."
msgstr ""
msgid "Import project"
msgstr ""
+msgid "Import project members"
+msgstr ""
+
msgid "Import projects from Bitbucket"
msgstr ""
@@ -3699,6 +3867,12 @@ msgstr ""
msgid "Invite"
msgstr ""
+msgid "Invite group"
+msgstr ""
+
+msgid "Invite member"
+msgstr ""
+
msgid "Invoke Count"
msgstr ""
@@ -3726,6 +3900,9 @@ msgstr ""
msgid "Issues, merge requests, pushes and comments."
msgstr ""
+msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
+msgstr ""
+
msgid "It's you"
msgstr ""
@@ -3741,6 +3918,12 @@ msgstr ""
msgid "Job has been erased"
msgstr ""
+msgid "Job is stuck. Check runners."
+msgstr ""
+
+msgid "Job was retried"
+msgstr ""
+
msgid "Jobs"
msgstr ""
@@ -3890,6 +4073,9 @@ msgstr ""
msgid "Last reply by"
msgstr ""
+msgid "Last seen"
+msgstr ""
+
msgid "Last update"
msgstr ""
@@ -4033,6 +4219,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage two-factor authentication"
+msgstr ""
+
msgid "Manifest"
msgstr ""
@@ -4060,6 +4249,9 @@ msgstr ""
msgid "Mark todo as done"
msgstr ""
+msgid "Markdown"
+msgstr ""
+
msgid "Markdown enabled"
msgstr ""
@@ -4111,6 +4303,12 @@ msgstr ""
msgid "Members"
msgstr ""
+msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
+msgstr ""
+
+msgid "Members of <strong>%{project_name}</strong>"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -4144,6 +4342,9 @@ msgstr ""
msgid "MergeRequests|View replaced file @ %{commitId}"
msgstr ""
+msgid "MergeRequests|commented on commit %{commitLink}"
+msgstr ""
+
msgid "MergeRequests|started a discussion"
msgstr ""
@@ -4276,6 +4477,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Monitor your errors by integrating with Sentry"
+msgstr ""
+
msgid "Monitoring"
msgstr ""
@@ -4440,9 +4644,15 @@ msgstr ""
msgid "No contributions were found"
msgstr ""
+msgid "No details available"
+msgstr ""
+
msgid "No due date"
msgstr ""
+msgid "No errors to display"
+msgstr ""
+
msgid "No estimate or time spent"
msgstr ""
@@ -4646,9 +4856,15 @@ msgstr ""
msgid "Only mirror protected branches"
msgstr ""
+msgid "Only policy:"
+msgstr ""
+
msgid "Only project members can comment."
msgstr ""
+msgid "Only project members will be imported. Group members will be skipped."
+msgstr ""
+
msgid "Oops, are you sure?"
msgstr ""
@@ -4658,6 +4874,12 @@ msgstr ""
msgid "Open Documentation"
msgstr ""
+msgid "Open comment type dropdown"
+msgstr ""
+
+msgid "Open errors"
+msgstr ""
+
msgid "Open in Xcode"
msgstr ""
@@ -4724,6 +4946,9 @@ msgstr ""
msgid "Pagination|« First"
msgstr ""
+msgid "Parameter"
+msgstr ""
+
msgid "Part of merge request changes"
msgstr ""
@@ -4766,6 +4991,9 @@ msgstr ""
msgid "Personal Access Token"
msgstr ""
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
msgid "Pick a name"
msgstr ""
@@ -4952,6 +5180,9 @@ msgstr ""
msgid "Play"
msgstr ""
+msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
+msgstr ""
+
msgid "Please accept the Terms of Service before continuing."
msgstr ""
@@ -5042,6 +5273,12 @@ msgstr ""
msgid "Profiles|Account scheduled for removal."
msgstr ""
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
msgid "Profiles|Add key"
msgstr ""
@@ -5057,6 +5294,9 @@ msgstr ""
msgid "Profiles|Change username"
msgstr ""
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
msgid "Profiles|Choose file..."
msgstr ""
@@ -5069,6 +5309,15 @@ msgstr ""
msgid "Profiles|Clear status"
msgstr ""
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
msgid "Profiles|Current path: %{path}"
msgstr ""
@@ -5087,6 +5336,9 @@ msgstr ""
msgid "Profiles|Deleting an account has the following effects:"
msgstr ""
+msgid "Profiles|Disconnect"
+msgstr ""
+
msgid "Profiles|Do not show on profile"
msgstr ""
@@ -5099,6 +5351,9 @@ msgstr ""
msgid "Profiles|Enter your name, so people you know can recognize you"
msgstr ""
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
msgid "Profiles|Invalid password"
msgstr ""
@@ -5135,6 +5390,9 @@ msgstr ""
msgid "Profiles|Set new profile picture"
msgstr ""
+msgid "Profiles|Social sign-in"
+msgstr ""
+
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr ""
@@ -5162,6 +5420,9 @@ msgstr ""
msgid "Profiles|This information will appear on your profile"
msgstr ""
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr ""
@@ -5294,12 +5555,18 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project members"
+msgstr ""
+
msgid "Project name"
msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project:"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -5814,6 +6081,9 @@ msgstr ""
msgid "Save changes"
msgstr ""
+msgid "Save comment"
+msgstr ""
+
msgid "Save pipeline schedule"
msgstr ""
@@ -5859,6 +6129,9 @@ msgstr ""
msgid "Search for projects, issues, etc."
msgstr ""
+msgid "Search groups"
+msgstr ""
+
msgid "Search merge requests"
msgstr ""
@@ -5874,6 +6147,9 @@ msgstr ""
msgid "Search project"
msgstr ""
+msgid "Search projects"
+msgstr ""
+
msgid "Search users"
msgstr ""
@@ -5934,6 +6210,9 @@ msgstr ""
msgid "Select branch/tag"
msgstr ""
+msgid "Select members to invite"
+msgstr ""
+
msgid "Select project"
msgstr ""
@@ -5967,6 +6246,9 @@ msgstr ""
msgid "Send usage data"
msgstr ""
+msgid "Sentry API URL"
+msgstr ""
+
msgid "Sep"
msgstr ""
@@ -5979,13 +6261,31 @@ msgstr ""
msgid "Serverless"
msgstr ""
+msgid "ServerlessDetails|Copy URL to clipboard"
+msgstr ""
+
+msgid "ServerlessDetails|Kubernetes Pods"
+msgstr ""
+
+msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
+msgstr ""
+
+msgid "ServerlessDetails|pod in use"
+msgstr ""
+
+msgid "ServerlessDetails|pods in use"
+msgstr ""
+
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
msgid "Serverless|An error occurred while retrieving serverless components"
msgstr ""
-msgid "Serverless|Domain"
+msgid "Serverless|Cluster Env"
+msgstr ""
+
+msgid "Serverless|Description"
msgstr ""
msgid "Serverless|Function"
@@ -6099,6 +6399,9 @@ msgstr ""
msgid "Show complete raw log"
msgstr ""
+msgid "Show file browser"
+msgstr ""
+
msgid "Show latest version"
msgstr ""
@@ -6146,6 +6449,9 @@ msgstr ""
msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
msgstr ""
+msgid "Snippet Contents"
+msgstr ""
+
msgid "Snippets"
msgstr ""
@@ -6374,6 +6680,15 @@ msgstr ""
msgid "Start date"
msgstr ""
+msgid "Start discussion"
+msgstr ""
+
+msgid "Start discussion & close %{noteable_name}"
+msgstr ""
+
+msgid "Start discussion & reopen %{noteable_name}"
+msgstr ""
+
msgid "Start the Runner!"
msgstr ""
@@ -6392,6 +6707,9 @@ msgstr ""
msgid "Status"
msgstr ""
+msgid "Status:"
+msgstr ""
+
msgid "Stop environment"
msgstr ""
@@ -6422,6 +6740,9 @@ msgstr ""
msgid "Submit as spam"
msgstr ""
+msgid "Submit feedback"
+msgstr ""
+
msgid "Submit search"
msgstr ""
@@ -6434,6 +6755,12 @@ msgstr ""
msgid "Subscribe at project level"
msgstr ""
+msgid "Subscribe to RSS feed"
+msgstr ""
+
+msgid "Subscribe to calendar"
+msgstr ""
+
msgid "Subscribed"
msgstr ""
@@ -6458,6 +6785,9 @@ msgstr ""
msgid "Tag"
msgstr ""
+msgid "Tag list:"
+msgstr ""
+
msgid "Tags"
msgstr ""
@@ -6593,7 +6923,7 @@ msgstr ""
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
msgstr ""
-msgid "The maximum file size allowed is %{max_attachment_size}mb"
+msgid "The maximum file size allowed is %{size}."
msgstr ""
msgid "The maximum file size allowed is 200KB."
@@ -6665,12 +6995,24 @@ msgstr ""
msgid "There are no archived projects yet"
msgstr ""
+msgid "There are no closed issues"
+msgstr ""
+
+msgid "There are no closed merge requests"
+msgstr ""
+
msgid "There are no issues to show"
msgstr ""
msgid "There are no labels yet"
msgstr ""
+msgid "There are no open issues"
+msgstr ""
+
+msgid "There are no open merge requests"
+msgstr ""
+
msgid "There are no projects shared with this group yet"
msgstr ""
@@ -6707,6 +7049,9 @@ msgstr ""
msgid "Third party offers"
msgstr ""
+msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
+msgstr ""
+
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
@@ -6833,6 +7178,9 @@ msgstr ""
msgid "This pipeline is run in a merge request context"
msgstr ""
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
+msgstr ""
+
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
msgstr ""
@@ -7056,6 +7404,9 @@ msgstr ""
msgid "Title"
msgstr ""
+msgid "Titles and Filenames"
+msgstr ""
+
msgid "To GitLab"
msgstr ""
@@ -7089,10 +7440,19 @@ msgstr ""
msgid "To import an SVN repository, check out %{svn_link}."
msgstr ""
+msgid "To keep this project going, create a new issue"
+msgstr ""
+
+msgid "To keep this project going, create a new merge request"
+msgstr ""
+
+msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
+msgstr ""
+
msgid "To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here."
msgstr ""
-msgid "To preserve performance only <strong>%{display_size} of ${real_size}</strong> files are displayed."
+msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
msgstr ""
msgid "To start serving your jobs you can add Runners to your group"
@@ -7128,10 +7488,10 @@ msgstr ""
msgid "Toggle commit description"
msgstr ""
-msgid "Toggle discussion"
+msgid "Toggle commit list"
msgstr ""
-msgid "Toggle file browser"
+msgid "Toggle discussion"
msgstr ""
msgid "Toggle navigation"
@@ -7254,6 +7614,9 @@ msgstr ""
msgid "Unsubscribe at project level"
msgstr ""
+msgid "Unsubscribe from %{type}"
+msgstr ""
+
msgid "Unverified"
msgstr ""
@@ -7278,6 +7641,9 @@ msgstr ""
msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr ""
+msgid "Upload CSV file"
+msgstr ""
+
msgid "Upload New File"
msgstr ""
@@ -7362,6 +7728,9 @@ msgstr ""
msgid "UserProfile|Subscribe"
msgstr ""
+msgid "UserProfile|This user doesn't have any personal projects"
+msgstr ""
+
msgid "UserProfile|This user has a private profile"
msgstr ""
@@ -7377,6 +7746,15 @@ msgstr ""
msgid "Users requesting access to"
msgstr ""
+msgid "Validate"
+msgstr ""
+
+msgid "Validate your GitLab CI configuration file"
+msgstr ""
+
+msgid "Value"
+msgstr ""
+
msgid "Various container registry settings."
msgstr ""
@@ -7461,6 +7839,9 @@ msgstr ""
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
+msgid "We couldn't find any results matching"
+msgstr ""
+
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -7479,12 +7860,18 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "What's new?"
+msgstr ""
+
msgid "When a runner is locked, it cannot be assigned to other projects"
msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "When:"
+msgstr ""
+
msgid "Who can see this group?"
msgstr ""
@@ -7632,6 +8019,9 @@ msgstr ""
msgid "Withdraw Access Request"
msgstr ""
+msgid "Write a comment or drag your files here…"
+msgstr ""
+
msgid "Yes"
msgstr ""
@@ -7680,6 +8070,9 @@ msgstr ""
msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
msgstr ""
+msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
+msgstr ""
+
msgid "You can move around the graph by using the arrow keys."
msgstr ""
@@ -7719,6 +8112,9 @@ msgstr ""
msgid "You have reached your project limit"
msgstr ""
+msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
+msgstr ""
+
msgid "You must accept our Terms of Service and privacy policy in order to register an account"
msgstr ""
@@ -7731,10 +8127,10 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
-msgid "You will loose all changes you've made to this file. This action cannot be undone."
+msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
-msgid "You will loose all the unstaged changes you've made in this project. This action cannot be undone."
+msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
msgid "You will not get any notifications via email"
@@ -7809,6 +8205,9 @@ msgstr ""
msgid "Your changes have been committed. Commit %{commitId} %{commitStats}"
msgstr ""
+msgid "Your changes have been saved"
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
@@ -7818,9 +8217,18 @@ msgstr ""
msgid "Your groups"
msgstr ""
+msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
+msgstr ""
+
+msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
+msgstr ""
+
msgid "Your name"
msgstr ""
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
msgid "Your projects"
msgstr ""
@@ -7830,18 +8238,27 @@ msgstr ""
msgid "ago"
msgstr ""
+msgid "allowed to fail"
+msgstr ""
+
msgid "among other things"
msgstr ""
msgid "assign yourself"
msgstr ""
+msgid "attach a new file"
+msgstr ""
+
msgid "branch name"
msgstr ""
msgid "command line instructions"
msgstr ""
+msgid "commented on %{link_to_project}"
+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 ""
@@ -7879,6 +8296,9 @@ msgstr ""
msgid "enabled"
msgstr ""
+msgid "error"
+msgstr ""
+
msgid "error code:"
msgstr ""
@@ -7897,6 +8317,9 @@ msgstr ""
msgid "here"
msgstr ""
+msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
+msgstr ""
+
msgid "https://your-bitbucket-server"
msgstr ""
@@ -7909,7 +8332,10 @@ msgstr ""
msgid "importing"
msgstr ""
-msgid "in"
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
msgstr ""
msgid "issue boards"
@@ -7924,12 +8350,18 @@ msgstr ""
msgid "it is too large"
msgstr ""
+msgid "latest"
+msgstr ""
+
msgid "latest deployment"
msgstr ""
msgid "latest version"
msgstr ""
+msgid "manual"
+msgstr ""
+
msgid "merge request"
msgid_plural "merge requests"
msgstr[0] ""
@@ -7938,6 +8370,9 @@ msgstr[1] ""
msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
msgstr ""
+msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
+msgstr ""
+
msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
msgstr ""
@@ -7977,6 +8412,9 @@ msgstr ""
msgid "mrWidget|Create an issue to resolve them later"
msgstr ""
+msgid "mrWidget|Delete source branch"
+msgstr ""
+
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
@@ -8037,12 +8475,6 @@ msgstr ""
msgid "mrWidget|Refreshing now"
msgstr ""
-msgid "mrWidget|Remove Source Branch"
-msgstr ""
-
-msgid "mrWidget|Remove source branch"
-msgstr ""
-
msgid "mrWidget|Request to merge"
msgstr ""
@@ -8076,19 +8508,19 @@ msgstr ""
msgid "mrWidget|The source branch HEAD has recently changed. Please reload the page and review the changes before merging"
msgstr ""
-msgid "mrWidget|The source branch has been removed"
+msgid "mrWidget|The source branch has been deleted"
msgstr ""
msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
msgstr ""
-msgid "mrWidget|The source branch is being removed"
+msgid "mrWidget|The source branch is being deleted"
msgstr ""
-msgid "mrWidget|The source branch will be removed"
+msgid "mrWidget|The source branch will be deleted"
msgstr ""
-msgid "mrWidget|The source branch will not be removed"
+msgid "mrWidget|The source branch will not be deleted"
msgstr ""
msgid "mrWidget|There are merge conflicts"
@@ -8097,6 +8529,9 @@ msgstr ""
msgid "mrWidget|There are unresolved discussions. Please resolve these discussions"
msgstr ""
+msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
+msgstr ""
+
msgid "mrWidget|This merge request failed to be merged automatically"
msgstr ""
@@ -8109,10 +8544,10 @@ msgstr ""
msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
msgstr ""
-msgid "mrWidget|You can merge this merge request manually using the"
+msgid "mrWidget|You can delete the source branch now"
msgstr ""
-msgid "mrWidget|You can remove source branch now"
+msgid "mrWidget|You can merge this merge request manually using the"
msgstr ""
msgid "mrWidget|branch does not exist."
@@ -8133,6 +8568,9 @@ msgstr ""
msgid "new merge request"
msgstr ""
+msgid "none"
+msgstr ""
+
msgid "notification emails"
msgstr ""
@@ -8155,9 +8593,18 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "private"
+msgstr ""
+
msgid "project"
msgstr ""
+msgid "quick actions"
+msgstr ""
+
+msgid "register"
+msgstr ""
+
msgid "remaining"
msgstr ""
@@ -8175,6 +8622,12 @@ msgstr[1] ""
msgid "should be higher than %{access} inherited membership from group %{group_name}"
msgstr ""
+msgid "show less"
+msgstr ""
+
+msgid "sign in"
+msgstr ""
+
msgid "source"
msgstr ""
@@ -8187,9 +8640,21 @@ msgstr ""
msgid "started"
msgstr ""
+msgid "stuck"
+msgstr ""
+
+msgid "syntax is correct"
+msgstr ""
+
+msgid "syntax is incorrect"
+msgstr ""
+
msgid "this document"
msgstr ""
+msgid "triggered"
+msgstr ""
+
msgid "updated"
msgstr ""
@@ -8212,3 +8677,6 @@ msgid "within %d minute "
msgid_plural "within %d minutes "
msgstr[0] ""
msgstr[1] ""
+
+msgid "yaml invalid"
+msgstr ""
diff --git a/package.json b/package.json
index 1dc9797c59b..82c15f90745 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"eslint-fix": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .",
"jest": "BABEL_ENV=jest jest",
+ "jsdoc": "jsdoc -c config/jsdocs.config.js",
"karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js",
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
@@ -19,23 +20,24 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
- "@babel/core": "^7.1.2",
- "@babel/plugin-proposal-class-properties": "^7.1.0",
- "@babel/plugin-proposal-json-strings": "^7.0.0",
- "@babel/plugin-syntax-dynamic-import": "^7.0.0",
- "@babel/plugin-syntax-import-meta": "^7.0.0",
- "@babel/preset-env": "^7.1.0",
+ "@babel/core": "^7.2.2",
+ "@babel/plugin-proposal-class-properties": "^7.3.0",
+ "@babel/plugin-proposal-json-strings": "^7.2.0",
+ "@babel/plugin-proposal-private-methods": "^7.3.0",
+ "@babel/plugin-syntax-dynamic-import": "^7.2.0",
+ "@babel/plugin-syntax-import-meta": "^7.2.0",
+ "@babel/preset-env": "^7.3.1",
"@gitlab/csslab": "^1.8.0",
- "@gitlab/svgs": "^1.43.0",
- "@gitlab/ui": "^1.18.0",
+ "@gitlab/svgs": "^1.47.0",
+ "@gitlab/ui": "^1.20.0",
"apollo-boost": "^0.1.20",
"apollo-client": "^2.4.5",
"autosize": "^4.0.0",
"axios": "^0.17.1",
- "babel-loader": "^8.0.4",
+ "babel-loader": "^8.0.5",
"bootstrap": "4.1.3",
"brace-expansion": "^1.1.8",
- "cache-loader": "^1.2.2",
+ "cache-loader": "^2.0.1",
"chart.js": "1.0.2",
"classlist-polyfill": "^1.2.0",
"clipboard": "^1.7.1",
@@ -54,14 +56,14 @@
"d3-time": "^1.0.8",
"d3-time-format": "^2.1.1",
"dateformat": "^3.0.3",
- "deckar01-task_list": "^2.0.0",
+ "deckar01-task_list": "^2.0.1",
"diff": "^3.4.0",
"document-register-element": "1.3.0",
"dropzone": "^4.2.0",
"echarts": "^4.2.0-rc.2",
"emoji-unicode-version": "^0.2.1",
"exports-loader": "^0.7.0",
- "file-loader": "^2.0.0",
+ "file-loader": "^3.0.1",
"formdata-polyfill": "^3.0.11",
"fuzzaldrin-plus": "^0.5.0",
"glob": "^7.1.2",
@@ -74,45 +76,50 @@
"js-cookie": "^2.1.3",
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
- "katex": "^0.9.0",
+ "katex": "^0.10.0",
"marked": "^0.3.12",
"mermaid": "^8.0.0-rc.8",
- "monaco-editor": "^0.14.3",
- "monaco-editor-webpack-plugin": "^1.5.4",
+ "monaco-editor": "^0.15.6",
+ "monaco-editor-webpack-plugin": "^1.7.0",
"mousetrap": "^1.4.6",
"pikaday": "^1.6.1",
"popper.js": "^1.14.3",
"prismjs": "^1.6.0",
+ "prosemirror-markdown": "^1.3.0",
+ "prosemirror-model": "^1.6.4",
"raphael": "^2.2.7",
"raven-js": "^3.22.1",
- "raw-loader": "^0.5.1",
+ "raw-loader": "^1.0.0",
"sanitize-html": "^1.16.1",
"select2": "3.5.2-browserify",
"sha1": "^1.1.1",
- "smooshpack": "^0.0.53",
+ "smooshpack": "^0.0.54",
"sortablejs": "^1.7.0",
"sql.js": "^0.4.0",
"stickyfilljs": "^2.0.5",
- "style-loader": "^0.23.0",
+ "style-loader": "^0.23.1",
"svg4everybody": "2.1.9",
"three": "^0.84.0",
"three-orbit-controls": "^82.1.0",
"three-stl-loader": "^1.0.4",
"timeago.js": "^3.0.2",
+ "tiptap": "^1.8.0",
+ "tiptap-commands": "^1.4.0",
+ "tiptap-extensions": "^1.8.0",
"underscore": "^1.9.0",
- "url-loader": "^1.1.1",
+ "url-loader": "^1.1.2",
"visibilityjs": "^1.2.4",
- "vue": "^2.5.17",
+ "vue": "^2.5.21",
"vue-apollo": "^3.0.0-beta.25",
"vue-loader": "^15.4.2",
- "vue-resource": "^1.5.0",
- "vue-router": "^3.0.1",
- "vue-template-compiler": "^2.5.17",
+ "vue-resource": "^1.5.1",
+ "vue-router": "^3.0.2",
+ "vue-template-compiler": "^2.5.21",
"vue-virtual-scroll-list": "^1.2.5",
"vuex": "^3.0.1",
- "webpack": "^4.19.1",
- "webpack-bundle-analyzer": "^3.0.2",
- "webpack-cli": "^3.1.0",
+ "webpack": "^4.29.0",
+ "webpack-bundle-analyzer": "^3.0.3",
+ "webpack-cli": "^3.2.1",
"webpack-stats-plugin": "^0.2.1",
"worker-loader": "^2.0.0",
"xterm": "^3.5.0"
@@ -131,6 +138,7 @@
"babel-types": "^6.26.0",
"chalk": "^2.4.1",
"commander": "^2.18.0",
+ "docdash": "^1.0.2",
"eslint": "~5.9.0",
"eslint-import-resolver-jest": "^2.1.1",
"eslint-import-resolver-webpack": "^0.10.1",
@@ -147,6 +155,8 @@
"jasmine-jquery": "^2.1.1",
"jest": "^23.6.0",
"jest-junit": "^5.2.0",
+ "jsdoc": "^3.5.5",
+ "jsdoc-vue": "^1.0.0",
"karma": "^3.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage-istanbul-reporter": "^2.0.4",
@@ -155,13 +165,15 @@
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.0-beta.0",
- "nodemon": "^1.18.4",
- "prettier": "1.15.2",
- "vue-jest": "^3.0.1",
- "webpack-dev-server": "^3.1.10",
- "yarn-deduplicate": "^1.0.5"
+ "nodemon": "^1.18.9",
+ "pixelmatch": "^4.0.2",
+ "prettier": "1.16.1",
+ "vue-jest": "^3.0.2",
+ "webpack-dev-server": "^3.1.14",
+ "yarn-deduplicate": "^1.1.0"
},
"engines": {
+ "node": ">=8.10.0",
"yarn": "^1.10.0"
}
}
diff --git a/qa/Gemfile b/qa/Gemfile
index 75ad7bd07af..873eac1013f 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -7,4 +7,4 @@ gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.12'
gem 'airborne', '~> 0.2.13'
-gem 'nokogiri', '~> 1.8.5'
+gem 'nokogiri', '~> 1.10.1'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 55f3211482b..419cacdb2af 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -44,11 +44,11 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mini_mime (1.0.0)
- mini_portile2 (2.3.0)
+ mini_portile2 (2.4.0)
minitest (5.11.1)
netrc (0.11.0)
- nokogiri (1.8.5)
- mini_portile2 (~> 2.3.0)
+ nokogiri (1.10.1)
+ mini_portile2 (~> 2.4.0)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
@@ -97,7 +97,7 @@ DEPENDENCIES
airborne (~> 0.2.13)
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
- nokogiri (~> 1.8.5)
+ nokogiri (~> 1.10.0)
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rspec (~> 3.7)
diff --git a/qa/README.md b/qa/README.md
index 08ba59e117d..5e32496ea9f 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -86,7 +86,7 @@ The environment variable `QA_COOKIES` can be set to send additional cookies
on every request. This is necessary on gitlab.com to direct traffic to the
canary fleet. To do this set `QA_COOKIES="gitlab_canary=true"`.
-To set multiple cookies, separate them with the `;` character, for example: `QA_COOKIES="cookie1=value;cookie2=value2"`
+To set multiple cookies, separate them with the `;` character, for example: `QA_COOKIES="cookie1=value;cookie2=value2"`
### Building a Docker image to test
@@ -100,3 +100,24 @@ docker build -t gitlab/gitlab-ce-qa:nightly .
```
[GDK]: https://gitlab.com/gitlab-org/gitlab-development-kit/
+
+### Quarantined tests
+
+Tests can be put in quarantine by assigning `:quarantine` metadata. This means
+they will be skipped unless run with `--tag quarantine`. This can be used for
+tests that are expected to fail while a fix is in progress (similar to how
+[`skip` or `pending`](https://relishapp.com/rspec/rspec-core/v/3-8/docs/pending-and-skipped-examples)
+ can be used).
+
+```
+bin/qa Test::Instance::All http://localhost --tag quarantine
+```
+
+If `quarantine` is used with other tags, tests will only be run if they have at
+least one of the tags other than `quarantine`. This is different from how RSpec
+tags usually work, where all tags are inclusive.
+
+For example, suppose one test has `:smoke` and `:quarantine` metadata, and
+another test has `:ldap` and `:quarantine` metadata. If the tests are run with
+`--tag smoke --tag quarantine`, only the first test will run. The test with
+`:ldap` will not run even though it also has `:quarantine`.
diff --git a/qa/Rakefile b/qa/Rakefile
new file mode 100644
index 00000000000..8df1cfdc174
--- /dev/null
+++ b/qa/Rakefile
@@ -0,0 +1,6 @@
+require_relative 'qa/tools/revoke_all_personal_access_tokens'
+
+desc "Revokes all personal access tokens"
+task :revoke_personal_access_tokens do
+ QA::Tools::RevokeAllPersonalAccessTokens.new.run
+end
diff --git a/qa/qa.rb b/qa/qa.rb
index ef6a92f9768..7aaf56bd51f 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -158,6 +158,10 @@ module QA
autoload :Activity, 'qa/page/project/activity'
autoload :Menu, 'qa/page/project/menu'
+ module Branches
+ autoload :Show, 'qa/page/project/branches/show'
+ end
+
module Commit
autoload :Show, 'qa/page/project/commit/show'
end
@@ -191,6 +195,15 @@ module QA
autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories'
end
+ module SubMenus
+ autoload :CiCd, 'qa/page/project/sub_menus/ci_cd'
+ autoload :Common, 'qa/page/project/sub_menus/common'
+ autoload :Issues, 'qa/page/project/sub_menus/issues'
+ autoload :Operations, 'qa/page/project/sub_menus/operations'
+ autoload :Repository, 'qa/page/project/sub_menus/repository'
+ autoload :Settings, 'qa/page/project/sub_menus/settings'
+ end
+
module Issue
autoload :New, 'qa/page/project/issue/new'
autoload :Show, 'qa/page/project/issue/show'
diff --git a/qa/qa/fixtures/auto_devops_rack/config.ru b/qa/qa/fixtures/auto_devops_rack/config.ru
index bde8e15488a..e990662145a 100644
--- a/qa/qa/fixtures/auto_devops_rack/config.ru
+++ b/qa/qa/fixtures/auto_devops_rack/config.ru
@@ -1 +1 @@
-run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, StringIO.new("Hello World!\n")] }
+run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, StringIO.new("Hello World! #{ENV['OPTIONAL_MESSAGE']}\n")] }
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 7f959441dac..ac8dcbf0d83 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -11,14 +11,15 @@ module QA
class Repository
include Scenario::Actable
- attr_writer :password
+ attr_writer :password, :use_lfs
attr_accessor :env_vars
def initialize
# We set HOME to the current working directory (which is a
# temporary directory created in .perform()) so the temporarily dropped
# .netrc can be utilised
- self.env_vars = [%Q{HOME="#{File.dirname(netrc_file_path)}"}]
+ self.env_vars = [%Q{HOME="#{tmp_home_dir}"}]
+ @use_lfs = false
end
def self.perform(*args)
@@ -33,25 +34,27 @@ module QA
def username=(username)
@username = username
- @uri.user = username
+ # Only include the user in the URI if we're using HTTP as this breaks
+ # SSH authentication.
+ @uri.user = username unless ssh_key_set?
end
def use_default_credentials
self.username, self.password = default_credentials
-
- add_credentials_to_netrc unless ssh_key_set?
end
def clone(opts = '')
- run("git clone #{opts} #{uri} ./")
- end
+ clone_result = run("git clone #{opts} #{uri} ./")
+ return clone_result.response unless clone_result.success
+
+ enable_lfs_result = enable_lfs if use_lfs?
- def checkout(branch_name)
- run(%Q{git checkout "#{branch_name}"})
+ clone_result.to_s + enable_lfs_result.to_s
end
- def checkout_new_branch(branch_name)
- run(%Q{git checkout -b "#{branch_name}"})
+ def checkout(branch_name, new_branch: false)
+ opts = new_branch ? '-b' : ''
+ run(%Q{git checkout #{opts} "#{branch_name}"}).to_s
end
def shallow_clone
@@ -61,8 +64,6 @@ module QA
def configure_identity(name, email)
run(%Q{git config user.name #{name}})
run(%Q{git config user.email #{email}})
-
- add_credentials_to_netrc
end
def commit_file(name, contents, message)
@@ -73,19 +74,30 @@ module QA
def add_file(name, contents)
::File.write(name, contents)
- run(%Q{git add #{name}})
+ if use_lfs?
+ git_lfs_track_result = run(%Q{git lfs track #{name} --lockable})
+ return git_lfs_track_result.response unless git_lfs_track_result.success
+ end
+
+ git_add_result = run(%Q{git add #{name}})
+
+ git_lfs_track_result.to_s + git_add_result.to_s
end
def commit(message)
- run(%Q{git commit -m "#{message}"})
+ run(%Q{git commit -m "#{message}"}).to_s
end
def push_changes(branch = 'master')
- run("git push #{uri} #{branch}")
+ run("git push #{uri} #{branch}").to_s
+ end
+
+ def merge(branch)
+ run("git merge #{branch}")
end
def commits
- run('git log --oneline').split("\n")
+ run('git log --oneline').to_s.split("\n")
end
def use_ssh_key(key)
@@ -97,7 +109,8 @@ module QA
keyscan_params = ['-H']
keyscan_params << "-p #{uri.port}" if uri.port
keyscan_params << uri.host
- run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
+ res = run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
+ return res.response unless res.success?
self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"}
end
@@ -131,23 +144,66 @@ module QA
output[/git< version (\d+)/, 1] || 'unknown'
end
+ def try_add_credentials_to_netrc
+ return unless add_credentials?
+ return if netrc_already_contains_content?
+
+ # Despite libcurl supporting a custom .netrc location through the
+ # CURLOPT_NETRC_FILE environment variable, git does not support it :(
+ # Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
+ #
+ # This will create a .netrc in the correct working directory, which is
+ # a temporary directory created in .perform()
+ #
+ FileUtils.mkdir_p(tmp_home_dir)
+ File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
+ File.chmod(0600, netrc_file_path)
+ end
+
private
- attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file
+ attr_reader :uri, :username, :password, :known_hosts_file,
+ :private_key_file, :use_lfs
+
+ alias_method :use_lfs?, :use_lfs
+
+ Result = Struct.new(:success, :response) do
+ alias_method :success?, :success
+ alias_method :to_s, :response
+ end
+
+ def add_credentials?
+ return false if !username || !password
+ return true unless ssh_key_set?
+ return true if ssh_key_set? && use_lfs?
+
+ false
+ end
def ssh_key_set?
!private_key_file.nil?
end
+ def enable_lfs
+ # git lfs install *needs* a .gitconfig defined at ${HOME}/.gitconfig
+ FileUtils.mkdir_p(tmp_home_dir)
+ touch_gitconfig_result = run("touch #{tmp_home_dir}/.gitconfig")
+ return touch_gitconfig_result.response unless touch_gitconfig_result.success?
+
+ git_lfs_install_result = run('git lfs install')
+
+ touch_gitconfig_result.to_s + git_lfs_install_result.to_s
+ end
+
def run(command_str, *extra_env)
command = [env_vars, *extra_env, command_str, '2>&1'].compact.join(' ')
- Runtime::Logger.debug "Git: command=[#{command}]"
+ Runtime::Logger.debug "Git: pwd=[#{Dir.pwd}], command=[#{command}]"
- output, _ = Open3.capture2(command)
- output = output.chomp.gsub(/\s+$/, '')
- Runtime::Logger.debug "Git: output=[#{output}]"
+ output, status = Open3.capture2e(command)
+ output.chomp!
+ Runtime::Logger.debug "Git: output=[#{output}], exitstatus=[#{status.exitstatus}]"
- output
+ Result.new(status.exitstatus == 0, output)
end
def default_credentials
@@ -158,12 +214,12 @@ module QA
end
end
- def tmp_netrc_directory
- @tmp_netrc_directory ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
+ def tmp_home_dir
+ @tmp_home_dir ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
end
def netrc_file_path
- @netrc_file_path ||= File.join(tmp_netrc_directory, '.netrc')
+ @netrc_file_path ||= File.join(tmp_home_dir, '.netrc')
end
def netrc_content
@@ -174,21 +230,6 @@ module QA
File.exist?(netrc_file_path) &&
File.readlines(netrc_file_path).grep(/^#{netrc_content}$/).any?
end
-
- def add_credentials_to_netrc
- # Despite libcurl supporting a custom .netrc location through the
- # CURLOPT_NETRC_FILE environment variable, git does not support it :(
- # Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
- #
- # This will create a .netrc in the correct working directory, which is
- # a temporary directory created in .perform()
- #
- return if netrc_already_contains_content?
-
- FileUtils.mkdir_p(tmp_netrc_directory)
- File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
- File.chmod(0600, netrc_file_path)
- end
end
end
end
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 5788dceaaae..c3c90f254b7 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -92,6 +92,10 @@ module QA
find_element(name).set(true)
end
+ def uncheck_element(name)
+ find_element(name).set(false)
+ end
+
def click_element(name)
find_element(name).click
end
@@ -112,6 +116,14 @@ module QA
has_css?(element_selector_css(name), wait: wait)
end
+ def has_no_element?(name, wait: Capybara.default_max_wait_time)
+ has_no_css?(element_selector_css(name), wait: wait)
+ end
+
+ def has_text?(text)
+ page.has_text? text
+ end
+
def has_no_text?(text)
page.has_no_text? text
end
diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb
index 67d7f114786..f5add6bc9b5 100644
--- a/qa/qa/page/component/note.rb
+++ b/qa/qa/page/component/note.rb
@@ -32,9 +32,13 @@ module QA
click_element :comment_button
end
- def reply_to_discussion(reply_text)
+ def type_reply_to_discussion(reply_text)
all_elements(:discussion_reply).last.click
fill_element :reply_input, reply_text
+ end
+
+ def reply_to_discussion(reply_text)
+ type_reply_to_discussion(reply_text)
click_element :reply_comment_button
end
diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb
index 6d07d5a10e6..98bcb96b92c 100644
--- a/qa/qa/page/component/select2.rb
+++ b/qa/qa/page/component/select2.rb
@@ -6,6 +6,12 @@ module QA
find('.select2-result-label', text: item_text).click
end
+ def clear_current_selection_if_present
+ if has_css?('a > abbr.select2-search-choice-close', wait: 1.0)
+ find('a > abbr.select2-search-choice-close').click
+ end
+ end
+
def search_and_select(item_text)
find('.select2-input').set(item_text)
select_item(item_text)
diff --git a/qa/qa/page/label/index.rb b/qa/qa/page/label/index.rb
index 323acd57743..97ce8f0eba5 100644
--- a/qa/qa/page/label/index.rb
+++ b/qa/qa/page/label/index.rb
@@ -2,11 +2,26 @@ module QA
module Page
module Label
class Index < Page::Base
- view 'app/views/projects/labels/index.html.haml' do
+ view 'app/views/shared/labels/_nav.html.haml' do
element :label_create_new
end
+ view 'app/views/shared/empty_states/_labels.html.haml' do
+ element :label_svg
+ end
+
+ view 'app/assets/javascripts/lazy_loader.js' do
+ element :js_lazy_loaded
+ end
+
def go_to_new_label
+ # The 'labels.svg' takes a fraction of a second to load after which the "New label" button shifts up a bit
+ # This can cause webdriver to miss the hit so we wait for the svg to load (implicitly with has_element?)
+ # before clicking the button.
+ within_element(:label_svg) do
+ has_element?(:js_lazy_loaded)
+ end
+
click_element :label_create_new
end
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 4f21ed602d9..3823afb9b9f 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -50,17 +50,17 @@ module QA
end
def fast_forward_possible?
- !has_text?('Fast-forward merge is not possible')
+ has_no_text?('Fast-forward merge is not possible')
end
def has_merge_button?
refresh
- has_css?(element_selector_css(:merge_button))
+ has_element?(:merge_button)
end
def has_merge_options?
- has_css?(element_selector_css(:merge_moment_dropdown))
+ has_element?(:merge_moment_dropdown)
end
def merge_immediately
@@ -75,19 +75,21 @@ module QA
def rebase!
# The rebase button is disabled on load
wait do
- has_css?(element_selector_css(:mr_rebase_button))
+ has_element?(:mr_rebase_button)
end
# The rebase button is enabled via JS
wait(reload: false) do
- !first(element_selector_css(:mr_rebase_button)).disabled?
+ !find_element(:mr_rebase_button).disabled?
end
click_element :mr_rebase_button
- wait(reload: false) do
+ success = wait(reload: false) do
has_text?('Fast-forward merge without a merge commit')
end
+
+ raise "Rebase did not appear to be successful" unless success
end
def has_assignee?(username)
@@ -106,30 +108,32 @@ module QA
def merge!
# The merge button is disabled on load
wait do
- has_css?(element_selector_css(:merge_button))
+ has_element?(:merge_button)
end
# The merge button is enabled via JS
wait(reload: false) do
- !first(element_selector_css(:merge_button)).disabled?
+ !find_element(:merge_button).disabled?
end
merge_immediately
- wait(reload: false) do
+ success = wait(reload: false) do
has_text?('The changes were merged into')
end
+
+ raise "Merge did not appear to be successful" unless success
end
def mark_to_squash
# The squash checkbox is disabled on load
wait do
- has_css?(element_selector_css(:squash_checkbox))
+ has_element?(:squash_checkbox)
end
# The squash checkbox is enabled via JS
wait(reload: false) do
- !first(element_selector_css(:squash_checkbox)).disabled?
+ !find_element(:squash_checkbox).disabled?
end
click_element :squash_checkbox
@@ -145,7 +149,7 @@ module QA
def add_comment_to_diff(text)
wait(time: 5) do
- page.has_text?("No newline at end of file")
+ has_text?("No newline at end of file")
end
all_elements(:new_diff_line).first.hover
click_element :diff_comment
diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb
index 9191dbe9cf3..8c12eff5cf1 100644
--- a/qa/qa/page/profile/personal_access_tokens.rb
+++ b/qa/qa/page/profile/personal_access_tokens.rb
@@ -3,29 +3,51 @@ module QA
module Profile
class PersonalAccessTokens < Page::Base
view 'app/views/shared/_personal_access_tokens_form.html.haml' do
- element :personal_access_token_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
- element :create_token_button, 'submit "Create #{type} token"' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
- element :scopes_api_radios, "label :scopes" # rubocop:disable QA/ElementWithPattern
+ element :personal_access_token_name_field
+ element :create_token_button
+ end
+
+ view 'app/views/shared/tokens/_scopes_form.html.haml' do
+ element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
view 'app/views/shared/_personal_access_tokens_created_container.html.haml' do
- element :create_token_field, "text_field_tag 'created-personal-access-token'" # rubocop:disable QA/ElementWithPattern
+ element :created_personal_access_token
+ end
+ view 'app/views/shared/_personal_access_tokens_table.html.haml' do
+ element :revoke_button
end
def fill_token_name(name)
- fill_in 'personal_access_token_name', with: name
+ fill_element(:personal_access_token_name_field, name)
end
def check_api
- check 'personal_access_token_scopes_api'
+ check_element(:api_radio)
end
def create_token
- click_on 'Create personal access token'
+ click_element(:create_token_button)
end
def created_access_token
- page.find('#created-personal-access-token').value
+ find_element(:created_personal_access_token, wait: 30).value
+ end
+
+ def has_token_row_for_name?(token_name)
+ page.has_css?('tr', text: token_name, wait: 1.0)
+ end
+
+ def first_token_row_for_name(token_name)
+ page.find('tr', text: token_name, match: :first, wait: 1.0)
+ end
+
+ def revoke_first_token_with_name(token_name)
+ within first_token_row_for_name(token_name) do
+ accept_confirm do
+ click_element(:revoke_button)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/branches/show.rb b/qa/qa/page/project/branches/show.rb
new file mode 100644
index 00000000000..762a97e2088
--- /dev/null
+++ b/qa/qa/page/project/branches/show.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Branches
+ class Show < Page::Base
+ view 'app/views/projects/branches/_branch.html.haml' do
+ element :remove_btn
+ end
+ view 'app/views/projects/branches/_panel.html.haml' do
+ element :all_branches
+ end
+ view 'app/views/projects/branches/index.html.haml' do
+ element :delete_merged_branches
+ end
+
+ def delete_branch(branch_name)
+ within_element(:all_branches) do
+ within(".js-branch-#{branch_name}") do
+ accept_alert do
+ find_element(:remove_btn).click
+ end
+ end
+ end
+ end
+
+ def has_branch_title?(branch_title)
+ within_element(:all_branches) do
+ within(".item-title") do
+ has_text?(branch_title)
+ end
+ end
+ end
+
+ def has_branch_with_badge?(branch_name, badge)
+ within_element(:all_branches) do
+ within(".js-branch-#{branch_name} .badge") do
+ has_text?(badge)
+ end
+ end
+ end
+
+ def delete_merged_branches
+ accept_alert do
+ click_element(:delete_merged_branches)
+ end
+ end
+
+ def wait_for_texts_not_to_be_visible(texts)
+ text_not_visible = wait do
+ texts.all? do |text|
+ has_no_text?(text)
+ end
+ end
+ raise "Expected text(s) #{texts} not to be visible" unless text_not_visible
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 835e1ed00b5..46dfe87fe25 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -4,162 +4,35 @@ module QA
module Page
module Project
class Menu < Page::Base
- view 'app/views/layouts/nav/sidebar/_project.html.haml' do
- element :settings_item
- element :settings_link, 'link_to edit_project_path' # rubocop:disable QA/ElementWithPattern
- element :repository_link, "title: _('Repository')" # rubocop:disable QA/ElementWithPattern
- element :link_pipelines
- element :link_members_settings
- element :pipelines_settings_link, "title: _('CI / CD')" # rubocop:disable QA/ElementWithPattern
- element :operations_kubernetes_link, "title: _('Kubernetes')" # rubocop:disable QA/ElementWithPattern
- element :operations_environments_link
- element :issues_link, /link_to.*shortcuts-issues/ # rubocop:disable QA/ElementWithPattern
- element :issues_link_text, "Issues" # rubocop:disable QA/ElementWithPattern
- element :merge_requests_link, /link_to.*shortcuts-merge_requests/ # rubocop:disable QA/ElementWithPattern
- element :merge_requests_link_text, "Merge Requests" # rubocop:disable QA/ElementWithPattern
- element :top_level_items, '.sidebar-top-level-items' # rubocop:disable QA/ElementWithPattern
- element :operations_section, "class: 'shortcuts-operations'" # rubocop:disable QA/ElementWithPattern
- element :activity_link, "title: _('Activity')" # rubocop:disable QA/ElementWithPattern
- element :wiki_link_text, "Wiki" # rubocop:disable QA/ElementWithPattern
- element :milestones_link
- element :labels_link
- end
-
- view 'app/assets/javascripts/fly_out_nav.js' do
- element :fly_out, "classList.add('fly-out-list')" # rubocop:disable QA/ElementWithPattern
- end
-
- def click_ci_cd_pipelines
- within_sidebar do
- click_element :link_pipelines
- end
- end
+ include SubMenus::Common
- def click_ci_cd_settings
- hover_settings do
- within_submenu do
- click_link('CI / CD')
- end
- end
- end
-
- def click_issues
- within_sidebar do
- click_link('Issues')
- end
- end
+ include SubMenus::CiCd
+ include SubMenus::Issues
+ include SubMenus::Operations
+ include SubMenus::Repository
+ include SubMenus::Settings
- def click_members_settings
- hover_settings do
- within_submenu do
- click_element :link_members_settings
- end
- end
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :activity_link
+ element :merge_requests_link
+ element :wiki_link
end
def click_merge_requests
within_sidebar do
- click_link('Merge Requests')
- end
- end
-
- def click_operations_environments
- hover_operations do
- within_submenu do
- click_element(:operations_environments_link)
- end
- end
- end
-
- def click_operations_kubernetes
- hover_operations do
- within_submenu do
- click_link('Kubernetes')
- end
- end
- end
-
- def click_milestones
- within_sidebar do
- click_element :milestones_link
- end
- end
-
- def click_repository
- within_sidebar do
- click_link('Repository')
- end
- end
-
- def click_repository_settings
- hover_settings do
- within_submenu do
- click_link('Repository')
- end
+ click_element(:merge_requests_link)
end
end
def click_wiki
within_sidebar do
- click_link('Wiki')
+ click_element(:wiki_link)
end
end
def go_to_activity
within_sidebar do
- click_on 'Activity'
- end
- end
-
- def go_to_labels
- hover_issues do
- within_submenu do
- click_element(:labels_link)
- end
- end
- end
-
- def go_to_settings
- within_sidebar do
- click_on 'Settings'
- end
- end
-
- private
-
- def hover_issues
- within_sidebar do
- find_element(:issues_item).hover
-
- yield
- end
- end
-
- def hover_operations
- within_sidebar do
- find('.shortcuts-operations').hover
-
- yield
- end
- end
-
- def hover_settings
- within_sidebar do
- find('.qa-settings-item').hover
-
- yield
- end
- end
-
- def within_sidebar
- page.within('.sidebar-top-level-items') do
- yield
- end
- end
-
- def within_submenu
- page.within('.fly-out-list') do
- yield
+ click_element(:activity_link)
end
end
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 6acc413b586..a588af07e4a 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -5,6 +5,7 @@ module QA
include Page::Component::Select2
view 'app/views/projects/new.html.haml' do
+ element :project_create_from_template_tab
element :import_project_tab, "Import project" # rubocop:disable QA/ElementWithPattern
end
@@ -44,6 +45,10 @@ module QA
click_on 'Create project'
end
+ def go_to_create_from_template
+ click_element(:project_create_from_template_tab)
+ end
+
def set_visibility(visibility)
choose visibility
end
diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb
index f3ab636ecc1..ffd5b36e1ae 100644
--- a/qa/qa/page/project/operations/kubernetes/add_existing.rb
+++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb
@@ -33,8 +33,8 @@ module QA
click_on 'Add Kubernetes cluster'
end
- def check_rbac!
- check_element :rbac_checkbox
+ def uncheck_rbac!
+ uncheck_element :rbac_checkbox
end
end
end
diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb
index 3c8c0cbdf7c..5da8d352e74 100644
--- a/qa/qa/page/project/settings/deploy_keys.rb
+++ b/qa/qa/page/project/settings/deploy_keys.rb
@@ -14,8 +14,13 @@ module QA
end
view 'app/assets/javascripts/deploy_keys/components/key.vue' do
- element :key_title, /class=".*qa-key-title.*"/ # rubocop:disable QA/ElementWithPattern
- element :key_fingerprint, /class=".*qa-key-fingerprint.*"/ # rubocop:disable QA/ElementWithPattern
+ element :key
+ element :key_title
+ element :key_fingerprint
+ end
+
+ def add_key
+ click_on 'Add key'
end
def fill_key_title(title)
@@ -26,31 +31,29 @@ module QA
fill_in 'deploy_key_key', with: key
end
- def add_key
- click_on 'Add key'
- end
-
- def key_title
+ def find_fingerprint(title)
within_project_deploy_keys do
- find_element(:key_title).text
+ find_element(:key, title)
+ .find(element_selector_css(:key_fingerprint)).text
end
end
- def key_fingerprint
+ def has_key?(title, fingerprint)
within_project_deploy_keys do
- find_element(:key_fingerprint).text
+ find_element(:key, title)
+ .has_css?(element_selector_css(:key_fingerprint), text: fingerprint)
end
end
- def key_titles
+ def key_title
within_project_deploy_keys do
- all_elements(:key_title)
+ find_element(:key_title).text
end
end
- def key_fingerprints
+ def key_fingerprint
within_project_deploy_keys do
- all_elements(:key_fingerprint)
+ find_element(:key_fingerprint).text
end
end
@@ -58,7 +61,7 @@ module QA
def within_project_deploy_keys
wait(reload: false) do
- has_css?(element_selector_css(:project_deploy_keys))
+ has_element?(:project_deploy_keys)
end
within_element(:project_deploy_keys) do
diff --git a/qa/qa/page/project/sub_menus/ci_cd.rb b/qa/qa/page/project/sub_menus/ci_cd.rb
new file mode 100644
index 00000000000..adae2ce08c4
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/ci_cd.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module CiCd
+ def self.included(base)
+ base.class_eval do
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :link_pipelines
+ end
+ end
+ end
+
+ def click_ci_cd_pipelines
+ within_sidebar do
+ click_element :link_pipelines
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb
new file mode 100644
index 00000000000..c94e1e85256
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/common.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Common
+ def within_sidebar
+ within('.sidebar-top-level-items') do
+ yield
+ end
+ end
+
+ def within_submenu
+ within('.fly-out-list') do
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/issues.rb b/qa/qa/page/project/sub_menus/issues.rb
new file mode 100644
index 00000000000..f81e4f34909
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/issues.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Issues
+ def self.included(base)
+ base.class_eval do
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :issues_item
+ element :labels_link
+ element :milestones_link
+ end
+ end
+ end
+
+ def click_issues
+ within_sidebar do
+ click_link('Issues')
+ end
+ end
+
+ def click_milestones
+ within_sidebar do
+ click_element :milestones_link
+ end
+ end
+
+ def go_to_labels
+ hover_issues do
+ within_submenu do
+ click_element(:labels_link)
+ end
+ end
+ end
+
+ private
+
+ def hover_issues
+ within_sidebar do
+ scroll_to_element(:issues_item)
+ find_element(:issues_item).hover
+
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/operations.rb b/qa/qa/page/project/sub_menus/operations.rb
new file mode 100644
index 00000000000..cf9fc453565
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/operations.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Operations
+ def self.included(base)
+ base.class_eval do
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :link_operations
+ element :operations_environments_link
+ end
+ end
+ end
+
+ def click_operations_environments
+ hover_operations do
+ within_submenu do
+ click_element(:operations_environments_link)
+ end
+ end
+ end
+
+ def click_operations_kubernetes
+ hover_operations do
+ within_submenu do
+ click_link('Kubernetes')
+ end
+ end
+ end
+
+ private
+
+ def hover_operations
+ within_sidebar do
+ scroll_to_element(:link_operations)
+ find_element(:link_operations).hover
+
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb
new file mode 100644
index 00000000000..29eaa9a74de
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/repository.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Repository
+ def self.included(base)
+ base.class_eval do
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :project_menu_repo
+ element :branches_link
+ end
+ end
+ end
+
+ def click_repository
+ within_sidebar do
+ click_element(:project_menu_repo)
+ end
+ end
+
+ def click_repository_branches
+ hover_repository do
+ within_submenu do
+ click_element(:branches_link)
+ end
+ end
+ end
+
+ private
+
+ def hover_repository
+ within_sidebar do
+ find_element(:project_menu_repo).hover
+
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb
new file mode 100644
index 00000000000..62c594c0210
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/settings.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Settings
+ def self.included(base)
+ base.class_eval do
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :settings_item
+ element :link_members_settings
+ end
+ end
+ end
+
+ def click_ci_cd_settings
+ hover_settings do
+ within_submenu do
+ click_link('CI / CD')
+ end
+ end
+ end
+
+ def click_members_settings
+ hover_settings do
+ within_submenu do
+ click_element :link_members_settings
+ end
+ end
+ end
+
+ def click_repository_settings
+ hover_settings do
+ within_submenu do
+ click_link('Repository')
+ end
+ end
+ end
+
+ def go_to_settings
+ within_sidebar do
+ click_on 'Settings'
+ end
+ end
+
+ private
+
+ def hover_settings
+ within_sidebar do
+ scroll_to_element(:settings_item)
+ find_element(:settings_item).hover
+
+ yield
+ end
+ end
+ end
+ 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 23e580b81b6..a3e126b51da 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -66,11 +66,29 @@ module QA
def commit_changes
click_element :begin_commit_button
- click_element :commit_button
+ # After clicking :begin_commit_button 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.
wait(reload: false) do
- page.has_content?('Your changes have been committed')
+ has_no_element?(:begin_commit_button) &&
+ has_element?(:commit_button)
end
+
+ # Retry the attempt to click :commit_button just in case part of the
+ # animation is still in process even when the buttons have the
+ # expected visibility.
+ commit_success_msg_shown = with_retry do
+ click_element :commit_button
+
+ wait(reload: false) do
+ has_text?('Your changes have been committed')
+ end
+ end
+
+ raise "The changes do not appear to have been committed successfully." unless commit_success_msg_shown
end
end
end
diff --git a/qa/qa/page/settings/common.rb b/qa/qa/page/settings/common.rb
index f9f71aa4a72..9fea74eabc9 100644
--- a/qa/qa/page/settings/common.rb
+++ b/qa/qa/page/settings/common.rb
@@ -11,7 +11,7 @@ module QA
wait(reload: false) do
click_button 'Expand' unless first('button', text: 'Collapse')
- page.has_content?('Collapse')
+ has_content?('Collapse')
end
yield if block_given?
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index dcea144ab74..ffe8633dd16 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -116,27 +116,13 @@ module QA
end
private_class_method :evaluator
- def self.dynamic_attributes
- const_get(:DynamicAttributes)
- rescue NameError
- mod = const_set(:DynamicAttributes, Module.new)
-
- include mod
-
- mod
- end
-
- def self.attributes_names
- dynamic_attributes.instance_methods(false).sort.grep_v(/=$/)
- end
-
class DSL
def initialize(base)
@base = base
end
def attribute(name, &block)
- @base.dynamic_attributes.module_eval do
+ @base.module_eval do
attr_writer(name)
define_method(name) do
diff --git a/qa/qa/resource/deploy_key.rb b/qa/qa/resource/deploy_key.rb
index 9ed8fb7726e..9565598efb0 100644
--- a/qa/qa/resource/deploy_key.rb
+++ b/qa/qa/resource/deploy_key.rb
@@ -8,11 +8,7 @@ module QA
attribute :fingerprint do
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |key|
- key_offset = key.key_titles.index do |key_title|
- key_title.text == title
- end
-
- key.key_fingerprints[key_offset].text
+ key.find_fingerprint(title)
end
end
end
diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb
index 9fd66f3a36a..c6243ff43fa 100644
--- a/qa/qa/resource/fork.rb
+++ b/qa/qa/resource/fork.rb
@@ -3,6 +3,13 @@
module QA
module Resource
class Fork < Base
+ attribute :project do
+ Resource::Project.fabricate! do |resource|
+ resource.name = push.project.name
+ resource.path_with_namespace = "#{user.name}/#{push.project.name}"
+ end
+ end
+
attribute :push do
Repository::ProjectPush.fabricate!
end
@@ -37,6 +44,8 @@ module QA
Page::Layout::Banner.perform do |page|
page.has_notice?('The project was successfully forked.')
end
+
+ populate(:project)
end
end
end
diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb
index 96c8843fb99..d67e5f6da20 100644
--- a/qa/qa/resource/kubernetes_cluster.rb
+++ b/qa/qa/resource/kubernetes_cluster.rb
@@ -29,7 +29,7 @@ module QA
page.set_api_url(@cluster.api_url)
page.set_ca_certificate(@cluster.ca_certificate)
page.set_token(@cluster.token)
- page.check_rbac! if @cluster.rbac
+ page.uncheck_rbac! unless @cluster.rbac
page.add_cluster!
end
diff --git a/qa/qa/resource/merge_request_from_fork.rb b/qa/qa/resource/merge_request_from_fork.rb
index f91ae299d76..5d20a6e9c75 100644
--- a/qa/qa/resource/merge_request_from_fork.rb
+++ b/qa/qa/resource/merge_request_from_fork.rb
@@ -11,7 +11,7 @@ module QA
attribute :push do
Repository::ProjectPush.fabricate! do |resource|
- resource.project = fork
+ resource.project = fork.project
resource.branch_name = fork_branch
resource.file_name = 'file2.txt'
resource.user = fork.user
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 1fafbf5d73e..433e5a8f7c9 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -12,6 +12,10 @@ module QA
Group.fabricate!
end
+ attribute :path_with_namespace do
+ "#{group.sandbox.path}/#{group.path}/#{name}" if group
+ end
+
attribute :repository_ssh_location do
Page::Project::Show.perform do |page|
page.repository_clone_ssh_location
@@ -46,8 +50,14 @@ module QA
end
end
+ def fabricate_via_api!
+ resource_web_url(api_get)
+ rescue ResourceNotFoundError
+ super
+ end
+
def api_get_path
- "/projects/#{name}"
+ "/projects/#{CGI.escape(path_with_namespace)}"
end
def api_post_path
diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb
index c14d97ff7fb..32f15547da2 100644
--- a/qa/qa/resource/repository/push.rb
+++ b/qa/qa/resource/repository/push.rb
@@ -8,7 +8,7 @@ module QA
class Push < Base
attr_accessor :file_name, :file_content, :commit_message,
:branch_name, :new_branch, :output, :repository_http_uri,
- :repository_ssh_uri, :ssh_key, :user
+ :repository_ssh_uri, :ssh_key, :user, :use_lfs
attr_writer :remote_branch
@@ -20,6 +20,7 @@ module QA
@new_branch = true
@repository_http_uri = ""
@ssh_key = nil
+ @use_lfs = false
end
def remote_branch
@@ -33,7 +34,9 @@ module QA
end
def files=(files)
- if !files.is_a?(Array) || files.empty?
+ if !files.is_a?(Array) ||
+ files.empty? ||
+ files.any? { |file| !file.has_key?(:name) || !file.has_key?(:content) }
raise ArgumentError, "Please provide an array of hashes e.g.: [{name: 'file1', content: 'foo'}]"
end
@@ -42,6 +45,8 @@ module QA
def fabricate!
Git::Repository.perform do |repository|
+ @output = ''
+
if ssh_key
repository.uri = repository_ssh_uri
repository.use_ssh_key(ssh_key)
@@ -50,6 +55,8 @@ module QA
repository.use_default_credentials unless user
end
+ repository.use_lfs = use_lfs
+
username = 'GitLab QA'
email = 'root@gitlab.com'
@@ -60,29 +67,27 @@ module QA
email = user.email
end
- repository.clone
+ repository.try_add_credentials_to_netrc
+
+ @output += repository.clone
repository.configure_identity(username, email)
- if new_branch
- repository.checkout_new_branch(branch_name)
- else
- repository.checkout(branch_name)
- end
+ @output += repository.checkout(branch_name, new_branch: new_branch)
if @directory
@directory.each_child do |f|
- repository.add_file(f.basename, f.read) if f.file?
+ @output += repository.add_file(f.basename, f.read) if f.file?
end
elsif @files
@files.each do |f|
repository.add_file(f[:name], f[:content])
end
else
- repository.add_file(file_name, file_content)
+ @output += repository.add_file(file_name, file_content)
end
- repository.commit(commit_message)
- @output = repository.push_changes("#{branch_name}:#{remote_branch}")
+ @output += repository.commit(commit_message)
+ @output += repository.push_changes("#{branch_name}:#{remote_branch}")
repository.delete_ssh_key
end
diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb
index c26f0c84a1f..6c5e91b6488 100644
--- a/qa/qa/resource/user.rb
+++ b/qa/qa/resource/user.rb
@@ -6,29 +6,40 @@ module QA
module Resource
class User < Base
attr_reader :unique_id
- attr_writer :username, :password, :name, :email
+ attr_writer :username, :password
attr_accessor :provider, :extern_uid
+ attribute :name
+ attribute :email
+
def initialize
@unique_id = SecureRandom.hex(8)
end
def username
- @username ||= "qa-user-#{unique_id}"
+ @username || "qa-user-#{unique_id}"
end
def password
- @password ||= 'password'
+ @password || 'password'
end
def name
- @name ||= username
+ @name ||= api_resource&.dig(:name) || username
end
def email
@email ||= "#{username}@example.com"
end
+ def public_email
+ @public_email ||= begin
+ api_public_email = api_resource&.dig(:public_email)
+
+ api_public_email && api_public_email != '' ? api_public_email : Runtime::User.default_email
+ end
+ end
+
def credentials_given?
defined?(@username) && defined?(@password)
end
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index b706d6565d2..0bcf5e693f0 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -40,34 +40,38 @@ module QA
return if Capybara.drivers.include?(:chrome)
- Capybara.register_driver :chrome do |app|
- capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
- # This enables access to logs with `page.driver.manage.get_log(:browser)`
- loggingPrefs: {
- browser: "ALL",
- client: "ALL",
- driver: "ALL",
- server: "ALL"
- }
- )
+ Capybara.register_driver QA::Runtime::Env.browser do |app|
+ capabilities = Selenium::WebDriver::Remote::Capabilities.send(QA::Runtime::Env.browser,
+ # This enables access to logs with `page.driver.manage.get_log(:browser)`
+ loggingPrefs: {
+ browser: "ALL",
+ client: "ALL",
+ driver: "ALL",
+ server: "ALL"
+ })
if QA::Runtime::Env.accept_insecure_certs?
capabilities['acceptInsecureCerts'] = true
end
- options = Selenium::WebDriver::Chrome::Options.new
- options.add_argument("window-size=1240,1680")
+ # QA::Runtime::Env.browser.capitalize will work for every driver type except PhantomJS.
+ # We will have no use to use PhantomJS so this shouldn't be a problem.
+ options = Selenium::WebDriver.const_get(QA::Runtime::Env.browser.capitalize)::Options.new
+
+ if QA::Runtime::Env.browser == :chrome
+ options.add_argument("window-size=1240,1680")
- # Chrome won't work properly in a Docker container in sandbox mode
- options.add_argument("no-sandbox")
+ # Chrome won't work properly in a Docker container in sandbox mode
+ options.add_argument("no-sandbox")
- # Run headless by default unless CHROME_HEADLESS is false
- if QA::Runtime::Env.chrome_headless?
- options.add_argument("headless")
+ # Run headless by default unless CHROME_HEADLESS is false
+ if QA::Runtime::Env.chrome_headless?
+ options.add_argument("headless")
- # Chrome documentation says this flag is needed for now
- # https://developers.google.com/web/updates/2017/04/headless-chrome#cli
- options.add_argument("disable-gpu")
+ # Chrome documentation says this flag is needed for now
+ # https://developers.google.com/web/updates/2017/04/headless-chrome#cli
+ options.add_argument("disable-gpu")
+ end
end
# Use the same profile on QA runs if CHROME_REUSE_PROFILE is true.
@@ -80,12 +84,18 @@ module QA
# Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
options.add_argument("disable-dev-shm-usage") if QA::Runtime::Env.running_in_ci?
- Capybara::Selenium::Driver.new(
- app,
- browser: :chrome,
+ selenium_options = {
+ browser: QA::Runtime::Env.browser,
clear_local_storage: true,
desired_capabilities: capabilities,
options: options
+ }
+
+ selenium_options[:url] = QA::Runtime::Env.remote_grid if QA::Runtime::Env.remote_grid
+
+ Capybara::Selenium::Driver.new(
+ app,
+ selenium_options
)
end
@@ -93,7 +103,7 @@ module QA
Capybara::Screenshot.prune_strategy = :keep_last_run
# From https://github.com/mattheworiordan/capybara-screenshot/issues/84#issuecomment-41219326
- Capybara::Screenshot.register_driver(:chrome) do |driver, path|
+ Capybara::Screenshot.register_driver(QA::Runtime::Env.browser) do |driver, path|
driver.browser.save_screenshot(path)
end
@@ -102,8 +112,8 @@ module QA
end
Capybara.configure do |config|
- config.default_driver = :chrome
- config.javascript_driver = :chrome
+ config.default_driver = QA::Runtime::Env.browser
+ config.javascript_driver = QA::Runtime::Env.browser
config.default_max_wait_time = 10
# https://github.com/mattheworiordan/capybara-screenshot/issues/164
config.save_path = ::File.expand_path('../../tmp', __dir__)
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index dae5aa3f794..23a2ace6a55 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -56,6 +56,34 @@ module QA
@personal_access_token ||= ENV['PERSONAL_ACCESS_TOKEN']
end
+ def remote_grid
+ # if username specified, password/auth token is required
+ # can be
+ # - "http://user:pass@somehost.com/wd/hub"
+ # - "https://user:pass@somehost.com:443/wd/hub"
+ # - "http://localhost:4444/wd/hub"
+
+ return if (ENV['QA_REMOTE_GRID'] || '').empty?
+
+ "#{remote_grid_protocol}://#{remote_grid_credentials}#{ENV['QA_REMOTE_GRID']}/wd/hub"
+ end
+
+ def remote_grid_username
+ ENV['QA_REMOTE_GRID_USERNAME']
+ end
+
+ def remote_grid_access_key
+ ENV['QA_REMOTE_GRID_ACCESS_KEY']
+ end
+
+ def remote_grid_protocol
+ ENV['QA_REMOTE_GRID_PROTOCOL'] || 'http'
+ end
+
+ def browser
+ ENV['QA_BROWSER'].nil? ? :chrome : ENV['QA_BROWSER'].to_sym
+ end
+
def user_username
ENV['GITLAB_USERNAME']
end
@@ -158,6 +186,16 @@ module QA
private
+ def remote_grid_credentials
+ if remote_grid_username
+ raise ArgumentError, %Q(Please provide an access key for user "#{remote_grid_username}") unless remote_grid_access_key
+
+ return "#{remote_grid_username}:#{remote_grid_access_key}@"
+ end
+
+ ''
+ end
+
def enabled?(value, default: true)
return default if value.nil?
diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb
index 5eb7a210fce..e8bcb8a9f50 100644
--- a/qa/qa/runtime/user.rb
+++ b/qa/qa/runtime/user.rb
@@ -7,6 +7,10 @@ module QA
'root'
end
+ def default_email
+ 'admin@example.com'
+ end
+
def default_password
'5iveL!fe'
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
index 6ddd7dde2cf..c06f13ee204 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
@@ -3,7 +3,7 @@
module QA
context 'Create' do
describe 'Merge request creation' do
- it 'user creates a new merge request' do
+ it 'user creates a new merge request' do
gitlab_account_username = "@#{Runtime::User.username}"
Runtime::Browser.visit(:gitlab, Page::Main::Login)
@@ -49,7 +49,7 @@ module QA
end
describe 'creates a merge request', :smoke do
- it 'user creates a new merge request' do
+ it 'user creates a new merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index 6dcd74471fe..6ca7af8a3af 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -5,18 +5,18 @@ module QA
describe 'Merge request creation from fork' do
it 'user forks a project, submits a merge request and maintainer merges it' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
merge_request = Resource::MergeRequestFromFork.fabricate! do |merge_request|
merge_request.fork_branch = 'feature-branch'
end
- Page::Main::Menu.perform { |main| main.sign_out }
- Page::Main::Login.perform { |login| login.sign_in_using_credentials }
+ Page::Main::Menu.perform(&:sign_out)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
merge_request.visit!
- Page::MergeRequest::Show.perform { |show| show.merge! }
+ Page::MergeRequest::Show.perform(&:merge!)
expect(page).to have_content('The changes were merged')
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
index e2d639fd150..3fbcd77dac6 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
@@ -3,17 +3,17 @@
module QA
context 'Create' do
describe 'Merge request rebasing' do
- it 'user rebases source branch of merge request' do
+ it 'user rebases source branch of merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
project = Resource::Project.fabricate! do |project|
project.name = "only-fast-forward"
end
project.visit!
- Page::Project::Menu.act { go_to_settings }
- Page::Project::Settings::MergeRequest.act { enable_ff_only }
+ Page::Project::Menu.perform(&:go_to_settings)
+ Page::Project::Settings::MergeRequest.perform(&:enable_ff_only)
merge_request = Resource::MergeRequest.fabricate! do |merge_request|
merge_request.project = project
@@ -38,7 +38,7 @@ module QA
merge_request.rebase!
expect(merge_request).to have_merge_button
- expect(merge_request.fast_forward_possible?).to be_truthy
+ expect(merge_request).to be_fast_forward_possible
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index 4126f967ee2..10cc0480794 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -3,7 +3,7 @@
module QA
context 'Create' do
describe 'Merge request squashing' do
- it 'user squashes commits while merging' do
+ it 'user squashes commits while merging' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
new file mode 100644
index 00000000000..0f0c627d79a
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Create, list, and delete branches via web' do
+ master_branch = 'master'
+ second_branch = 'second-branch'
+ third_branch = 'third-branch'
+ file_1_master = 'file.txt'
+ file_2_master = 'other-file.txt'
+ file_second_branch = 'file-2.txt'
+ file_third_branch = 'file-3.txt'
+ first_commit_message_of_master_branch = "Add #{file_1_master}"
+ second_commit_message_of_master_branch = "Add #{file_2_master}"
+ commit_message_of_second_branch = "Add #{file_second_branch}"
+ commit_message_of_third_branch = "Add #{file_third_branch}"
+
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ project = Resource::Project.fabricate! do |proj|
+ proj.name = 'project-qa-test'
+ proj.description = 'project for qa test'
+ end
+ project.visit!
+
+ Git::Repository.perform do |repository|
+ repository.uri = project.repository_http_location.uri
+ repository.use_default_credentials
+
+ repository.act do
+ clone
+ configure_identity('GitLab QA', 'root@gitlab.com')
+ commit_file(file_1_master, 'Test file content', first_commit_message_of_master_branch)
+ push_changes
+ checkout(second_branch, new_branch: true)
+ commit_file(file_second_branch, 'File 2 content', commit_message_of_second_branch)
+ push_changes(second_branch)
+ checkout(master_branch)
+ # This second commit on master is needed for the master branch to be ahead
+ # of the second branch, and when the second branch is merged to master it will
+ # show the 'merged' badge on it.
+ # Refer to the below issue note:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/55524#note_126100848
+ commit_file(file_2_master, 'Other test file content', second_commit_message_of_master_branch)
+ push_changes
+ merge(second_branch)
+ push_changes
+ checkout(third_branch, new_branch: true)
+ commit_file(file_third_branch, 'File 3 content', commit_message_of_third_branch)
+ push_changes(third_branch)
+ end
+ end
+ Page::Project::Show.perform(&:wait_for_push)
+ end
+
+ it 'branches are correctly listed after CRUD operations' do
+ Page::Project::Menu.perform(&:click_repository_branches)
+
+ expect(page).to have_content(master_branch)
+ expect(page).to have_content(second_branch)
+ expect(page).to have_content(third_branch)
+ expect(page).to have_content("Merge branch 'second-branch'")
+ expect(page).to have_content(commit_message_of_second_branch)
+ expect(page).to have_content(commit_message_of_third_branch)
+
+ Page::Project::Branches::Show.perform do |branches|
+ expect(branches).to have_branch_with_badge(second_branch, 'merged')
+ end
+
+ Page::Project::Branches::Show.perform do |branches_view|
+ branches_view.delete_branch(third_branch)
+ end
+
+ expect(page).not_to have_content(third_branch)
+
+ Page::Project::Branches::Show.perform(&:delete_merged_branches)
+
+ expect(page).to have_content(
+ 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
+ )
+
+ page.refresh
+ Page::Project::Branches::Show.perform do |branches_view|
+ branches_view.wait_for_texts_not_to_be_visible([commit_message_of_second_branch])
+ expect(branches_view).not_to have_branch_title(second_branch)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
index a63b7dce8d6..3310a873a60 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb
@@ -3,7 +3,7 @@
module QA
context 'Create' do
describe 'Git push over HTTP', :ldap_no_tls do
- it 'user using a personal access token pushes code to the repository' do
+ it 'user using a personal access token pushes code to the repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
index 2d0e281ab59..9d31a25ab35 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
@@ -3,7 +3,7 @@
module QA
context 'Create' do
describe 'Push mirror a repository over HTTP' do
- it 'configures and syncs a (push) mirrored repository' do
+ it 'configures and syncs a (push) mirrored repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
index ad6426df420..d10ad896b3b 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
@@ -3,7 +3,7 @@
module QA
context 'Create' do
describe 'Git push over HTTP', :ldap_no_tls do
- it 'user pushes code to the repository' do
+ it 'user pushes code to the repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb
index 203338ddf77..621cca0f9a5 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb
@@ -39,11 +39,15 @@ 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: Administrator <admin@example.com>')
+ 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/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
index 210271705d9..a7d0998d42c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
@@ -17,7 +17,8 @@ module QA
login
end
- it 'user creates, edits, clones, and pushes to the wiki' do
+ # Failure reported: https://gitlab.com/gitlab-org/quality/nightly/issues/24
+ it 'user creates, edits, clones, and pushes to the wiki', :quarantine do
wiki = Resource::Wiki.fabricate! do |resource|
resource.title = 'Home'
resource.content = '# My First Wiki Content'
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
index 84757f25379..6f39a755392 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Deploy key creation' do
it 'user adds a deploy key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
key = Runtime::Key::RSA.new
deploy_key_title = 'deploy key title'
@@ -16,7 +16,13 @@ module QA
resource.key = deploy_key_value
end
- expect(deploy_key.fingerprint).to eq(key.fingerprint)
+ expect(deploy_key.fingerprint).to eq key.fingerprint
+
+ Page::Project::Settings::Repository.perform do |setting|
+ setting.expand_deploy_keys do |keys|
+ expect(keys).to have_key(deploy_key_title, key.fingerprint)
+ end
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index b0c277a48c3..7283468d66b 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
@@ -3,34 +3,37 @@
require 'pathname'
module QA
- context 'Configure', :orchestrated, :kubernetes do
+ # Issues for transient failure:
+ # https://gitlab.com/gitlab-org/quality/nightly/issues/40
+ # https://gitlab.com/gitlab-org/quality/nightly/issues/61
+ context 'Configure', :orchestrated, :kubernetes, :quarantine do
describe 'Auto DevOps support' do
- after do
- @cluster&.remove!
+ def login
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
end
[true, false].each do |rbac|
context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do
- it 'user creates a new project and runs auto devops' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ before(:all) do
+ login
- project = Resource::Project.fabricate! do |p|
+ @project = Resource::Project.fabricate! do |p|
p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops'
- p.description = 'Project with Auto Devops'
+ p.description = 'Project with Auto DevOps'
end
# Disable code_quality check in Auto DevOps pipeline as it takes
# too long and times out the test
Resource::CiVariable.fabricate! do |resource|
- resource.project = project
+ resource.project = @project
resource.key = 'CODE_QUALITY_DISABLED'
resource.value = '1'
end
- # Create Auto Devops compatible repo
+ # Create Auto DevOps compatible repo
Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = project
+ push.project = @project
push.directory = Pathname
.new(__dir__)
.join('../../../../../fixtures/auto_devops_rack')
@@ -42,23 +45,74 @@ module QA
# Create and connect K8s cluster
@cluster = Service::KubernetesCluster.new(rbac: rbac).create!
kubernetes_cluster = Resource::KubernetesCluster.fabricate! do |cluster|
- cluster.project = project
+ cluster.project = @project
cluster.cluster = @cluster
cluster.install_helm_tiller = true
cluster.install_ingress = true
cluster.install_prometheus = true
cluster.install_runner = true
end
+
kubernetes_cluster.populate(:ingress_ip)
- project.visit!
+ @project.visit!
Page::Project::Menu.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p|
p.enable_auto_devops_with_domain(
"#{kubernetes_cluster.ingress_ip}.nip.io")
end
+ end
+
+ after(:all) do
+ @cluster&.remove!
+ end
+
+ before do
+ login
+ end
+
+ it 'runs auto devops' do
+ @project.visit!
+ Page::Project::Menu.act { click_ci_cd_pipelines }
+ Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ expect(pipeline).to have_build('build', status: :success, wait: 600)
+ expect(pipeline).to have_build('test', status: :success, wait: 600)
+ expect(pipeline).to have_build('production', status: :success, wait: 1200)
+ end
+
+ Page::Project::Menu.act { click_operations_environments }
+ Page::Project::Operations::Environments::Index.perform do |index|
+ index.go_to_environment('production')
+ end
+ Page::Project::Operations::Environments::Show.perform do |show|
+ show.view_deployment do
+ expect(page).to have_content('Hello World!')
+ end
+ end
+ end
- project.visit!
+ it 'user sets application secret variable and Auto DevOps passes it to container' do
+ # Set an application secret CI variable (prefixed with K8S_SECRET_)
+ Resource::CiVariable.fabricate! do |resource|
+ resource.project = @project
+ resource.key = 'K8S_SECRET_OPTIONAL_MESSAGE'
+ resource.value = 'You can see this application secret'
+ end
+
+ # Our current Auto DevOps implementation won't update the production
+ # app if we only update a CI variable with no code change.
+ #
+ # Workaround: push new code and use the resultant pipeline.
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = @project
+ push.commit_message = 'Force a Deployment change by pushing new code'
+ push.file_name = 'new_file.txt'
+ push.file_content = 'new file contents'
+ end
+
+ @project.visit!
Page::Project::Menu.act { click_ci_cd_pipelines }
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
@@ -69,12 +123,15 @@ module QA
end
Page::Project::Menu.act { click_operations_environments }
+
Page::Project::Operations::Environments::Index.perform do |index|
index.go_to_environment('production')
end
+
Page::Project::Operations::Environments::Show.perform do |show|
show.view_deployment do
expect(page).to have_content('Hello World!')
+ expect(page).to have_content('You can see this application secret')
end
end
end
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
index cfccbb910b7..e96756642c8 100644
--- a/qa/qa/support/page/logging.rb
+++ b/qa/qa/support/page/logging.rb
@@ -88,6 +88,22 @@ module QA
found
end
+ def has_no_element?(name, wait: Capybara.default_max_wait_time)
+ found = super
+
+ log("has_no_element? :#{name} returned #{found}")
+
+ found
+ end
+
+ def has_text?(text)
+ found = super
+
+ log(%Q{has_text?('#{text}') returned #{found}})
+
+ found
+ end
+
def has_no_text?(text)
found = super
diff --git a/qa/qa/tools/revoke_all_personal_access_tokens.rb b/qa/qa/tools/revoke_all_personal_access_tokens.rb
new file mode 100644
index 00000000000..7484b633bf6
--- /dev/null
+++ b/qa/qa/tools/revoke_all_personal_access_tokens.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require_relative '../../qa'
+require 'net/protocol.rb'
+# This script revokes all personal access tokens with the name of 'api-test-token' on the host specified by GITLAB_ADDRESS
+# Required environment variables: GITLAB_USERNAME, GITLAB_PASSWORD and GITLAB_ADDRESS
+# Run `rake revoke_personal_access_tokens`
+
+module QA
+ module Tools
+ class RevokeAllPersonalAccessTokens
+ def run
+ do_run
+ rescue Net::ReadTimeout
+ STDOUT.puts 'Net::ReadTimeout during run. Trying again'
+ run
+ end
+
+ private
+
+ def do_run
+ raise ArgumentError, "Please provide GITLAB_USERNAME" unless ENV['GITLAB_USERNAME']
+ raise ArgumentError, "Please provide GITLAB_PASSWORD" unless ENV['GITLAB_PASSWORD']
+ raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
+
+ STDOUT.puts 'Running...'
+
+ Runtime::Browser.visit(ENV['GITLAB_ADDRESS'], Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Profile::Menu.perform(&:click_access_tokens)
+
+ token_name = 'api-test-token'
+
+ Page::Profile::PersonalAccessTokens.perform do |page|
+ while page.has_token_row_for_name?(token_name)
+ page.revoke_first_token_with_name(token_name)
+ print "\e[32m.\e[0m"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
index f108a5ca318..2eb826becea 100644
--- a/qa/spec/page/logging_spec.rb
+++ b/qa/spec/page/logging_spec.rb
@@ -74,6 +74,20 @@ describe QA::Support::Page::Logging do
.to output(/has_element\? :element returned true/).to_stdout_from_any_process
end
+ it 'logs has_no_element?' do
+ allow(page).to receive(:has_no_css?).and_return(true)
+
+ expect { subject.has_no_element?(:element) }
+ .to output(/has_no_element\? :element returned true/).to_stdout_from_any_process
+ end
+
+ it 'logs has_text?' do
+ allow(page).to receive(:has_text?).and_return(true)
+
+ expect { subject.has_text? 'foo' }
+ .to output(/has_text\?\('foo'\) returned true/).to_stdout_from_any_process
+ end
+
it 'logs has_no_text?' do
allow(page).to receive(:has_no_text?).with('foo').and_return(true)
diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb
index dc9e16792d3..a2a3ad01749 100644
--- a/qa/spec/resource/base_spec.rb
+++ b/qa/spec/resource/base_spec.rb
@@ -138,10 +138,6 @@ describe QA::Resource::Base do
describe '.attribute' do
include_context 'simple resource'
- it 'appends new attribute' do
- expect(subject.attributes_names).to eq([:no_block, :test, :web_url])
- end
-
context 'when the attribute is populated via a block' do
it 'returns value from the block' do
result = subject.fabricate!(resource: resource)
@@ -217,6 +213,42 @@ describe QA::Resource::Base do
.to raise_error(described_class::NoValueError, "No value was computed for no_block of #{resource.class.name}.")
end
end
+
+ context 'when multiple resources have the same attribute name' do
+ let(:base) do
+ Class.new(QA::Resource::Base) do
+ def fabricate!
+ 'any'
+ end
+
+ def self.current_url
+ 'http://stub'
+ end
+ end
+ end
+ let(:first_resource) do
+ Class.new(base) do
+ attribute :test do
+ 'first block'
+ end
+ end
+ end
+ let(:second_resource) do
+ Class.new(base) do
+ attribute :test do
+ 'second block'
+ end
+ end
+ end
+
+ it 'has unique attribute values' do
+ first_result = first_resource.fabricate!(resource: first_resource.new)
+ second_result = second_resource.fabricate!(resource: second_resource.new)
+
+ expect(first_result.test).to eq 'first block'
+ expect(second_result.test).to eq 'second block'
+ end
+ end
end
describe '#web_url' do
diff --git a/qa/spec/resource/user_spec.rb b/qa/spec/resource/user_spec.rb
new file mode 100644
index 00000000000..d612dfc530e
--- /dev/null
+++ b/qa/spec/resource/user_spec.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+describe QA::Resource::User do
+ let(:api_resource) do
+ {
+ name: "GitLab QA",
+ username: "gitlab-qa",
+ web_url: "https://staging.gitlab.com/gitlab-qa",
+ public_email: "1614863-gitlab-qa@users.noreply.staging.gitlab.com"
+ }
+ end
+
+ describe '#username' do
+ it 'generates a default username' do
+ expect(subject.username).to match(/qa-user-\w+/)
+ end
+
+ it 'is possible to set the username' do
+ subject.username = 'johndoe'
+
+ expect(subject.username).to eq('johndoe')
+ end
+ end
+
+ describe '#password' do
+ it 'generates a default password' do
+ expect(subject.password).to eq('password')
+ end
+
+ it 'is possible to set the password' do
+ subject.password = 'secret'
+
+ expect(subject.password).to eq('secret')
+ end
+ end
+
+ describe '#name' do
+ it 'defaults to the username' do
+ expect(subject.name).to eq(subject.username)
+ end
+
+ it 'retrieves the name from the api_resource if present' do
+ subject.__send__(:api_resource=, api_resource)
+
+ expect(subject.name).to eq(api_resource[:name])
+ end
+
+ it 'is possible to set the name' do
+ subject.name = 'John Doe'
+
+ expect(subject.name).to eq('John Doe')
+ end
+ end
+
+ describe '#email' do
+ it 'defaults to the <username>@example.com' do
+ expect(subject.email).to eq("#{subject.username}@example.com")
+ end
+
+ it 'is possible to set the email' do
+ subject.email = 'johndoe@example.org'
+
+ expect(subject.email).to eq('johndoe@example.org')
+ end
+ end
+
+ describe '#public_email' do
+ it 'defaults to QA::Runtime::User.default_email' do
+ expect(subject.public_email).to eq(QA::Runtime::User.default_email)
+ end
+
+ it 'retrieves the public_email from the api_resource if present' do
+ subject.__send__(:api_resource=, api_resource)
+
+ expect(subject.public_email).to eq(api_resource[:public_email])
+ end
+
+ it 'defaults to QA::Runtime::User.default_email if the public_email from the api_resource is blank' do
+ subject.__send__(:api_resource=, api_resource.merge(public_email: ''))
+
+ expect(subject.public_email).to eq(QA::Runtime::User.default_email)
+ end
+ end
+
+ describe '#credentials_given?' do
+ it 'returns false when username and email have not been overridden' do
+ expect(subject).not_to be_credentials_given
+ end
+
+ it 'returns false even after username and email have been called' do
+ # Call #username and #password to ensure this doesn't set their respective
+ # instance variable.
+ subject.username
+ subject.password
+
+ expect(subject).not_to be_credentials_given
+ end
+
+ it 'returns false if only the username has been overridden' do
+ subject.username = 'johndoe'
+
+ expect(subject).not_to be_credentials_given
+ end
+
+ it 'returns false if only the password has been overridden' do
+ subject.password = 'secret'
+
+ expect(subject).not_to be_credentials_given
+ end
+
+ it 'returns true if both the username and password have been overridden' do
+ subject.username = 'johndoe'
+ subject.password = 'secret'
+
+ expect(subject).to be_credentials_given
+ end
+ end
+end
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index ded51d5bb7c..bc0ec08d66d 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -207,4 +207,63 @@ describe QA::Runtime::Env do
expect { described_class.can_test? :foo }.to raise_error(ArgumentError, 'Unknown feature "foo"')
end
end
+
+ describe 'remote grid credentials' do
+ it 'is blank if username is empty' do
+ stub_env('QA_REMOTE_GRID_USERNAME', nil)
+
+ expect(described_class.send(:remote_grid_credentials)).to eq('')
+ end
+
+ it 'throws ArgumentError if GRID_ACCESS_KEY is not specified with USERNAME' do
+ stub_env('QA_REMOTE_GRID_USERNAME', 'foo')
+
+ expect { described_class.send(:remote_grid_credentials) }.to raise_error(ArgumentError, 'Please provide an access key for user "foo"')
+ end
+
+ it 'returns a user:key@ combination when all args are satiated' do
+ stub_env('QA_REMOTE_GRID_USERNAME', 'foo')
+ stub_env('QA_REMOTE_GRID_ACCESS_KEY', 'bar')
+
+ expect(described_class.send(:remote_grid_credentials)).to eq('foo:bar@')
+ end
+ end
+
+ describe '.remote_grid_protocol' do
+ it 'defaults protocol to http' do
+ stub_env('QA_REMOTE_GRID_PROTOCOL', nil)
+ expect(described_class.remote_grid_protocol).to eq('http')
+ end
+ end
+
+ describe '.remote_grid' do
+ it 'is falsey if QA_REMOTE_GRID is not set' do
+ expect(described_class.remote_grid).to be_falsey
+ end
+
+ it 'accepts https protocol' do
+ stub_env('QA_REMOTE_GRID', 'localhost:4444')
+ stub_env('QA_REMOTE_GRID_PROTOCOL', 'https')
+
+ expect(described_class.remote_grid).to eq('https://localhost:4444/wd/hub')
+ end
+
+ context 'with credentials' do
+ it 'has a grid of http://user:key@grid/wd/hub' do
+ stub_env('QA_REMOTE_GRID_USERNAME', 'foo')
+ stub_env('QA_REMOTE_GRID_ACCESS_KEY', 'bar')
+ stub_env('QA_REMOTE_GRID', 'localhost:4444')
+
+ expect(described_class.remote_grid).to eq('http://foo:bar@localhost:4444/wd/hub')
+ end
+ end
+
+ context 'without credentials' do
+ it 'has a grid of http://grid/wd/hub' do
+ stub_env('QA_REMOTE_GRID', 'localhost:4444')
+
+ expect(described_class.remote_grid).to eq('http://localhost:4444/wd/hub')
+ end
+ end
+ end
end
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 8e01da01340..3537ba7c235 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -5,6 +5,24 @@ Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
RSpec.configure do |config|
config.before do |example|
QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
+
+ # If quarantine is tagged, skip tests that have other metadata unless
+ # they're also tagged. This lets us run quarantined tests in a particular
+ # category without running tests in other categories.
+ # E.g., if a test is tagged 'smoke' and 'quarantine', and another is tagged
+ # 'ldap' and 'quarantine', if we wanted to run just quarantined smoke tests
+ # using `--tag quarantine --tag smoke`, without this check we'd end up
+ # running that ldap test as well.
+ if config.inclusion_filter[:quarantine]
+ skip("Running tests tagged with all of #{config.inclusion_filter.rules.keys}") unless quarantine_and_optional_other_tag?(example, config)
+ end
+ end
+
+ config.before(:each, :quarantine) do |example|
+ # Skip tests in quarantine unless we explicitly focus on them
+ # We could use an exclusion filter, but this way the test report will list
+ # the quarantined tests when they're not run so that we're aware of them
+ skip('In quarantine') unless config.inclusion_filter[:quarantine]
end
config.expect_with :rspec do |expectations|
@@ -22,3 +40,19 @@ RSpec.configure do |config|
config.order = :random
Kernel.srand config.seed
end
+
+# Checks if a test has the 'quarantine' tag and other tags in the inclusion filter.
+#
+# Returns true if
+# - the example metadata includes the quarantine tag
+# - and the metadata and inclusion filter both have any other tag
+# - or no other tags are in the inclusion filter
+def quarantine_and_optional_other_tag?(example, config)
+ return false unless example.metadata.keys.include? :quarantine
+
+ filters_other_than_quarantine = config.inclusion_filter.rules.keys.reject { |key| key == :quarantine }
+
+ return true if filters_other_than_quarantine.empty?
+
+ filters_other_than_quarantine.any? { |key| example.metadata.keys.include? key }
+end
diff --git a/qa/spec/spec_helper_spec.rb b/qa/spec/spec_helper_spec.rb
new file mode 100644
index 00000000000..f001200fb52
--- /dev/null
+++ b/qa/spec/spec_helper_spec.rb
@@ -0,0 +1,264 @@
+# frozen_string_literal: true
+
+describe 'rspec config tests' do
+ let(:group) do
+ RSpec.describe do
+ shared_examples 'passing tests' do
+ example 'not in quarantine' do
+ end
+ example 'in quarantine', :quarantine do
+ end
+ end
+
+ context 'foo', :foo do
+ it_behaves_like 'passing tests'
+ end
+
+ context 'default' do
+ it_behaves_like 'passing tests'
+ end
+ end
+ end
+
+ context 'default config' do
+ it 'tests are skipped if in quarantine' do
+ group.run
+
+ foo_context = group.children.find { |c| c.description == "foo" }
+ foo_examples = foo_context.descendant_filtered_examples
+ expect(foo_examples.count).to eq(2)
+
+ ex = foo_examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = foo_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+
+ default_context = group.children.find { |c| c.description == "default" }
+ default_examples = default_context.descendant_filtered_examples
+ expect(default_examples.count).to eq(2)
+
+ ex = default_examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = default_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
+ end
+
+ context "with 'quarantine' tagged" do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = :quarantine
+ end
+ end
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
+
+ it "only quarantined tests are run" do
+ group.run
+
+ foo_context = group.children.find { |c| c.description == "foo" }
+ foo_examples = foo_context.descendant_filtered_examples
+ expect(foo_examples.count).to be(1)
+
+ ex = foo_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ default_context = group.children.find { |c| c.description == "default" }
+ default_examples = default_context.descendant_filtered_examples
+ expect(default_examples.count).to be(1)
+
+ ex = default_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+ end
+
+ context "with 'foo' tagged" do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = :foo
+ end
+
+ group.run
+ end
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
+
+ it "tests are not run if not tagged 'foo'" do
+ default_context = group.children.find { |c| c.description == "default" }
+ expect(default_context.descendant_filtered_examples.count).to eq(0)
+ end
+
+ it "tests are skipped if in quarantine" do
+ foo_context = group.children.find { |c| c.description == "foo" }
+ foo_examples = foo_context.descendant_filtered_examples
+ expect(foo_examples.count).to eq(2)
+
+ ex = foo_examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = foo_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
+ end
+
+ context "with 'quarantine' and 'foo' tagged" do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = { quarantine: true, foo: true }
+ end
+ end
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
+
+ it 'of tests tagged foo, only tests in quarantine run' do
+ group.run
+
+ foo_context = group.children.find { |c| c.description == "foo" }
+ foo_examples = foo_context.descendant_filtered_examples
+ expect(foo_examples.count).to eq(2)
+
+ ex = foo_examples.find { |e| e.description == "not in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]')
+
+ ex = foo_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+
+ it 'if tests are not tagged they are skipped, even if they are in quarantine' do
+ group.run
+ default_context = group.children.find { |c| c.description == "default" }
+ default_examples = default_context.descendant_filtered_examples
+ expect(default_examples.count).to eq(1)
+
+ ex = default_examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:quarantine, :foo]')
+ end
+ end
+
+ context "with 'foo' and 'bar' tagged" do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = { bar: true, foo: true }
+ end
+ end
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
+
+ it "runs tests tagged either 'foo' or 'bar'" do
+ group = RSpec.describe do
+ example 'foo', :foo do
+ end
+ example 'bar', :bar do
+ end
+ example 'foo and bar', :foo, :bar do
+ end
+ end
+
+ group.run
+ expect(group.examples.count).to eq(3)
+
+ ex = group.examples.find { |e| e.description == "foo" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = group.examples.find { |e| e.description == "bar" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = group.examples.find { |e| e.description == "foo and bar" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+
+ it "skips quarantined tests tagged 'foo' and/or 'bar'" do
+ group = RSpec.describe do
+ example 'foo in quarantine', :foo, :quarantine do
+ end
+ example 'foo and bar in quarantine', :foo, :bar, :quarantine do
+ end
+ end
+
+ group.run
+ expect(group.examples.count).to eq(2)
+
+ ex = group.examples.find { |e| e.description == "foo in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+
+ ex = group.examples.find { |e| e.description == "foo and bar in quarantine" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('In quarantine')
+ end
+
+ it "ignores quarantined tests not tagged either 'foo' or 'bar'" do
+ group = RSpec.describe do
+ example 'in quarantine', :quarantine do
+ end
+ end
+
+ group.run
+
+ ex = group.examples.find { |e| e.description == "in quarantine" }
+ expect(ex.execution_result.status).to be_nil
+ end
+ end
+
+ context "with 'foo' and 'bar' and 'quarantined' tagged" do
+ before do
+ RSpec.configure do |config|
+ config.inclusion_filter = { bar: true, foo: true, quarantine: true }
+ end
+ end
+ after do
+ RSpec.configure do |config|
+ config.inclusion_filter.clear
+ end
+ end
+
+ it "runs tests tagged 'quarantine' and 'foo' or 'bar'" do
+ group = RSpec.describe do
+ example 'foo', :foo do
+ end
+ example 'bar and quarantine', :bar, :quarantine do
+ end
+ example 'foo and bar', :foo, :bar do
+ end
+ example 'foo, bar, and quarantine', :foo, :bar, :quarantine do
+ end
+ end
+
+ group.run
+ expect(group.examples.count).to eq(4)
+
+ ex = group.examples.find { |e| e.description == "foo" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]')
+
+ ex = group.examples.find { |e| e.description == "bar and quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+
+ ex = group.examples.find { |e| e.description == "foo and bar" }
+ expect(ex.execution_result.status).to eq(:pending)
+ expect(ex.execution_result.pending_message).to eq('Running tests tagged with all of [:bar, :foo, :quarantine]')
+
+ ex = group.examples.find { |e| e.description == "foo, bar, and quarantine" }
+ expect(ex.execution_result.status).to eq(:passed)
+ end
+ end
+end
diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb
index c8b8aca51ab..1d37b1bd12d 100644
--- a/rubocop/cop/inject_enterprise_edition_module.rb
+++ b/rubocop/cop/inject_enterprise_edition_module.rb
@@ -11,9 +11,13 @@ module RuboCop
METHODS = Set.new(%i[include extend prepend]).freeze
- def_node_matcher :ee_const?, <<~PATTERN
- (const (const _ :EE) _)
- PATTERN
+ def ee_const?(node)
+ line = node.location.expression.source_line
+
+ # We use `match?` here instead of RuboCop's AST matching, as this makes
+ # it far easier to handle nested constants such as `EE::Foo::Bar::Baz`.
+ line.match?(/(\s|\()(::)?EE::/)
+ end
def on_send(node)
return unless METHODS.include?(node.children[1])
diff --git a/rubocop/spec_helpers.rb b/rubocop/spec_helpers.rb
index 9bf5f1e3b18..63c1b975a65 100644
--- a/rubocop/spec_helpers.rb
+++ b/rubocop/spec_helpers.rb
@@ -1,6 +1,7 @@
module RuboCop
module SpecHelpers
SPEC_HELPERS = %w[fast_spec_helper.rb rails_helper.rb spec_helper.rb].freeze
+ MIGRATION_SPEC_DIRECTORIES = ['spec/migrations', 'spec/lib/gitlab/background_migration'].freeze
# Returns true if the given node originated from the spec directory.
def in_spec?(node)
@@ -10,14 +11,18 @@ module RuboCop
path.start_with?(File.join(Dir.pwd, 'spec'), File.join(Dir.pwd, 'ee', 'spec'))
end
+ def migration_directories
+ @migration_directories ||= MIGRATION_SPEC_DIRECTORIES.map do |dir|
+ [File.join(Dir.pwd, dir), File.join(Dir.pwd, 'ee', dir)]
+ end.flatten
+ end
+
# Returns true if the given node originated from a migration spec.
def in_migration_spec?(node)
path = node.location.expression.source_buffer.name
in_spec?(node) &&
- path.start_with?(
- File.join(Dir.pwd, 'spec', 'migrations'),
- File.join(Dir.pwd, 'ee', 'spec', 'migrations'))
+ path.start_with?(*migration_directories)
end
end
end
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 9e52366f800..6e0dee9e090 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -2,6 +2,7 @@
export TILLER_NAMESPACE="$KUBE_NAMESPACE"
function echoerr() { printf "\033[0;31m%s\n\033[0m" "$*" >&2; }
+function echoinfo() { printf "\033[0;33m%s\n\033[0m" "$*" >&2; }
function check_kube_domain() {
if [ -z ${REVIEW_APPS_DOMAIN+x} ]; then
@@ -31,7 +32,9 @@ function ensure_namespace() {
function install_tiller() {
echo "Checking Tiller..."
- helm init --upgrade
+ helm init \
+ --upgrade \
+ --replicas 2
kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy"
if ! helm version --debug; then
echo "Failed to init Tiller."
@@ -149,19 +152,19 @@ HELM_CMD=$(cat << EOF
--set redis.resources.requests.cpu=100m \
--set minio.resources.requests.cpu=100m \
--set gitlab.migrations.image.repository="$gitlab_migrations_image_repository" \
- --set gitlab.migrations.image.tag="$CI_COMMIT_REF_NAME" \
+ --set gitlab.migrations.image.tag="$CI_COMMIT_REF_SLUG" \
--set gitlab.sidekiq.image.repository="$gitlab_sidekiq_image_repository" \
- --set gitlab.sidekiq.image.tag="$CI_COMMIT_REF_NAME" \
+ --set gitlab.sidekiq.image.tag="$CI_COMMIT_REF_SLUG" \
--set gitlab.unicorn.image.repository="$gitlab_unicorn_image_repository" \
- --set gitlab.unicorn.image.tag="$CI_COMMIT_REF_NAME" \
+ --set gitlab.unicorn.image.tag="$CI_COMMIT_REF_SLUG" \
--set gitlab.task-runner.image.repository="$gitlab_task_runner_image_repository" \
- --set gitlab.task-runner.image.tag="$CI_COMMIT_REF_NAME" \
+ --set gitlab.task-runner.image.tag="$CI_COMMIT_REF_SLUG" \
--set gitlab.gitaly.image.repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitaly" \
--set gitlab.gitaly.image.tag="v$GITALY_VERSION" \
--set gitlab.gitlab-shell.image.repository="registry.gitlab.com/gitlab-org/build/cng-mirror/gitlab-shell" \
--set gitlab.gitlab-shell.image.tag="v$GITLAB_SHELL_VERSION" \
--set gitlab.unicorn.workhorse.image="$gitlab_workhorse_image_repository" \
- --set gitlab.unicorn.workhorse.tag="$CI_COMMIT_REF_NAME" \
+ --set gitlab.unicorn.workhorse.tag="$CI_COMMIT_REF_SLUG" \
--set nginx-ingress.controller.config.ssl-ciphers="ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4" \
--namespace="$KUBE_NAMESPACE" \
--version="$CI_PIPELINE_ID-$CI_JOB_ID" \
@@ -236,17 +239,17 @@ function get_pod() {
local app_name="${1}"
local status="${2-Running}"
get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name"
- echoerr "Running '${get_pod_cmd}'"
+ echoinfo "Running '${get_pod_cmd}'"
while true; do
local pod_name="$(eval $get_pod_cmd)"
[[ "${pod_name}" == "" ]] || break
- echoerr "Waiting till '${app_name}' pod is ready";
+ echoinfo "Waiting till '${app_name}' pod is ready";
sleep 5;
done
- echoerr "The pod name is '${pod_name}'."
+ echoinfo "The pod name is '${pod_name}'."
echo "${pod_name}"
}
@@ -288,7 +291,7 @@ function get_job_id() {
while true; do
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
- echoerr "GET ${url}"
+ echoinfo "GET ${url}"
local job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
[[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
@@ -299,7 +302,7 @@ function get_job_id() {
if [[ "${job_id}" == "" ]]; then
echoerr "The '${job_name}' job ID couldn't be retrieved!"
else
- echoerr "The '${job_name}' job ID is ${job_id}"
+ echoinfo "The '${job_name}' job ID is ${job_id}"
echo "${job_id}"
fi
}
@@ -310,10 +313,10 @@ function play_job() {
if [ -z "${job_id}" ]; then return; fi
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
- echoerr "POST ${url}"
+ echoinfo "POST ${url}"
local job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".web_url")
- echo "Manual job '${job_name}' started at: ${job_url}"
+ echoinfo "Manual job '${job_name}' started at: ${job_url}"
}
function wait_for_job_to_be_done() {
@@ -322,10 +325,10 @@ function wait_for_job_to_be_done() {
local job_id=$(get_job_id "${job_name}" "${query_string}");
if [ -z "${job_id}" ]; then return; fi
- echoerr "Waiting for the '${job_name}' job to finish..."
+ echoinfo "Waiting for the '${job_name}' job to finish..."
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}"
- echo "GET ${url}"
+ echoinfo "GET ${url}"
# In case the job hasn't finished yet. Keep trying until the job times out.
local interval=30
@@ -340,13 +343,13 @@ function wait_for_job_to_be_done() {
done
local elapsed_minutes=$((elapsed_seconds / 60))
- echoerr "Waited '${job_name}' for ${elapsed_minutes} minutes."
+ echoinfo "Waited '${job_name}' for ${elapsed_minutes} minutes."
if [[ "${job_status}" == "failed" ]]; then
- echo "The '${job_name}' failed."
+ echoerr "The '${job_name}' failed."
elif [[ "${job_status}" == "manual" ]]; then
- echo "The '${job_name}' is manual."
+ echoinfo "The '${job_name}' is manual."
else
- echo "The '${job_name}' passed."
+ echoinfo "The '${job_name}' passed."
fi
}
diff --git a/scripts/trigger-build b/scripts/trigger-build
index 4032ba853e6..fbf35e7217c 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -68,7 +68,7 @@ module Trigger
def base_variables
{
- 'GITLAB_REF_SLUG' => ENV['CI_COMMIT_REF_SLUG'],
+ 'GITLAB_REF_SLUG' => ENV['CI_COMMIT_TAG'] ? ENV['CI_COMMIT_REF_NAME'] : ENV['CI_COMMIT_REF_SLUG'],
'TRIGGERED_USER' => ENV['TRIGGERED_USER'] || ENV['GITLAB_USER_NAME'],
'TRIGGER_SOURCE' => ENV['CI_JOB_URL'],
'TOP_UPSTREAM_SOURCE_PROJECT' => ENV['CI_PROJECT_PATH'],
@@ -137,7 +137,10 @@ module Trigger
edition = Trigger.ee? ? 'EE' : 'CE'
{
+ # Back-compatibility until https://gitlab.com/gitlab-org/build/CNG/merge_requests/189 is merged
"GITLAB_#{edition}_VERSION" => ENV['CI_COMMIT_REF_NAME'],
+ "GITLAB_VERSION" => ENV['CI_COMMIT_REF_NAME'],
+ "GITLAB_ASSETS_TAG" => ENV['CI_COMMIT_REF_SLUG'],
"#{edition}_PIPELINE" => 'true'
}
end
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index c59add88a82..b2c2fb810e8 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -80,7 +80,7 @@ describe 'bin/changelog' do
end
end
- describe '.read_type' do
+ describe '.read_type' do
let(:type) { '1' }
it 'reads type from $stdin' do
diff --git a/spec/controllers/admin/requests_profiles_controller_spec.rb b/spec/controllers/admin/requests_profiles_controller_spec.rb
new file mode 100644
index 00000000000..10850cb4603
--- /dev/null
+++ b/spec/controllers/admin/requests_profiles_controller_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Admin::RequestsProfilesController do
+ set(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe '#show' do
+ let(:basename) { "profile_#{Time.now.to_i}.html" }
+ let(:tmpdir) { Dir.mktmpdir('profiler-test') }
+ let(:test_file) { File.join(tmpdir, basename) }
+ let(:profile) { Gitlab::RequestProfiler::Profile.new(basename) }
+ let(:sample_data) do
+ <<~HTML
+ <!DOCTYPE html>
+ <html>
+ <body>
+ <h1>My First Heading</h1>
+ <p>My first paragraph.</p>
+ </body>
+ </html>
+ HTML
+ end
+
+ before do
+ stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
+ output = File.open(test_file, 'w')
+ output.write(sample_data)
+ output.close
+ end
+
+ after do
+ File.unlink(test_file)
+ end
+
+ it 'loads an HTML profile' do
+ get :show, params: { name: basename }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq(sample_data)
+ end
+ end
+end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 43f561f7a25..c9e520317e8 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -423,7 +423,7 @@ describe ApplicationController do
enforce_terms
end
- it 'redirects if the user did not accept the terms' do
+ it 'redirects if the user did not accept the terms' do
get :index
expect(response).to have_gitlab_http_status(302)
@@ -519,12 +519,14 @@ describe ApplicationController do
get :index
expect(response).to have_gitlab_http_status(404)
+ expect(response).to render_template('errors/not_found')
end
it 'renders a 403 when a message is passed to access denied' do
get :index, params: { message: 'None shall pass' }
expect(response).to have_gitlab_http_status(403)
+ expect(response).to render_template('errors/access_denied')
end
it 'renders a status passed to access denied' do
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index 8657fc2ebc0..5eb05f01b8d 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Boards::IssuesController do
- let(:project) { create(:project) }
+ let(:project) { create(:project, :private) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
let(:guest) { create(:user) }
@@ -127,14 +127,10 @@ describe Boards::IssuesController do
end
context 'with unauthorized user' do
- before do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
- allow(Ability).to receive(:allowed?).with(user, :read_issue, project).and_return(false)
- end
+ let(:unauth_user) { create(:user) }
it 'returns a forbidden 403 response' do
- list_issues user: user, board: board, list: list2
+ list_issues user: unauth_user, board: board, list: list2
expect(response).to have_gitlab_http_status(403)
end
@@ -233,7 +229,7 @@ describe Boards::IssuesController do
post :create, params: {
board_id: board.to_param,
list_id: list.to_param,
- issue: { title: title, project_id: project.id }
+ issue: { title: title, project_id: project.id }
},
format: :json
end
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index 70033857168..e5b8aa2e678 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -31,13 +31,10 @@ describe Boards::ListsController do
end
context 'with unauthorized user' do
- before do
- allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
- allow(Ability).to receive(:allowed?).with(user, :read_list, project).and_return(false)
- end
+ let(:unauth_user) { create(:user) }
it 'returns a forbidden 403 response' do
- read_board_list user: user, board: board
+ read_board_list user: unauth_user, board: board
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/controllers/concerns/checks_collaboration_spec.rb b/spec/controllers/concerns/checks_collaboration_spec.rb
index 1bd764290ae..d7f110e11f3 100644
--- a/spec/controllers/concerns/checks_collaboration_spec.rb
+++ b/spec/controllers/concerns/checks_collaboration_spec.rb
@@ -22,7 +22,7 @@ describe ChecksCollaboration do
end
end
- it 'is true if the user can push to the project' do
+ it 'is true if the user can push to the project' do
project.add_developer(user)
expect(helper.can_collaborate_with_project?(project)).to be_truthy
diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb
index c9ccd5f7c55..8b176e07bc8 100644
--- a/spec/controllers/dashboard/milestones_controller_spec.rb
+++ b/spec/controllers/dashboard/milestones_controller_spec.rb
@@ -56,6 +56,24 @@ describe Dashboard::MilestonesController do
expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
end
+ it 'searches legacy project milestones by title when search_title is given' do
+ project_milestone = create(:milestone, title: 'Project milestone title', project: project)
+
+ get :index, params: { search_title: 'Project mil' }
+
+ expect(response.body).to include(project_milestone.title)
+ expect(response.body).not_to include(group_milestone.title)
+ end
+
+ it 'searches group milestones by title when search_title is given' do
+ group_milestone = create(:milestone, title: 'Group milestone title', group: group)
+
+ get :index, params: { search_title: 'Group mil' }
+
+ expect(response.body).to include(group_milestone.title)
+ expect(response.body).not_to include(project_milestone.title)
+ end
+
it 'should contain group and project milestones to which the user belongs to' do
get :index
diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb
index 4d5bb1488ab..e1b97013408 100644
--- a/spec/controllers/groups/children_controller_spec.rb
+++ b/spec/controllers/groups/children_controller_spec.rb
@@ -110,7 +110,7 @@ describe Groups::ChildrenController do
matched_project_1 = create(:project, :public, namespace: shared_subgroup, name: 'mobile-soc')
l2_subgroup = create(:group, :public, parent: shared_subgroup, path: 'broadcom')
- l3_subgroup = create(:group, :public, parent: l2_subgroup, path: 'wifi-group')
+ l3_subgroup = create(:group, :public, parent: l2_subgroup, path: 'wifi-group')
matched_project_2 = create(:project, :public, namespace: l3_subgroup, name: 'mobile')
get :index, params: { group_id: group.to_param, filter: 'mobile' }, format: :json
@@ -289,7 +289,7 @@ describe Groups::ChildrenController do
end
context 'with subgroups and projects', :nested_groups do
- let!(:first_page_subgroups) { create_list(:group, per_page, :public, parent: group) }
+ let!(:first_page_subgroups) { create_list(:group, per_page, :public, parent: group) }
let!(:other_subgroup) { create(:group, :public, parent: group) }
let!(:next_page_projects) { create_list(:project, per_page, :public, namespace: group) }
@@ -306,7 +306,7 @@ describe Groups::ChildrenController do
end
context 'with a mixed first page' do
- let!(:first_page_subgroups) { [create(:group, :public, parent: group)] }
+ let!(:first_page_subgroups) { [create(:group, :public, parent: group)] }
let!(:first_page_projects) { create_list(:project, per_page, :public, namespace: group) }
it 'correctly calculates the counts' do
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 40d991a669c..043cf28514b 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -32,10 +32,35 @@ describe Groups::MilestonesController do
end
describe '#index' do
- it 'shows group milestones page' do
- get :index, params: { group_id: group.to_param }
+ describe 'as HTML' do
+ render_views
- expect(response).to have_gitlab_http_status(200)
+ it 'shows group milestones page' do
+ milestone
+
+ get :index, params: { group_id: group.to_param }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to include(milestone.title)
+ end
+
+ it 'searches legacy milestones by title when search_title is given' do
+ project_milestone = create(:milestone, project: project, title: 'Project milestone title')
+
+ get :index, params: { group_id: group.to_param, search_title: 'Project mil' }
+
+ expect(response.body).to include(project_milestone.title)
+ expect(response.body).not_to include(milestone.title)
+ end
+
+ it 'searches group milestones by title when search_title is given' do
+ group_milestone = create(:milestone, title: 'Group milestone title', group: group)
+
+ get :index, params: { group_id: group.to_param, search_title: 'Group mil' }
+
+ expect(response.body).to include(group_milestone.title)
+ expect(response.body).not_to include(milestone.title)
+ end
end
context 'as JSON' do
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index 73195463a50..bb282db5a41 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -78,7 +78,7 @@ describe Import::BitbucketServerController do
end
it "returns an error when the server can't be contacted" do
- expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(BitbucketServer::Client::ServerError)
+ expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
post :create, params: { project: project_key, repository: repo_slug }, format: :json
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index d377d69457f..59463462e5a 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -45,6 +45,40 @@ describe OmniauthCallbacksController, type: :controller do
end
end
+ context 'when a redirect fragment is provided' do
+ let(:provider) { :jwt }
+ let(:extern_uid) { 'my-uid' }
+
+ before do
+ request.env['omniauth.params'] = { 'redirect_fragment' => 'L101' }
+ end
+
+ context 'when a redirect url is stored' do
+ it 'redirects with fragment' do
+ post provider, session: { user_return_to: '/fake/url' }
+
+ expect(response).to redirect_to('/fake/url#L101')
+ end
+ end
+
+ context 'when a redirect url with a fragment is stored' do
+ it 'redirects with the new fragment' do
+ post provider, session: { user_return_to: '/fake/url#replaceme' }
+
+ expect(response).to redirect_to('/fake/url#L101')
+ end
+ end
+
+ context 'when no redirect url is stored' do
+ it 'does not redirect with the fragment' do
+ post provider
+
+ expect(response.redirect?).to be true
+ expect(response.location).not_to include('#L101')
+ end
+ end
+ end
+
context 'strategies' do
context 'github' do
let(:extern_uid) { 'my-uid' }
diff --git a/spec/controllers/profiles/avatars_controller_spec.rb b/spec/controllers/profiles/avatars_controller_spec.rb
index 909709e1103..1ee0bf44e92 100644
--- a/spec/controllers/profiles/avatars_controller_spec.rb
+++ b/spec/controllers/profiles/avatars_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Profiles::AvatarsController do
- let(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png")) }
+ let(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png")) }
before do
sign_in(user)
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
index 2556bc3ae50..8eac3d9a459 100644
--- a/spec/controllers/projects/badges_controller_spec.rb
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -22,7 +22,44 @@ describe Projects::BadgesController do
expect(response).to have_gitlab_http_status(:ok)
end
- def get_badge(badge)
- get badge, params: { namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref }, format: :svg
+ it 'renders the `flat` badge layout by default' do
+ get_badge(:coverage)
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+
+ context 'when style param is set to `flat`' do
+ it 'renders the `flat` badge layout' do
+ get_badge(:coverage, 'flat')
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
+
+ context 'when style param is set to an invalid type' do
+ it 'renders the `flat` (default) badge layout' do
+ get_badge(:coverage, 'xxx')
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
+
+ context 'when style param is set to `flat-square`' do
+ it 'renders the `flat-square` badge layout' do
+ get_badge(:coverage, 'flat-square')
+
+ expect(response).to render_template('projects/badges/badge_flat-square')
+ end
+ end
+
+ def get_badge(badge, style = nil)
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ ref: pipeline.ref,
+ style: style
+ }
+
+ get badge, params: params, format: :svg
end
end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 02b3d5269a6..52a20fa8d07 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -331,7 +331,7 @@ describe Projects::BranchesController do
let(:branch) { "feature" }
it 'returns JSON response with message' do
- expect(json_response).to eql("message" => 'Branch was removed')
+ expect(json_response).to eql("message" => 'Branch was deleted')
end
it { expect(response).to have_gitlab_http_status(200) }
@@ -341,7 +341,7 @@ describe Projects::BranchesController do
let(:branch) { "improve/awesome" }
it 'returns JSON response with message' do
- expect(json_response).to eql('message' => 'Branch was removed')
+ expect(json_response).to eql('message' => 'Branch was deleted')
end
it { expect(response).to have_gitlab_http_status(200) }
@@ -351,7 +351,7 @@ describe Projects::BranchesController do
let(:branch) { 'improve%2Fawesome' }
it 'returns JSON response with message' do
- expect(json_response).to eql('message' => 'Branch was removed')
+ expect(json_response).to eql('message' => 'Branch was deleted')
end
it { expect(response).to have_gitlab_http_status(200) }
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 26eec90da06..19cac47325c 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -5,7 +5,7 @@ describe Projects::CommitController do
set(:user) { create(:user) }
let(:commit) { project.commit("master") }
let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
- let(:master_pickable_commit) { project.commit(master_pickable_sha) }
+ let(:master_pickable_commit) { project.commit(master_pickable_sha) }
before do
sign_in(user)
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb
new file mode 100644
index 00000000000..6464398cea1
--- /dev/null
+++ b/spec/controllers/projects/error_tracking_controller_spec.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Projects::ErrorTrackingController do
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ project.add_maintainer(user)
+ end
+
+ describe 'GET #index' do
+ describe 'html' do
+ it 'renders index with 200 status code' do
+ get :index, params: project_params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ end
+
+ context 'with insufficient permissions' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'returns 404' do
+ get :index, params: project_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'with an anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it 'redirects to sign-in page' do
+ get :index, params: project_params
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ describe 'format json' do
+ shared_examples 'no data' do
+ it 'returns no data' do
+ get :index, params: project_params(format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('error_tracking/index')
+ expect(json_response['external_url']).to be_nil
+ expect(json_response['errors']).to eq([])
+ end
+ end
+
+ let(:list_issues_service) { spy(:list_issues_service) }
+ let(:external_url) { 'http://example.com' }
+
+ before do
+ expect(ErrorTracking::ListIssuesService)
+ .to receive(:new).with(project, user)
+ .and_return(list_issues_service)
+ end
+
+ context 'service result is successful' do
+ before do
+ expect(list_issues_service).to receive(:execute)
+ .and_return(status: :success, issues: [error])
+ expect(list_issues_service).to receive(:external_url)
+ .and_return(external_url)
+ end
+
+ let(:error) { build(:error_tracking_error) }
+
+ it 'returns a list of errors' do
+ get :index, params: project_params(format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('error_tracking/index')
+ expect(json_response['external_url']).to eq(external_url)
+ expect(json_response['errors']).to eq([error].as_json)
+ end
+ end
+
+ context 'service result is erroneous' do
+ let(:error_message) { 'error message' }
+
+ context 'without http_status' do
+ before do
+ expect(list_issues_service).to receive(:execute)
+ .and_return(status: :error, message: error_message)
+ end
+
+ it 'returns 400 with message' do
+ get :index, params: project_params(format: :json)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq(error_message)
+ end
+ end
+
+ context 'with explicit http_status' do
+ let(:http_status) { :no_content }
+
+ before do
+ expect(list_issues_service).to receive(:execute)
+ .and_return(status: :error, message: error_message, http_status: http_status)
+ end
+
+ it 'returns http_status with message' do
+ get :index, params: project_params(format: :json)
+
+ expect(response).to have_gitlab_http_status(http_status)
+ expect(json_response['message']).to eq(error_message)
+ end
+ end
+ end
+ end
+ end
+
+ private
+
+ def project_params(opts = {})
+ opts.reverse_merge(namespace_id: project.namespace, project_id: project)
+ end
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index a239ac16c0d..a2c3bb2919d 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1026,6 +1026,59 @@ describe Projects::IssuesController do
end
end
+ describe 'POST #import_csv' do
+ let(:project) { create(:project, :public) }
+ let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') }
+
+ context 'unauthorized' do
+ it 'returns 404 for guests' do
+ sign_out(:user)
+
+ import_csv
+
+ expect(response).to have_gitlab_http_status :not_found
+ end
+
+ it 'returns 404 for project members with reporter role' do
+ sign_in(user)
+ project.add_reporter(user)
+
+ import_csv
+
+ expect(response).to have_gitlab_http_status :not_found
+ end
+ end
+
+ context 'authorized' do
+ before do
+ sign_in(user)
+ project.add_developer(user)
+ end
+
+ it "returns 302 for project members with developer role" do
+ import_csv
+
+ expect(flash[:notice]).to include('Your issues are being imported')
+ expect(response).to redirect_to(project_issues_path(project))
+ end
+
+ it "shows error when upload fails" do
+ allow_any_instance_of(UploadService).to receive(:execute).and_return(nil)
+
+ import_csv
+
+ expect(flash[:alert]).to include('File upload error.')
+ expect(response).to redirect_to(project_issues_path(project))
+ end
+ end
+
+ def import_csv
+ post :import_csv, params: { namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ file: file }
+ end
+ end
+
describe 'GET #discussions' do
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
context 'when authenticated' do
@@ -1065,6 +1118,7 @@ describe Projects::IssuesController do
context 'when user is setting notes filters' do
let(:issuable) { issue }
+ let(:issuable_parent) { project }
let!(:discussion_note) { create(:discussion_note_on_issue, :system, noteable: issuable, project: project) }
it_behaves_like 'issuable notes filter'
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 7f65fe551e9..d8a331b3cf0 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -770,50 +770,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
- describe 'POST cancel_all' do
- before do
- project.add_developer(user)
- sign_in(user)
- end
-
- context 'when jobs are cancelable' do
- before do
- create_list(:ci_build, 2, :cancelable, pipeline: pipeline)
-
- post_cancel_all
- end
-
- it 'redirects to a index page' do
- expect(response).to have_gitlab_http_status(:found)
- expect(response).to redirect_to(namespace_project_jobs_path)
- end
-
- it 'transits to canceled' do
- expect(Ci::Build.all).to all(be_canceled)
- end
- end
-
- context 'when jobs are not cancelable' do
- before do
- create_list(:ci_build, 2, :canceled, pipeline: pipeline)
-
- post_cancel_all
- end
-
- it 'redirects to a index page' do
- expect(response).to have_gitlab_http_status(:found)
- expect(response).to redirect_to(namespace_project_jobs_path)
- end
- end
-
- def post_cancel_all
- post :cancel_all, params: {
- namespace_id: project.namespace,
- project_id: project
- }
- end
- end
-
describe 'POST erase' do
let(:role) { :maintainer }
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index ac93393ac3a..f031a74c5bd 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -71,7 +71,7 @@ describe Projects::MergeRequests::CreationsController do
expect(response).to be_success
total = assigns(:total_commit_count)
- expect(assigns(:commits)).to be_an Array
+ expect(assigns(:commits)).to be_an CommitCollection
expect(total).to be > 0
expect(assigns(:hidden_commit_count)).to eq(0)
expect(response).to have_gitlab_http_status(200)
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 4f4d3ca226f..53d5bf752ef 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -78,6 +78,7 @@ describe Projects::MergeRequestsController do
context 'when user is setting notes filters' do
let(:issuable) { merge_request }
+ let(:issuable_parent) { project }
let!(:discussion_note) { create(:discussion_note_on_merge_request, :system, noteable: issuable, project: project) }
let!(:discussion_comment) { create(:discussion_note_on_merge_request, noteable: issuable, project: project) }
@@ -252,8 +253,8 @@ describe Projects::MergeRequestsController do
end
context 'there is no source project' do
- let(:project) { create(:project, :repository) }
- let(:forked_project) { fork_project_with_submodules(project) }
+ let(:project) { create(:project, :repository) }
+ let(:forked_project) { fork_project_with_submodules(project) }
let!(:merge_request) { create(:merge_request, source_project: forked_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
before do
@@ -883,7 +884,7 @@ describe Projects::MergeRequestsController do
end
describe 'POST #rebase' do
- let(:viewer) { user }
+ let(:viewer) { user }
def post_rebase
post :rebase, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 5892024e756..ac54b3c3952 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -42,10 +42,11 @@ describe Projects::MilestonesController do
describe "#index" do
context "as html" do
- def render_index(project:, page:)
+ def render_index(project:, page:, search_title: '')
get :index, params: {
namespace_id: project.namespace.id,
project_id: project.id,
+ search_title: search_title,
page: page
}
end
@@ -59,6 +60,15 @@ describe Projects::MilestonesController do
expect(milestones.where(project_id: nil)).to be_empty
end
+ it 'searches milestones by title when search_title is given' do
+ milestone1 = create(:milestone, title: 'Project milestone title', project: project)
+
+ render_index project: project, page: 1, search_title: 'Project mile'
+
+ milestones = assigns(:milestones)
+ expect(milestones).to eq([milestone1])
+ end
+
it 'renders paginated milestones without missing or duplicates' do
allow(Milestone).to receive(:default_per_page).and_return(2)
create_list(:milestone, 5, project: project)
diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb
index 382c1b5d124..4b742a5d427 100644
--- a/spec/controllers/projects/pages_controller_spec.rb
+++ b/spec/controllers/projects/pages_controller_spec.rb
@@ -28,10 +28,10 @@ describe Projects::PagesController do
let(:group) { create(:group, :nested) }
let(:project) { create(:project, namespace: group) }
- it 'returns a 404 status code' do
+ it 'returns a 200 status code' do
get :show, params: request_params
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(200)
end
end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 0bb3ef76a3b..97e04a63d4a 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -50,7 +50,7 @@ describe Projects::PipelinesController do
end
end
- context 'when using legacy stages', :request_store do
+ context 'when using legacy stages', :request_store do
before do
stub_feature_flags(ci_pipeline_persisted_stages: false)
end
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
index ed0197afcfc..74ed89ba1c3 100644
--- a/spec/controllers/projects/registry/tags_controller_spec.rb
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::Registry::TagsController do
end
before do
- stub_container_registry_tags(repository: /image/, tags: tags)
+ stub_container_registry_tags(repository: /image/, tags: tags, with_manifest: true)
end
context 'when user can control the registry' do
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index f170a2ab613..5b9d21d3d5b 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -6,10 +6,6 @@ describe Projects::ReleasesController do
let!(:project) { create(:project, :repository, :public) }
let!(:user) { create(:user) }
- before do
- stub_feature_flags(releases_page: true)
- end
-
describe 'GET #index' do
it 'renders a 200' do
get_index
@@ -43,18 +39,6 @@ describe Projects::ReleasesController do
expect(response.status).to eq(404)
end
end
-
- context 'when releases_page feature flag is disabled' do
- before do
- stub_feature_flags(releases_page: false)
- end
-
- it 'renders a 404' do
- get_index
-
- expect(response.status).to eq(404)
- end
- end
end
private
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
index a9759c4fbd8..276cf340962 100644
--- a/spec/controllers/projects/serverless/functions_controller_spec.rb
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -45,13 +45,49 @@ describe Projects::Serverless::FunctionsController do
end
end
+ describe 'GET #show' do
+ context 'invalid data' do
+ it 'has a bad function name' do
+ get :show, params: params({ format: :json, environment_id: "*", id: "foo" })
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'valid data', :use_clean_rails_memory_store_caching do
+ before do
+ stub_kubeclient_service_pods
+ stub_reactive_cache(knative,
+ {
+ services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
+ pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
+ })
+ end
+
+ it 'has a valid function name' do
+ get :show, params: params({ format: :json, environment_id: "*", id: cluster.project.name })
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response).to include(
+ "name" => project.name,
+ "url" => "http://#{project.name}.#{namespace.namespace}.example.com",
+ "podcount" => 1
+ )
+ end
+ end
+ end
+
describe 'GET #index with data', :use_clean_rails_memory_store_caching do
before do
- stub_reactive_cache(knative, services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"])
+ stub_kubeclient_service_pods
+ stub_reactive_cache(knative,
+ {
+ services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
+ pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
+ })
end
it 'has data' do
- get :index, params: params({ format: :json })
+ get :index, params: params({ format: :json })
expect(response).to have_gitlab_http_status(200)
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
new file mode 100644
index 00000000000..d989ec22481
--- /dev/null
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::Settings::OperationsController do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+
+ before do
+ sign_in(user)
+ project.add_maintainer(user)
+ end
+
+ context 'error tracking' do
+ describe 'GET #show' do
+ it 'renders show template' do
+ get :show, params: project_params(project)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ end
+
+ context 'with existing setting' do
+ let!(:error_tracking_setting) do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ it 'loads existing setting' do
+ get :show, params: project_params(project)
+
+ expect(controller.helpers.error_tracking_setting)
+ .to eq(error_tracking_setting)
+ end
+ end
+
+ context 'without an existing setting' do
+ it 'builds a new setting' do
+ get :show, params: project_params(project)
+
+ expect(controller.helpers.error_tracking_setting).to be_new_record
+ end
+ end
+
+ context 'with insufficient permissions' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'renders 404' do
+ get :show, params: project_params(project)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'as an anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it 'redirects to signup page' do
+ get :show, params: project_params(project)
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ describe 'PATCH #update' do
+ let(:operations_update_service) { spy(:operations_update_service) }
+ let(:operations_url) { project_settings_operations_url(project) }
+
+ let(:error_tracking_params) do
+ {
+ error_tracking_setting_attributes: {
+ enabled: '1',
+ api_url: 'http://url',
+ token: 'token'
+ }
+ }
+ end
+ let(:error_tracking_permitted) do
+ ActionController::Parameters.new(error_tracking_params).permit!
+ end
+
+ context 'when update succeeds' do
+ before do
+ stub_operations_update_service_returning(status: :success)
+ end
+
+ it 'shows a notice' do
+ patch :update, params: project_params(project, error_tracking_params)
+
+ expect(response).to redirect_to(operations_url)
+ expect(flash[:notice]).to eq _('Your changes have been saved')
+ end
+ end
+
+ context 'when update fails' do
+ before do
+ stub_operations_update_service_returning(status: :error)
+ end
+
+ it 'renders show page' do
+ patch :update, params: project_params(project, error_tracking_params)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ end
+ end
+
+ context 'with insufficient permissions' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'renders 404' do
+ patch :update, params: project_params(project)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'as an anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it 'redirects to signup page' do
+ patch :update, params: project_params(project)
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ private
+
+ def stub_operations_update_service_returning(return_value = {})
+ expect(::Projects::Operations::UpdateService)
+ .to receive(:new).with(project, user, error_tracking_permitted)
+ .and_return(operations_update_service)
+ expect(operations_update_service).to receive(:execute)
+ .and_return(return_value)
+ end
+ end
+
+ private
+
+ def project_params(project, params = {})
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ project: params
+ }
+ end
+end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index f84f069f4db..a1662658ade 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -456,7 +456,7 @@ describe ProjectsController do
end
context "when the project is forked" do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository) }
let(:forked_project) { fork_project(project, nil, repository: true) }
let(:merge_request) do
create(:merge_request,
@@ -955,6 +955,59 @@ describe ProjectsController do
end
end
+ describe 'GET resolve' do
+ shared_examples 'resolvable endpoint' do
+ it 'redirects to the project page' do
+ get :resolve, params: { id: project.id }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(response).to redirect_to(project_path(project))
+ end
+ end
+
+ context 'with an authenticated user' do
+ before do
+ sign_in(user)
+ end
+
+ context 'when user has access to the project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'resolvable endpoint'
+ end
+
+ context 'when user has no access to the project' do
+ it 'gives 404 for existing project' do
+ get :resolve, params: { id: project.id }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ it 'gives 404 for non-existing project' do
+ get :resolve, params: { id: '0' }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'non authenticated user' do
+ context 'with a public project' do
+ let(:project) { public_project }
+
+ it_behaves_like 'resolvable endpoint'
+ end
+
+ it 'gives 404 for private project' do
+ get :resolve, params: { id: project.id }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
def project_moved_message(redirect_route, project)
"Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path."
end
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index c9b53336fd1..02a0cfe0272 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe SearchController do
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
before do
sign_in(user)
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 19142aa1272..e52a5fe42f2 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -12,6 +12,12 @@ shared_examples 'content not cached without revalidation and no-store' do
end
end
+shared_examples 'content publicly cached' do
+ it 'ensures content is publicly cached' do
+ expect(subject['Cache-Control']).to eq('max-age=300, public')
+ end
+end
+
describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
@@ -184,7 +190,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation and no-store' do
+ it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' }
@@ -201,7 +207,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' }
@@ -321,7 +327,7 @@ describe UploadsController do
end
context "when viewing a group avatar" do
- let!(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
+ let!(:group) { create(:group, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
context "when the group is public" do
context "when not signed in" do
@@ -537,7 +543,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png' }
@@ -557,7 +563,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png' }
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 7c505ee0d43..897b4411055 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -64,6 +64,7 @@ describe 'Database schema' do
let(:indexes) { connection.indexes(table) }
let(:columns) { connection.columns(table) }
let(:foreign_keys) { connection.foreign_keys(table) }
+ let(:primary_key_column) { connection.primary_key(table) }
context 'all foreign keys' do
# for index to be effective, the FK constraint has to be at first place
@@ -71,6 +72,12 @@ describe 'Database schema' do
first_indexed_column = indexes.map(&:columns).map(&:first)
foreign_keys_columns = foreign_keys.map(&:column)
+ # Add the primary key column to the list of indexed columns because
+ # postgres and mysql both automatically create an index on the primary
+ # key. Also, the rails connection.indexes() method does not return
+ # automatically generated indexes (like the primary key index).
+ first_indexed_column = first_indexed_column.push(primary_key_column)
+
expect(first_indexed_column.uniq).to include(*foreign_keys_columns)
end
end
diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb
index 5f83b80ad7b..39427f416a0 100644
--- a/spec/factories/ci/bridge.rb
+++ b/spec/factories/ci/bridge.rb
@@ -10,8 +10,16 @@ FactoryBot.define do
pipeline factory: :ci_pipeline
+ transient { downstream nil }
+
after(:build) do |bridge, evaluator|
bridge.project ||= bridge.pipeline.project
+
+ if evaluator.downstream.present?
+ bridge.options = bridge.options.to_h.merge(
+ trigger: { project: evaluator.downstream.full_path }
+ )
+ end
end
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 07c1fc31152..0b3e67b4987 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -7,7 +7,6 @@ FactoryBot.define do
stage_idx 0
ref 'master'
tag false
- commands 'ls -a'
protected false
created_at 'Di 29. Okt 09:50:00 CET 2013'
pending
@@ -15,7 +14,8 @@ FactoryBot.define do
options do
{
image: 'ruby:2.1',
- services: ['postgres']
+ services: ['postgres'],
+ script: ['ls -a']
}
end
@@ -28,7 +28,6 @@ FactoryBot.define do
pipeline factory: :ci_pipeline
trait :degenerated do
- commands nil
options nil
yaml_variables nil
end
@@ -79,7 +78,7 @@ FactoryBot.define do
trait :scheduled do
schedulable
status 'scheduled'
- scheduled_at { 1.minute.since }
+ scheduled_at { 1.minute.since }
end
trait :expired_scheduled do
@@ -95,33 +94,53 @@ FactoryBot.define do
trait :teardown_environment do
environment 'staging'
- options environment: { name: 'staging',
- action: 'stop',
- url: 'http://staging.example.com/$CI_JOB_NAME' }
+ options do
+ {
+ script: %w(ls),
+ environment: { name: 'staging',
+ action: 'stop',
+ url: 'http://staging.example.com/$CI_JOB_NAME' }
+ }
+ end
end
trait :deploy_to_production do
environment 'production'
- options environment: { name: 'production',
- url: 'http://prd.example.com/$CI_JOB_NAME' }
+ options do
+ {
+ script: %w(ls),
+ environment: { name: 'production',
+ url: 'http://prd.example.com/$CI_JOB_NAME' }
+ }
+ end
end
trait :start_review_app do
environment 'review/$CI_COMMIT_REF_NAME'
- options environment: { name: 'review/$CI_COMMIT_REF_NAME',
- url: 'http://staging.example.com/$CI_JOB_NAME',
- on_stop: 'stop_review_app' }
+ options do
+ {
+ script: %w(ls),
+ environment: { name: 'review/$CI_COMMIT_REF_NAME',
+ url: 'http://staging.example.com/$CI_JOB_NAME',
+ on_stop: 'stop_review_app' }
+ }
+ end
end
trait :stop_review_app do
name 'stop_review_app'
environment 'review/$CI_COMMIT_REF_NAME'
- options environment: { name: 'review/$CI_COMMIT_REF_NAME',
- url: 'http://staging.example.com/$CI_JOB_NAME',
- action: 'stop' }
+ options do
+ {
+ script: %w(ls),
+ environment: { name: 'review/$CI_COMMIT_REF_NAME',
+ url: 'http://staging.example.com/$CI_JOB_NAME',
+ action: 'stop' }
+ }
+ end
end
trait :allowed_to_fail do
@@ -142,7 +161,13 @@ FactoryBot.define do
trait :schedulable do
self.when 'delayed'
- options start_in: '1 minute'
+
+ options do
+ {
+ script: ['ls -a'],
+ start_in: '1 minute'
+ }
+ end
end
trait :actionable do
@@ -265,6 +290,7 @@ FactoryBot.define do
{
image: { name: 'ruby:2.1', entrypoint: '/bin/sh' },
services: ['postgres', { name: 'docker:stable-dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }],
+ script: %w(echo),
after_script: %w(ls date),
artifacts: {
name: 'artifacts_file',
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index c9f5d0a813e..a2e5f4862db 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -44,6 +44,10 @@ FactoryBot.define do
provider_gcp factory: [:cluster_provider_gcp, :creating]
end
+ trait :rbac_disabled do
+ platform_kubernetes factory: [:cluster_platform_kubernetes, :configured, :rbac_disabled]
+ end
+
trait :disabled do
enabled false
end
@@ -55,5 +59,9 @@ FactoryBot.define do
trait :with_installed_helm do
application_helm factory: %i(clusters_applications_helm installed)
end
+
+ trait :with_domain do
+ domain 'example.com'
+ end
end
end
diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb
index 8169c457ab7..bf30a9c3a61 100644
--- a/spec/factories/clusters/platforms/kubernetes.rb
+++ b/spec/factories/clusters/platforms/kubernetes.rb
@@ -16,8 +16,8 @@ FactoryBot.define do
end
end
- trait :rbac_enabled do
- authorization_type :rbac
+ trait :rbac_disabled do
+ authorization_type :abac
end
end
end
diff --git a/spec/factories/container_repositories.rb b/spec/factories/container_repositories.rb
index 62a89a12ef5..00fad7975c9 100644
--- a/spec/factories/container_repositories.rb
+++ b/spec/factories/container_repositories.rb
@@ -1,6 +1,6 @@
FactoryBot.define do
factory :container_repository do
- name 'test_container_image'
+ name 'test_image'
project
transient do
diff --git a/spec/factories/error_tracking/error.rb b/spec/factories/error_tracking/error.rb
new file mode 100644
index 00000000000..ff883a3d22c
--- /dev/null
+++ b/spec/factories/error_tracking/error.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :error_tracking_error, class: Gitlab::ErrorTracking::Error do
+ id 'id'
+ title 'title'
+ type 'error'
+ user_count 1
+ count 2
+ first_seen { Time.now }
+ last_seen { Time.now }
+ message 'message'
+ culprit 'culprit'
+ external_url 'http://example.com/id'
+ project_id 'project1'
+ project_name 'project name'
+ project_slug 'project_name'
+ short_id 'ID'
+ status 'unresolved'
+ frequency []
+
+ skip_create
+ end
+end
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index 12be63e5d92..077c6ddc5ae 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -8,7 +8,7 @@ FactoryBot.define do
trait(:reporter) { access_level GroupMember::REPORTER }
trait(:developer) { access_level GroupMember::DEVELOPER }
trait(:maintainer) { access_level GroupMember::MAINTAINER }
- trait(:owner) { access_level GroupMember::OWNER }
+ trait(:owner) { access_level GroupMember::OWNER }
trait(:access_request) { requested_at { Time.now } }
trait(:invited) do
diff --git a/spec/factories/project_error_tracking_settings.rb b/spec/factories/project_error_tracking_settings.rb
new file mode 100644
index 00000000000..fbd8dfd395c
--- /dev/null
+++ b/spec/factories/project_error_tracking_settings.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_error_tracking_setting, class: ErrorTracking::ProjectErrorTrackingSetting do
+ project
+ api_url 'https://gitlab.com/api/0/projects/sentry-org/sentry-project'
+ enabled true
+ token 'access_token_123'
+ end
+end
diff --git a/spec/factories/releases/link.rb b/spec/factories/releases/link.rb
new file mode 100644
index 00000000000..d23db6d4bad
--- /dev/null
+++ b/spec/factories/releases/link.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :release_link, class: ::Releases::Link do
+ release
+ sequence(:name) { |n| "release-18.#{n}.dmg" }
+ sequence(:url) { |n| "https://example.com/scrambled-url/app-#{n}.zip" }
+ end
+end
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index 2335b5118dd..ae257d769e8 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -5,7 +5,7 @@ FactoryBot.define do
transient do
attrs do
{
- title: 'Title',
+ title: 'Title.with.dot',
content: 'Content for wiki page',
format: 'markdown'
}
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index d8fcdebfc6d..3ff1a66b0b2 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Admin::AbuseReports", :js do
+describe "Admin::AbuseReports", :js do
let(:user) { create(:user) }
context 'as an admin' do
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index d6ee256f5b5..2b6bfa40beb 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Admin::Projects" do
+describe "Admin::Projects" do
include Select2Helper
let(:user) { create :user }
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 0a69a26eb3e..04f39b807d7 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -238,7 +238,7 @@ describe 'Admin updates settings' do
page.within('.as-ci-cd') do
check 'Default to Auto DevOps pipeline for all projects'
- fill_in 'Auto devops domain', with: 'domain.com'
+ fill_in 'application_setting_auto_devops_domain', with: 'domain.com'
click_button 'Save changes'
end
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index 5fa1a26f1a6..dfa1c92ea49 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Dashboard Issues Feed" do
+describe "Dashboard Issues Feed" do
describe "GET /issues" do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index 86b3f88298f..947587220a9 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Dashboard Feed" do
+describe "Dashboard Feed" do
describe "GET /" do
let!(:user) { create(:user, name: "Jonh") }
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index ee3570a5b2b..714a9885caa 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Issues Feed' do
+describe 'Issues Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 8d7df346abb..7de8bea5049 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "User Feed" do
+describe "User Feed" do
describe "GET /" do
let!(:user) { create(:user) }
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index baa2b1d8af5..ea69ec0319b 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -68,7 +68,7 @@ describe 'Issue Boards', :js do
let(:bug) { create(:label, project: project, name: 'Bug') }
let!(:backlog) { create(:label, project: project, name: 'Backlog') }
let!(:closed) { create(:label, project: project, name: 'Closed') }
- let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') }
+ let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') }
let!(:a_plus) { create(:label, project: project, name: 'A+') }
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
@@ -97,7 +97,7 @@ describe 'Issue Boards', :js do
expect(find('.board:nth-child(4)')).to have_selector('.board-card')
end
- it 'shows description tooltip on list title' do
+ it 'shows description tooltip on list title', :quarantine do
page.within('.board:nth-child(2)') do
expect(find('.board-title span.has-tooltip')[:title]).to eq('Test')
end
@@ -411,7 +411,7 @@ describe 'Issue Boards', :js do
wait_for_empty_boards((2..4))
end
- it 'filters by label with space after reload' do
+ it 'filters by label with space after reload', :quarantine do
set_filter("label", "\"#{accepting.title}")
click_filter_link(accepting.title)
submit_filter
@@ -477,7 +477,7 @@ describe 'Issue Boards', :js do
end
end
- it 'filters by multiple labels' do
+ it 'filters by multiple labels', :quarantine do
set_filter("label", testing.title)
click_filter_link(testing.title)
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index 164442a47f5..d0c4534e317 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -86,6 +86,27 @@ describe 'Issue Boards new issue', :js do
expect(page).to have_selector('.issue-boards-sidebar')
end
+
+ it 'successfuly loads labels to be added to newly created issue' do
+ page.within(first('.board')) do
+ find('.issue-count-badge-add-button').click
+ end
+
+ page.within(first('.board-new-issue-form')) do
+ find('.form-control').set('new issue')
+ click_button 'Submit issue'
+ end
+
+ wait_for_requests
+
+ page.within(first('.issue-boards-sidebar')) do
+ find('.labels .edit-link').click
+
+ wait_for_requests
+
+ expect(page).to have_selector('.labels .dropdown-content li a')
+ end
+ end
end
context 'unauthorized user' do
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 9986206f619..6f9901815e1 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -25,7 +25,7 @@ describe "Container Registry", :js do
context 'when there are image repositories' do
before do
- stub_container_registry_tags(repository: %r{my/image}, tags: %w[latest])
+ stub_container_registry_tags(repository: %r{my/image}, tags: %w[latest], with_manifest: true)
project.container_repositories << container_repository
end
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 32c75cae0a1..f4b2b9033ab 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -11,7 +11,7 @@ describe 'Cycle Analytics', :js do
context 'as an allowed user' do
context 'when project is new' do
- before do
+ before do
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb
index 68bfbf22736..fa12cecc984 100644
--- a/spec/features/dashboard/help_spec.rb
+++ b/spec/features/dashboard/help_spec.rb
@@ -5,13 +5,23 @@ RSpec.describe 'Dashboard Help' do
sign_in(create(:user))
end
- it 'renders correctly markdown' do
- visit help_page_path("administration/raketasks/maintenance")
+ context 'help dropdown' do
+ it 'shows the "What\'s new?" menu item' do
+ visit root_dashboard_path
- expect(page).to have_content('Gather information about GitLab and the system it runs on')
+ expect(page.find('.header-help .dropdown-menu')).to have_text("What's new?")
+ end
+ end
+
+ context 'documentation' do
+ it 'renders correctly markdown' do
+ visit help_page_path("administration/raketasks/maintenance")
+
+ expect(page).to have_content('Gather information about GitLab and the system it runs on')
- node = find('.documentation h2 a#user-content-check-gitlab-configuration')
- expect(node[:href]).to eq '#check-gitlab-configuration'
- expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration'
+ node = find('.documentation h2 a#user-content-check-gitlab-configuration')
+ expect(node[:href]).to eq '#check-gitlab-configuration'
+ expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration'
+ end
end
end
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 282bf542e77..9ffa75aee47 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -6,6 +6,7 @@ describe 'Dashboard Merge Requests' do
include ProjectForksHelper
let(:current_user) { create :user }
+ let(:user) { current_user }
let(:project) { create(:project) }
let(:public_project) { create(:project, :public, :repository) }
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 975b7944741..edca8f9df08 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -91,6 +91,7 @@ describe 'Dashboard Projects' do
visit dashboard_projects_path
expect(page).to have_content(project.name)
+ expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1)
end
it 'shows personal projects on personal projects tab', :js do
@@ -121,6 +122,8 @@ describe 'Dashboard Projects' do
expect(page).not_to have_content(project.name)
expect(page).to have_content(project2.name)
+ expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1)
+ expect(find('.nav-links li:nth-child(2) .badge-pill')).to have_content(1)
end
end
diff --git a/spec/features/dashboard/root_explore_spec.rb b/spec/features/dashboard/root_explore_spec.rb
new file mode 100644
index 00000000000..5b686d8b6f1
--- /dev/null
+++ b/spec/features/dashboard/root_explore_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Root explore' do
+ set(:public_project) { create(:project, :public) }
+ set(:archived_project) { create(:project, :archived) }
+ set(:internal_project) { create(:project, :internal) }
+ set(:private_project) { create(:project, :private) }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ end
+
+ context 'when logged in' do
+ set(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ visit explore_projects_path
+ end
+
+ include_examples 'shows public and internal projects'
+ end
+
+ context 'when not logged in' do
+ before do
+ visit explore_projects_path
+ end
+
+ include_examples 'shows public projects'
+ end
+end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 31a1dcf826d..cbddf117462 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -49,7 +49,7 @@ describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'P'])
find('.nothing-here-block')
- expect(page).to have_content('No projects found')
+ expect(page).to have_content('This user doesn\'t have any personal projects')
end
end
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index 96b22a0f64b..51f158d3045 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -112,7 +112,7 @@ describe 'Dashboard Todos' do
end
it 'shows issue assigned to yourself message' do
- page.within('.js-todos-all') do
+ page.within('.js-todos-all') do
expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself")
end
end
@@ -125,7 +125,7 @@ describe 'Dashboard Todos' do
end
it 'shows you added a todo message' do
- page.within('.js-todos-all') do
+ page.within('.js-todos-all') do
expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
expect(page).not_to have_content('to yourself')
end
@@ -139,7 +139,7 @@ describe 'Dashboard Todos' do
end
it 'shows you mentioned yourself message' do
- page.within('.js-todos-all') do
+ page.within('.js-todos-all') do
expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
expect(page).not_to have_content('to yourself')
end
@@ -153,7 +153,7 @@ describe 'Dashboard Todos' do
end
it 'shows you directly addressed yourself message' do
- page.within('.js-todos-all') do
+ page.within('.js-todos-all') do
expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
expect(page).not_to have_content('to yourself')
end
@@ -169,7 +169,7 @@ describe 'Dashboard Todos' do
end
it 'shows you set yourself as an approver message' do
- page.within('.js-todos-all') do
+ page.within('.js-todos-all') do
expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
expect(page).not_to have_content('to yourself')
end
@@ -332,7 +332,7 @@ describe 'Dashboard Todos' do
it 'links to the pipelines for the merge request' do
href = pipelines_project_merge_request_path(project, todo.target)
- expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href
+ expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href: href
end
end
end
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
index 3746d37b9cd..cc86114e436 100644
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -38,7 +38,7 @@ describe 'Dashboard > User filters projects' do
it 'returns message when starred projects fitler returns no results' do
fill_in 'project-filter-form-field', with: 'Beta\n'
- expect(page).to have_content('No projects found')
+ expect(page).to have_content('This user doesn\'t have any personal projects')
expect(page).not_to have_content('You don\'t have starred projects yet')
end
end
diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb
index 6ac9497b024..c724c3d17f8 100644
--- a/spec/features/explore/user_explores_projects_spec.rb
+++ b/spec/features/explore/user_explores_projects_spec.rb
@@ -6,24 +6,6 @@ describe 'User explores projects' do
set(:private_project) { create(:project, :private) }
set(:public_project) { create(:project, :public) }
- shared_examples_for 'shows public projects' do
- it 'shows projects' do
- expect(page).to have_content(public_project.title)
- expect(page).not_to have_content(internal_project.title)
- expect(page).not_to have_content(private_project.title)
- expect(page).not_to have_content(archived_project.title)
- end
- end
-
- shared_examples_for 'shows public and internal projects' do
- it 'shows projects' do
- expect(page).to have_content(public_project.title)
- expect(page).to have_content(internal_project.title)
- expect(page).not_to have_content(private_project.title)
- expect(page).not_to have_content(archived_project.title)
- end
- end
-
context 'when not signed in' do
context 'when viewing public projects' do
before do
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index 8f5ca781b2c..e4eb0d355d1 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -23,14 +23,52 @@ describe 'Group empty states' do
end
context "the project has #{issuable_name}s" do
- before do
+ it 'does not display an empty state' do
create(issuable, project_relation => project)
visit path
+ expect(page).not_to have_selector('.empty-state')
end
- it 'does not display an empty state' do
- expect(page).not_to have_selector('.empty-state')
+ it "displays link to create new #{issuable} when no open #{issuable} is found" do
+ create("closed_#{issuable}", project_relation => project)
+ issuable_link_fn = "project_#{issuable}s_path"
+
+ visit public_send(issuable_link_fn, project)
+
+ page.within(find('.empty-state')) do
+ expect(page).to have_content(/There are no open #{issuable.to_s.humanize.downcase}/)
+ expect(page).to have_selector("#new_#{issuable}_body_link")
+ end
+ end
+
+ it 'displays link to create new issue when the current search gave no results' do
+ create(issuable, project_relation => project)
+
+ issuable_link_fn = "project_#{issuable}s_path"
+
+ visit public_send(issuable_link_fn, project, author_username: 'foo', scope: 'all', state: 'opened')
+
+ page.within(find('.empty-state')) do
+ expect(page).to have_content(/Sorry, your filter produced no results/)
+ new_issuable_path = issuable == :issue ? 'new_project_issue_path' : 'project_new_merge_request_path'
+
+ path = public_send(new_issuable_path, project)
+
+ expect(page).to have_selector("#new_#{issuable}_body_link[href='#{path}']")
+ end
+ end
+
+ it "displays conditional text when no closed #{issuable} is found" do
+ create(issuable, project_relation => project)
+
+ issuable_link_fn = "project_#{issuable}s_path"
+
+ visit public_send(issuable_link_fn, project, state: 'closed')
+
+ page.within(find('.empty-state')) do
+ expect(page).to have_content(/There are no closed #{issuable.to_s.humanize.downcase}/)
+ end
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index d01fc04311a..f3e573ccbc4 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -154,7 +154,7 @@ describe 'Group' do
end
describe 'group edit', :js do
- let(:group) { create(:group) }
+ let(:group) { create(:group, :public) }
let(:path) { edit_group_path(group) }
let(:new_name) { 'new-name' }
@@ -163,6 +163,8 @@ describe 'Group' do
end
it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="group[name]"]' },
+ { form: '.js-general-settings-form', input: '#group_visibility_level_0' },
+ { form: '.js-general-permissions-form', input: '#group_request_access_enabled' },
{ form: '.js-general-permissions-form', input: 'input[name="group[two_factor_grace_period]"]' }]
it 'saves new settings' do
@@ -233,7 +235,7 @@ describe 'Group' do
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
let!(:project) { create(:project, namespace: group) }
- let!(:path) { group_path(group) }
+ let!(:path) { group_path(group) }
it 'it renders projects and groups on the page' do
visit path
diff --git a/spec/features/ics/dashboard_issues_spec.rb b/spec/features/ics/dashboard_issues_spec.rb
index ea714934ae7..debae0ea930 100644
--- a/spec/features/ics/dashboard_issues_spec.rb
+++ b/spec/features/ics/dashboard_issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Dashboard Issues Calendar Feed' do
+describe 'Dashboard Issues Calendar Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
diff --git a/spec/features/ics/group_issues_spec.rb b/spec/features/ics/group_issues_spec.rb
index 24de5b4b7c6..4177c7f8704 100644
--- a/spec/features/ics/group_issues_spec.rb
+++ b/spec/features/ics/group_issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Group Issues Calendar Feed' do
+describe 'Group Issues Calendar Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb
index 54143595e6b..0d9844be099 100644
--- a/spec/features/ics/project_issues_spec.rb
+++ b/spec/features/ics/project_issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Project Issues Calendar Feed' do
+describe 'Project Issues Calendar Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb
index 9613e22bf24..23385ba65fc 100644
--- a/spec/features/issuables/markdown_references/internal_references_spec.rb
+++ b/spec/features/issuables/markdown_references/internal_references_spec.rb
@@ -64,11 +64,13 @@ describe "Internal references", :js do
it "shows references" do
page.within("#merge-requests .merge-requests-title") do
- expect(page).to have_content("1 Related Merge Request")
+ expect(page).to have_content("Related merge requests")
+ expect(page).to have_css(".mr-count-badge")
end
page.within("#merge-requests ul") do
expect(page).to have_content(private_project_merge_request.title)
+ expect(page).to have_css(".merge-request-status")
end
expect(page).to have_content("mentioned in merge request #{private_project_merge_request.to_reference(public_project)}")
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 3b7a17ef355..c22ad0d20ef 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -279,7 +279,7 @@ describe 'GFM autocomplete', :js do
end
# This context has jsut one example in each contexts in order to improve spec performance.
- context 'labels' do
+ context 'labels', :quarantine do
let!(:backend) { create(:label, project: project, title: 'backend') }
let!(:bug) { create(:label, project: project, title: 'bug') }
let!(:feature_proposal) { create(:label, project: project, title: 'feature proposal') }
diff --git a/spec/features/issues/resource_label_events_spec.rb b/spec/features/issues/resource_label_events_spec.rb
index b0764db7751..3d380c183ec 100644
--- a/spec/features/issues/resource_label_events_spec.rb
+++ b/spec/features/issues/resource_label_events_spec.rb
@@ -6,7 +6,7 @@ describe 'List issue resource label events', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project, author: user) }
- let!(:label) { create(:label, project: project, title: 'foo') }
+ let!(:label) { create(:label, project: project, title: 'foo') }
let!(:user_status) { create(:user_status, user: user) }
context 'when user displays the issue' do
diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
index 32bc851f00f..693ad89069c 100644
--- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
@@ -141,7 +141,7 @@ describe 'User creates branch and merge request on issue page', :js do
it 'disables the create branch button' do
expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hidden)')
expect(page).to have_css('.create-mr-dropdown-wrap .available.hidden', visible: false)
- expect(page).to have_content /1 Related Merge Request/
+ expect(page).to have_content /Related merge requests/
end
end
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 09904cb907f..7c31e67a7fa 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -179,7 +179,7 @@ describe 'Labels Hierarchy', :js, :nested_groups do
end
context 'on project board issue sidebar' do
- let(:board) { create(:board, project: project_1) }
+ let(:board) { create(:board, project: project_1) }
before do
project_1.add_developer(user)
@@ -195,7 +195,7 @@ describe 'Labels Hierarchy', :js, :nested_groups do
end
context 'on group board issue sidebar' do
- let(:board) { create(:board, group: parent) }
+ let(:board) { create(:board, group: parent) }
before do
parent.add_developer(user)
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index 05228e27963..92dee494b7e 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -19,9 +19,9 @@ describe 'Copy as GFM', :js do
visit project_issue_path(@project, @feat.issue)
end
- # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML.
- # The handlers defined in app/assets/javascripts/behaviors/markdown/copy_as_gfm.js consequently convert that same HTML to GFM.
- # To make sure these filters and handlers are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle
+ # The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb transform GitLab Flavored Markdown (GFM) to HTML.
+ # The nodes and marks referenced in app/assets/javascripts/behaviors/markdown/editor_extensions.js consequently transform that same HTML to GFM.
+ # To make sure these filters and nodes/marks are properly aligned, this spec tests the GFM-to-HTML-to-GFM cycle
# by verifying (`html_to_gfm(gfm_to_html(gfm)) == gfm`) for a number of examples of GFM for every filter, using the `verify` helper.
# These are all in a single `it` for performance reasons.
@@ -35,12 +35,15 @@ describe 'Copy as GFM', :js do
verify(
'a real world example from the gitlab-ce README',
- <<-GFM.strip_heredoc
+ <<~GFM
# GitLab
[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
+
[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby)
+
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+
[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42)
## Canonical source
@@ -51,27 +54,31 @@ describe 'Copy as GFM', :js do
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
- - Manage Git repositories with fine grained access controls that keep your code secure
+ * Manage Git repositories with fine grained access controls that keep your code secure
- - Perform code reviews and enhance collaboration with merge requests
+ * Perform code reviews and enhance collaboration with merge requests
- - Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
+ * Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
- - Each project can also have an issue tracker, issue board, and a wiki
+ * Each project can also have an issue tracker, issue board, and a wiki
- - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
+ * Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
- - Completely free and open source (MIT Expat license)
+ * Completely free and open source (MIT Expat license)
GFM
)
aggregate_failures('an accidentally selected empty element') do
gfm = '# Heading1'
- html = <<-HTML.strip_heredoc
+ html = <<~HTML
<h1>Heading1</h1>
<h2></h2>
+
+ <blockquote></blockquote>
+
+ <pre class="code highlight"></pre>
HTML
output_gfm = html_to_gfm(html)
@@ -81,7 +88,7 @@ describe 'Copy as GFM', :js do
aggregate_failures('an accidentally selected other element') do
gfm = 'Test comment with **Markdown!**'
- html = <<-HTML.strip_heredoc
+ html = <<~HTML
<li class="note">
<div class="md">
<p>
@@ -107,10 +114,17 @@ describe 'Copy as GFM', :js do
verify(
'TaskListFilter',
- '- [ ] Unchecked task',
- '- [x] Checked task',
- '1. [ ] Unchecked numbered task',
- '1. [x] Checked numbered task'
+ <<~GFM,
+ * [ ] Unchecked task
+
+ * [x] Checked task
+ GFM
+
+ <<~GFM
+ 1. [ ] Unchecked ordered task
+
+ 1. [x] Checked ordered task
+ GFM
)
verify(
@@ -139,7 +153,16 @@ describe 'Copy as GFM', :js do
verify(
'TableOfContentsFilter',
- '[[_TOC_]]'
+ <<~GFM,
+ [[_TOC_]]
+
+ # Heading 1
+
+ ## Heading 2
+ GFM
+
+ pipeline: :wiki,
+ project_wiki: @project.wiki
)
verify(
@@ -166,7 +189,7 @@ describe 'Copy as GFM', :js do
'$`c = \pm\sqrt{a^2 + b^2}`$',
# math block
- <<-GFM.strip_heredoc
+ <<~GFM
```math
c = \pm\sqrt{a^2 + b^2}
```
@@ -176,7 +199,7 @@ describe 'Copy as GFM', :js do
aggregate_failures('MathFilter: math as transformed from HTML to KaTeX') do
gfm = '$`c = \pm\sqrt{a^2 + b^2}`$'
- html = <<-HTML.strip_heredoc
+ html = <<~HTML
<span class="katex">
<span class="katex-mathml">
<math>
@@ -287,7 +310,7 @@ describe 'Copy as GFM', :js do
verify(
'MermaidFilter: mermaid as converted from GFM to HTML',
- <<-GFM.strip_heredoc
+ <<~GFM
```mermaid
graph TD;
A-->B;
@@ -296,14 +319,14 @@ describe 'Copy as GFM', :js do
)
aggregate_failures('MermaidFilter: mermaid as transformed from HTML to SVG') do
- gfm = <<-GFM.strip_heredoc
+ gfm = <<~GFM
```mermaid
graph TD;
A-->B;
```
GFM
- html = <<-HTML.strip_heredoc
+ html = <<~HTML
<svg id="mermaidChart1" xmlns="http://www.w3.org/2000/svg" height="100%" viewBox="0 0 87.234375 174" style="max-width:87.234375px;" class="mermaid">
<style>
.mermaid {
@@ -371,8 +394,7 @@ describe 'Copy as GFM', :js do
</g>
</g>
<text class="source" display="none">graph TD;
- A--&gt;B;
- </text>
+ A--&gt;B;</text>
</svg>
HTML
@@ -383,11 +405,18 @@ describe 'Copy as GFM', :js do
verify(
'SanitizationFilter',
- <<-GFM.strip_heredoc
+ <<~GFM
<sub>sub</sub>
<dl>
<dt>dt</dt>
+ <dt>dt</dt>
+ <dd>dd</dd>
+ <dd>dd</dd>
+
+ <dt>dt</dt>
+ <dt>dt</dt>
+ <dd>dd</dd>
<dd>dd</dd>
</dl>
@@ -399,30 +428,26 @@ describe 'Copy as GFM', :js do
<var>var</var>
- <ruby>ruby</ruby>
-
- <rt>rt</rt>
-
- <rp>rp</rp>
-
- <abbr>abbr</abbr>
+ <abbr title="HyperText &quot;Markup&quot; Language">HTML</abbr>
- <summary>summary</summary>
+ <details>
+ <summary>summary></summary>
- <details>details</details>
+ details
+ </details>
GFM
)
verify(
'SanitizationFilter',
- <<-GFM.strip_heredoc,
+ <<~GFM,
```
Plain text
```
GFM
- <<-GFM.strip_heredoc,
+ <<~GFM,
```ruby
def foo
bar
@@ -430,11 +455,9 @@ describe 'Copy as GFM', :js do
```
GFM
- <<-GFM.strip_heredoc
+ <<~GFM
Foo
- This is an example of GFM
-
```js
Code goes here
```
@@ -452,9 +475,8 @@ describe 'Copy as GFM', :js do
'> Quote',
# multiline quote
- <<-GFM.strip_heredoc,
- > Multiline
- > Quote
+ <<~GFM,
+ > Multiline Quote
>
> With multiple paragraphs
GFM
@@ -465,48 +487,58 @@ describe 'Copy as GFM', :js do
'[Link](https://example.com)',
- '- List item',
+ <<~GFM,
+ * List item
+
+ * List item 2
+ GFM
# multiline list item
- <<-GFM.strip_heredoc,
- - Multiline
- List item
+ <<~GFM,
+ * Multiline
+
+ List item
GFM
# nested lists
- <<-GFM.strip_heredoc,
- - Nested
+ <<~GFM,
+ * Nested
- - Lists
+ * Lists
GFM
# list with blockquote
- <<-GFM.strip_heredoc,
- - List
+ <<~GFM,
+ * List
- > Blockquote
+ > Blockquote
GFM
- '1. Numbered list item',
+ <<~GFM,
+ 1. Ordered list item
- # multiline numbered list item
- <<-GFM.strip_heredoc,
+ 1. Ordered list item 2
+ GFM
+
+ # multiline ordered list item
+ <<~GFM,
1. Multiline
- Numbered list item
+
+ Ordered list item
GFM
- # nested numbered list
- <<-GFM.strip_heredoc,
+ # nested ordered list
+ <<~GFM,
1. Nested
- 1. Numbered lists
+ 1. Ordered lists
GFM
# list item followed by an HR
- <<-GFM.strip_heredoc,
- - list item
+ <<~GFM,
+ * list item
- -----
+ ---
GFM
'# Heading',
@@ -518,14 +550,14 @@ describe 'Copy as GFM', :js do
'**Bold**',
- '_Italics_',
+ '*Italics*',
'~~Strikethrough~~',
- '-----',
+ '---',
# table
- <<-GFM.strip_heredoc,
+ <<~GFM,
| Centered | Right | Left |
|:--------:|------:|------|
| Foo | Bar | **Baz** |
@@ -533,9 +565,9 @@ describe 'Copy as GFM', :js do
GFM
# table with empty heading
- <<-GFM.strip_heredoc,
+ <<~GFM,
| | x | y |
- |---|---|---|
+ |--|---|---|
| a | 1 | 0 |
| b | 0 | 1 |
GFM
@@ -545,9 +577,11 @@ describe 'Copy as GFM', :js do
alias_method :gfm_to_html, :markdown
def verify(label, *gfms)
+ markdown_options = gfms.extract_options!
+
aggregate_failures(label) do
gfms.each do |gfm|
- html = gfm_to_html(gfm).gsub(/\A&#x000A;|&#x000A;\z/, '')
+ html = gfm_to_html(gfm, markdown_options).gsub(/\A&#x000A;|&#x000A;\z/, '')
output_gfm = html_to_gfm(html)
expect(output_gfm.strip).to eq(gfm.strip)
end
@@ -594,7 +628,7 @@ describe 'Copy as GFM', :js do
verify(
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
- <<-GFM.strip_heredoc,
+ <<~GFM,
```ruby
raise RuntimeError, "System commands must be given as an array of strings"
end
@@ -627,7 +661,7 @@ describe 'Copy as GFM', :js do
verify(
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
- <<-GFM.strip_heredoc,
+ <<~GFM,
```ruby
unless cmd.is_a?(Array)
raise "System commands must be given as an array of strings"
@@ -645,7 +679,7 @@ describe 'Copy as GFM', :js do
verify(
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]',
- <<-GFM.strip_heredoc,
+ <<~GFM,
```ruby
unless cmd.is_a?(Array)
raise RuntimeError, "System commands must be given as an array of strings"
@@ -691,7 +725,7 @@ describe 'Copy as GFM', :js do
verify(
'.line[id="LC9"], .line[id="LC10"]',
- <<-GFM.strip_heredoc,
+ <<~GFM,
```ruby
raise RuntimeError, "System commands must be given as an array of strings"
end
@@ -733,7 +767,7 @@ describe 'Copy as GFM', :js do
verify(
'.line[id="LC27"], .line[id="LC28"]',
- <<-GFM.strip_heredoc,
+ <<~GFM,
```json
"bio": null,
"skype": "",
@@ -752,7 +786,7 @@ describe 'Copy as GFM', :js do
end
def html_for_selector(selector)
- js = <<-JS.strip_heredoc
+ js = <<~JS
(function(selector) {
var els = document.querySelectorAll(selector);
var htmls = [].slice.call(els).map(function(el) { return el.outerHTML; });
@@ -763,7 +797,7 @@ describe 'Copy as GFM', :js do
end
def html_to_gfm(html, transformer = 'transformGFMSelection', target: nil)
- js = <<-JS.strip_heredoc
+ js = <<~JS
(function(html) {
var transformer = window.CopyAsGFM[#{transformer.inspect}];
diff --git a/spec/features/markdown/math_spec.rb b/spec/features/markdown/math_spec.rb
index 6a23d6b78ab..678ce80b382 100644
--- a/spec/features/markdown/math_spec.rb
+++ b/spec/features/markdown/math_spec.rb
@@ -16,7 +16,7 @@ describe 'Math rendering', :js do
visit project_issue_path(project, issue)
- expect(page).to have_selector('.katex .mord.mathit', text: 'b')
- expect(page).to have_selector('.katex-display .mord.mathit', text: 'b')
+ expect(page).to have_selector('.katex .mord.mathdefault', text: 'b')
+ expect(page).to have_selector('.katex-display .mord.mathdefault', text: 'b')
end
end
diff --git a/spec/features/merge_request/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb
index 01aeed93947..00ac7c72a11 100644
--- a/spec/features/merge_request/user_accepts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb
@@ -25,7 +25,7 @@ describe 'User accepts a merge request', :js do
end
it 'accepts a merge request' do
- check('Remove source branch')
+ check('Delete source branch')
click_button('Merge')
expect(page).to have_content('The changes were merged into')
@@ -60,7 +60,7 @@ describe 'User accepts a merge request', :js do
end
it 'accepts a merge request' do
- check('Remove source branch')
+ check('Delete source branch')
click_button('Merge')
expect(page).to have_content('The changes were merged into')
diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index 00cf368e8c9..eb4b2cf5bd0 100644
--- a/spec/features/merge_request/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
@@ -91,6 +91,7 @@ describe 'User comments on a diff', :js do
# Check the same comments in the side-by-side view.
execute_script("window.scrollTo(0,0);")
+ find('.js-show-diff-settings').click
click_button 'Side-by-side'
wait_for_requests
diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb
index 9d2a94a4a41..c169a68cd1c 100644
--- a/spec/features/merge_request/user_creates_mr_spec.rb
+++ b/spec/features/merge_request/user_creates_mr_spec.rb
@@ -30,7 +30,7 @@ describe 'Merge request > User creates MR' do
end
context 'source project', :js do
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
let(:target_project) { create(:project, :public, :repository) }
let(:source_project) { target_project }
diff --git a/spec/features/merge_request/user_merges_immediately_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb
index ea61f9675bc..84636ae355c 100644
--- a/spec/features/merge_request/user_merges_immediately_spec.rb
+++ b/spec/features/merge_request/user_merges_immediately_spec.rb
@@ -25,6 +25,8 @@ describe 'Merge requests > User merges immediately', :js do
end
it 'enables merge immediately' do
+ wait_for_requests
+
page.within '.mr-widget-body' do
find('.dropdown-toggle').click
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 29b3d2b629b..6e54aa6006b 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
@@ -33,7 +33,7 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
click_button "Merge when pipeline succeeds"
expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds"
- expect(page).to have_content "The source branch will not be removed"
+ expect(page).to have_content "The source branch will not be deleted"
expect(page).to have_selector ".js-cancel-auto-merge"
visit project_merge_request_path(project, merge_request) # Needed to refresh the page
expect(page).to have_content /enabled an automatic merge when the pipeline for \h{8} succeeds/i
@@ -94,7 +94,7 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
click_link 'Merge when pipeline succeeds'
expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds"
- expect(page).to have_content "The source branch will not be removed"
+ expect(page).to have_content "The source branch will not be deleted"
expect(page).to have_link "Cancel automatic merge"
end
end
@@ -127,10 +127,10 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
expect(page).to have_content "canceled the automatic merge"
end
- it 'allows to remove source branch' do
- click_link "Remove source branch"
+ it 'allows to delete source branch' do
+ click_link "Delete source branch"
- expect(page).to have_content "The source branch will be removed"
+ expect(page).to have_content "The source branch will be deleted"
end
context 'when pipeline succeeds' do
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index ba4806821f9..08fa4a98feb 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -126,6 +126,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
describe 'side-by-side view' do
before do
page.within('.merge-request-tabs') { click_link 'Changes' }
+ find('.js-show-diff-settings').click
page.find('#parallel-diff-btn').click
end
diff --git a/spec/features/merge_request/user_sees_discussions_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb
index 4ab9a87ad4b..57be1d06708 100644
--- a/spec/features/merge_request/user_sees_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_discussions_spec.rb
@@ -88,5 +88,17 @@ describe 'Merge request > User sees discussions', :js do
expect(page).to have_content "started a discussion on commit #{note.commit_id[0...7]}"
end
end
+
+ context 'a commit non-diff discussion' do
+ let(:note) { create(:discussion_note_on_commit, project: project) }
+
+ it 'displays correct header' do
+ page.within(find("#note_#{note.id}", match: :first)) do
+ refresh # Trigger a refresh of notes.
+ wait_for_requests
+ expect(page).to have_content "commented on commit #{note.commit_id[0...7]}"
+ end
+ end
+ 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 d8ebd3c92af..afb978d7c45 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -316,7 +316,7 @@ describe 'Merge request > User sees merge widget', :js do
it 'user cannot remove source branch' do
expect(page).not_to have_field('remove-source-branch-input')
- expect(page).to have_content('Removes source branch')
+ expect(page).to have_content('Deletes source branch')
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 0272d300e06..5188dc3625f 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
@@ -5,7 +5,7 @@ describe 'Merge request < User sees mini pipeline graph', :js do
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project, head_pipeline: pipeline) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running', sha: project.commit.id) }
- let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') }
+ let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test') }
before do
build.run
@@ -51,22 +51,52 @@ describe 'Merge request < User sees mini pipeline graph', :js do
first('.mini-pipeline-graph-dropdown-toggle')
end
- it 'expands when hovered' do
+ # Status icon button styles should update as described in
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/42769
+ it 'has unique styles for default, :hover, :active, and :focus states' do
find('.mini-pipeline-graph-dropdown-toggle')
- before_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();")
+ default_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');")
+ default_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');")
+ default_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');")
toggle.hover
find('.mini-pipeline-graph-dropdown-toggle')
- after_width = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').outerWidth();")
+ hover_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');")
+ hover_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');")
+ hover_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');")
- expect(before_width).to be < after_width
- end
+ page.driver.browser.action.click_and_hold(toggle.native).perform
- it 'shows dropdown caret when hovered' do
- toggle.hover
+ find('.mini-pipeline-graph-dropdown-toggle')
+ active_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');")
+ active_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');")
+ active_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');")
+
+ page.driver.browser.action.release(toggle.native)
+ .move_by(100, 100)
+ .perform
+
+ find('.mini-pipeline-graph-dropdown-toggle')
+ focus_background_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('background-color');")
+ focus_foreground_color = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible svg').css('fill');")
+ focus_box_shadow = evaluate_script("$('.mini-pipeline-graph-dropdown-toggle:visible').css('box-shadow');")
+
+ expect(default_background_color).not_to eq(hover_background_color)
+ expect(hover_background_color).not_to eq(active_background_color)
+ expect(default_background_color).not_to eq(active_background_color)
+
+ expect(default_foreground_color).not_to eq(hover_foreground_color)
+ expect(hover_foreground_color).not_to eq(active_foreground_color)
+ expect(default_foreground_color).not_to eq(active_foreground_color)
+
+ expect(focus_background_color).to eq(hover_background_color)
+ expect(focus_foreground_color).to eq(hover_foreground_color)
- expect(toggle).to have_selector('.fa-caret-down')
+ expect(default_box_shadow).to eq('none')
+ expect(hover_box_shadow).to eq('none')
+ expect(active_box_shadow).not_to eq('none')
+ expect(focus_box_shadow).not_to eq('none')
end
it 'shows tooltip when hovered' do
diff --git a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
index dd860382daa..0decdfe3a14 100644
--- a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
+++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
@@ -9,17 +9,23 @@ describe 'Merge request > User toggles whitespace changes', :js do
project.add_maintainer(user)
sign_in(user)
visit diffs_project_merge_request_path(project, merge_request)
+
+ find('.js-show-diff-settings').click
end
it 'has a button to toggle whitespace changes' do
- expect(page).to have_content 'Hide whitespace changes'
+ expect(page).to have_content 'Show whitespace changes'
end
describe 'clicking "Hide whitespace changes" button' do
it 'toggles the "Hide whitespace changes" button' do
- click_link 'Hide whitespace changes'
+ find('#show-whitespace').click
+
+ visit diffs_project_merge_request_path(project, merge_request)
+
+ find('.js-show-diff-settings').click
- expect(page).to have_content 'Show whitespace changes'
+ expect(find('#show-whitespace')).to be_checked
end
end
end
diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb
index 7f95a1282f9..0434db04113 100644
--- a/spec/features/merge_request/user_views_diffs_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_spec.rb
@@ -23,6 +23,8 @@ describe 'User views diffs', :js do
end
it 'shows diffs' do
+ find('.js-show-diff-settings').click
+
expect(page).to have_css('.tab-content #diffs.active')
expect(page).to have_css('#parallel-diff-btn', count: 1)
expect(page).to have_css('#inline-diff-btn', count: 1)
@@ -38,6 +40,8 @@ describe 'User views diffs', :js do
context 'when in the side-by-side view' do
before do
+ find('.js-show-diff-settings').click
+
click_button 'Side-by-side'
wait_for_requests
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index cb6603d3f50..e535c7e5811 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -68,7 +68,7 @@ describe 'Merge requests > User mass updates', :js do
end
context 'milestone' do
- let(:milestone) { create(:milestone, project: project) }
+ let(:milestone) { create(:milestone, project: project) }
describe 'set milestone' do
before do
diff --git a/spec/features/merge_requests/user_squashes_merge_request_spec.rb b/spec/features/merge_requests/user_squashes_merge_request_spec.rb
index ec1153b7f7f..47f9f10815c 100644
--- a/spec/features/merge_requests/user_squashes_merge_request_spec.rb
+++ b/spec/features/merge_requests/user_squashes_merge_request_spec.rb
@@ -38,7 +38,7 @@ describe 'User squashes a merge request', :js do
def accept_mr
expect(page).to have_button('Merge')
- uncheck 'Remove source branch'
+ uncheck 'Delete source branch'
click_on 'Merge'
end
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index a0673b12738..6e349395017 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
describe 'Milestone' do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) }
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
before do
create(:group_member, group: group, user: user)
diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
index 9ebbbaea911..5f630c9ffa4 100644
--- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
@@ -25,8 +25,8 @@ describe "User browses artifacts" do
page.within(".tree-table") do
expect(page).to have_no_content("..")
.and have_content("other_artifacts_0.1.2")
- .and have_content("ci_artifacts.txt")
- .and have_content("rails_sample.jpg")
+ .and have_content("ci_artifacts.txt 27 Bytes")
+ .and have_content("rails_sample.jpg 34.4 KB")
end
page.within(".build-header") do
diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb
index 8918a7b7b9c..fab9e035d53 100644
--- a/spec/features/projects/clusters/applications_spec.rb
+++ b/spec/features/projects/clusters/applications_spec.rb
@@ -70,6 +70,34 @@ describe 'Clusters Applications', :js do
end
end
+ context 'when user installs Knative' do
+ before do
+ create(:clusters_applications_helm, :installed, cluster: cluster)
+ end
+
+ context 'on an abac cluster' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled, projects: [project])}
+
+ it 'should show info block and not be installable' do
+ page.within('.js-cluster-application-row-knative') do
+ expect(page).to have_css('.bs-callout-info')
+ expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
+ end
+ end
+ end
+
+ context 'on an rbac cluster' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project])}
+
+ it 'should not show callout block and be installable' do
+ page.within('.js-cluster-application-row-knative') do
+ expect(page).not_to have_css('.bs-callout-info')
+ expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
+ end
+ end
+ end
+ end
+
context 'when user installs Cert Manager' do
before do
allow(ClusterInstallAppWorker).to receive(:perform_async)
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 06e30571336..9322e29d744 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -33,32 +33,6 @@ describe 'Gcp Cluster', :js do
context 'when user filled form with valid parameters' do
subject { click_button 'Create Kubernetes cluster' }
- shared_examples 'valid cluster gcp form' do
- it 'users sees a form with the GCP token' do
- expect(page).to have_selector(:css, 'form[data-token="token"]')
- end
-
- it 'user sees a cluster details page and creation status' do
- subject
-
- expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
-
- Clusters::Cluster.last.provider.make_created!
-
- expect(page).to have_content('Kubernetes cluster was successfully created on Google Kubernetes Engine')
- end
-
- it 'user sees a error if something wrong during creation' do
- subject
-
- expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
-
- Clusters::Cluster.last.provider.make_errored!('Something wrong!')
-
- expect(page).to have_content('Something wrong!')
- end
- end
-
before do
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
.to receive(:projects_zones_clusters_create) do
@@ -82,14 +56,32 @@ describe 'Gcp Cluster', :js do
fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
end
- it_behaves_like 'valid cluster gcp form'
+ it 'users sees a form with the GCP token' do
+ expect(page).to have_selector(:css, 'form[data-token="token"]')
+ end
- context 'RBAC is enabled for the cluster' do
- before do
- check 'cluster_provider_gcp_attributes_legacy_abac'
- end
+ it 'user sees a cluster details page and creation status' do
+ subject
+
+ expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
+
+ Clusters::Cluster.last.provider.make_created!
+
+ expect(page).to have_content('Kubernetes cluster was successfully created on Google Kubernetes Engine')
+ end
+
+ it 'user sees a error if something wrong during creation' do
+ subject
+
+ expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
+
+ Clusters::Cluster.last.provider.make_errored!('Something wrong!')
+
+ expect(page).to have_content('Something wrong!')
+ end
- it_behaves_like 'valid cluster gcp form'
+ it 'user sees RBAC is enabled by default' do
+ expect(page).to have_checked_field('RBAC-enabled cluster')
end
end
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index 250c964cc32..1f2f7592d8b 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -23,19 +23,6 @@ describe 'User Cluster', :js do
end
context 'when user filled form with valid parameters' do
- shared_examples 'valid cluster user form' do
- it 'user sees a cluster details page' do
- subject
-
- expect(page).to have_content('Kubernetes cluster integration')
- expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
- expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
- .to have_content('http://example.com')
- expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
- .to have_content('my-token')
- end
- end
-
before do
fill_in 'cluster_name', with: 'dev-cluster'
fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com'
@@ -44,20 +31,19 @@ describe 'User Cluster', :js do
subject { click_button 'Add Kubernetes cluster' }
- it_behaves_like 'valid cluster user form'
-
- context 'RBAC is enabled for the cluster' do
- before do
- check 'cluster_platform_kubernetes_attributes_authorization_type'
- end
-
- it_behaves_like 'valid cluster user form'
+ it 'user sees a cluster details page' do
+ subject
- it 'user sees a cluster details page with RBAC enabled' do
- subject
+ expect(page).to have_content('Kubernetes cluster integration')
+ expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
+ expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
+ .to have_content('http://example.com')
+ expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
+ .to have_content('my-token')
+ end
- expect(page.find_field('cluster[platform_kubernetes_attributes][authorization_type]', disabled: true)).to be_checked
- end
+ it 'user sees RBAC is enabled by default' do
+ expect(page).to have_checked_field('RBAC-enabled cluster')
end
end
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index caf69796d52..d72476f36a9 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -20,7 +20,7 @@ describe 'project commit pipelines', :js do
visit pipelines_project_commit_path(project, project.commit.sha)
page.within('.table-holder') do
- expect(page).to have_content project.ci_pipelines[0].id # pipeline ids
+ expect(page).to have_content project.ci_pipelines[0].id # pipeline ids
end
end
end
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index a61b614dbc8..acfb582dba9 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -4,8 +4,8 @@ describe 'Cherry-pick Commits' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) }
- let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
- let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') }
+ let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
+ let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') }
before do
sign_in(user)
diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb
index 69600884909..5f7cf68987e 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -87,6 +87,21 @@ describe "Compare", :js do
expect(find(".js-compare-from-dropdown .dropdown-content")).to have_selector("li", count: 3)
end
+
+ context 'when commit has overflow', :js do
+ it 'displays warning' do
+ visit project_compare_index_path(project, from: "feature", to: "master")
+
+ allow(Commit).to receive(:max_diff_options).and_return(max_files: 3)
+ allow_any_instance_of(DiffHelper).to receive(:render_overflow_warning?).and_return(true)
+
+ click_button('Compare')
+
+ page.within('.alert') do
+ expect(page).to have_text("Too many changes to show. To preserve performance only 3 of 3+ files are displayed.")
+ end
+ end
+ end
end
describe "tags" do
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 89954d35f91..0c517d5f490 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -272,8 +272,7 @@ describe 'Environments page', :js do
create(:ci_build, :scheduled,
pipeline: pipeline,
name: 'delayed job',
- stage: 'test',
- commands: 'test')
+ stage: 'test')
end
let!(:deployment) do
@@ -304,8 +303,7 @@ describe 'Environments page', :js do
create(:ci_build, :expired_scheduled,
pipeline: pipeline,
name: 'delayed job',
- stage: 'test',
- commands: 'test')
+ stage: 'test')
end
it "shows 00:00:00 as the remaining time" do
diff --git a/spec/features/projects/files/user_browses_lfs_files_spec.rb b/spec/features/projects/files/user_browses_lfs_files_spec.rb
index c559a301ca1..d56476adb05 100644
--- a/spec/features/projects/files/user_browses_lfs_files_spec.rb
+++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb
@@ -12,6 +12,7 @@ describe 'Projects > Files > User browses LFS files' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
visit project_tree_path(project, 'lfs')
+ wait_for_requests
end
it 'is possible to see raw content of LFS pointer' do
@@ -26,10 +27,11 @@ describe 'Projects > Files > User browses LFS files' do
end
end
- context 'when LFS is enabled' do
+ context 'when LFS is enabled', :js do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
visit project_tree_path(project, 'lfs')
+ wait_for_requests
end
it 'shows an LFS object' do
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 60f37f4b74a..8230396a4cc 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -28,7 +28,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
it "shows Pending tab jobs" do
- expect(page).to have_link 'Cancel running'
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page).to have_content job.short_sha
expect(page).to have_content job.ref
@@ -44,7 +43,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it "shows Running tab jobs" do
expect(page).to have_selector('.nav-links li.active', text: 'Running')
- expect(page).to have_link 'Cancel running'
expect(page).to have_content job.short_sha
expect(page).to have_content job.ref
expect(page).to have_content job.name
@@ -60,7 +58,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it "shows Finished tab jobs" do
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No jobs to show'
- expect(page).to have_link 'Cancel running'
end
end
@@ -75,7 +72,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
expect(page).to have_content job.short_sha
expect(page).to have_content job.ref
expect(page).to have_content job.name
- expect(page).not_to have_link 'Cancel running'
end
end
@@ -94,23 +90,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
- describe "POST /:project/jobs/:id/cancel_all" do
- before do
- job.run!
- visit project_jobs_path(project)
- click_link "Cancel running"
- end
-
- it 'shows all necessary content' do
- expect(page).to have_selector('.nav-links li.active', text: 'All')
- expect(page).to have_content 'canceled'
- expect(page).to have_content job.short_sha
- expect(page).to have_content job.ref
- expect(page).to have_content job.name
- expect(page).not_to have_link 'Cancel running'
- end
- end
-
describe "GET /:project/jobs/:id" do
context "Job from project" do
let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline) }
@@ -191,7 +170,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
href = new_project_issue_path(project, options)
- page.within('.header-action-buttons') do
+ page.within('.build-sidebar') do
expect(find('.js-new-issue')['href']).to include(href)
end
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 055a0c83a11..d36f043f880 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -125,7 +125,7 @@ describe 'Prioritize labels' do
wait_for_requests
end
- page.within('.breadcrumbs-container') do
+ page.within('.top-area') do
expect(page).to have_link('New label')
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index a37ad9c3f43..72ef460d315 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -18,7 +18,7 @@ describe 'Pipeline', :js do
let!(:build_failed) do
create(:ci_build, :failed,
- pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
+ pipeline: pipeline, stage: 'test', name: 'test')
end
let!(:build_running) do
@@ -286,6 +286,49 @@ describe 'Pipeline', :js do
end
end
+ context 'when a bridge job exists' do
+ include_context 'pipeline builds'
+
+ let(:project) { create(:project, :repository) }
+ let(:downstream) { create(:project, :repository) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ let!(:bridge) do
+ create(:ci_bridge, pipeline: pipeline,
+ name: 'cross-build',
+ user: user,
+ downstream: downstream)
+ end
+
+ describe 'GET /:project/pipelines/:id' do
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'shows the pipeline with a bridge job' do
+ expect(page).to have_selector('.pipeline-visualization')
+ expect(page).to have_content('cross-build')
+ end
+ end
+
+ describe 'GET /:project/pipelines/:id/builds' do
+ before do
+ visit builds_project_pipeline_path(project, pipeline)
+ end
+
+ it 'shows a bridge job on a list' do
+ expect(page).to have_content('cross-build')
+ expect(page).to have_content(bridge.id)
+ end
+ end
+ end
+
describe 'GET /:project/pipelines/:id/builds' do
include_context 'pipeline builds'
@@ -477,10 +520,11 @@ describe 'Pipeline', :js do
end
context 'when accessing failed jobs page' do
- it 'fails to access the page' do
- subject
+ it 'renders a 404 page' do
+ requests = inspect_requests { subject }
- expect(page).to have_title('Access Denied')
+ expect(page).to have_title('Not Found')
+ expect(requests.first.status_code).to eq(404)
end
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 17772a35779..ffa165c5440 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -109,8 +109,7 @@ describe 'Pipelines', :js do
context 'when pipeline is cancelable' do
let!(:build) do
create(:ci_build, pipeline: pipeline,
- stage: 'test',
- commands: 'test')
+ stage: 'test')
end
before do
@@ -140,8 +139,7 @@ describe 'Pipelines', :js do
context 'when pipeline is retryable' do
let!(:build) do
create(:ci_build, pipeline: pipeline,
- stage: 'test',
- commands: 'test')
+ stage: 'test')
end
before do
@@ -202,8 +200,7 @@ describe 'Pipelines', :js do
create(:ci_build, :manual,
pipeline: pipeline,
name: 'manual build',
- stage: 'test',
- commands: 'test')
+ stage: 'test')
end
before do
@@ -237,8 +234,7 @@ describe 'Pipelines', :js do
create(:ci_build, :scheduled,
pipeline: pipeline,
name: 'delayed job',
- stage: 'test',
- commands: 'test')
+ stage: 'test')
end
before do
@@ -262,8 +258,7 @@ describe 'Pipelines', :js do
create(:ci_build, :expired_scheduled,
pipeline: pipeline,
name: 'delayed job',
- stage: 'test',
- commands: 'test')
+ stage: 'test')
end
it "shows 00:00:00 as the remaining time" do
@@ -470,7 +465,7 @@ describe 'Pipelines', :js do
context 'with pagination' do
before do
allow(Ci::Pipeline).to receive(:default_per_page).and_return(1)
- create(:ci_empty_pipeline, project: project)
+ create(:ci_empty_pipeline, project: project)
end
it 'should render pagination' do
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
new file mode 100644
index 00000000000..317ffb6a2ff
--- /dev/null
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe 'User views releases', :js do
+ let!(:project) { create(:project, :repository) }
+ let!(:release) { create(:release, project: project ) }
+ let!(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+
+ gitlab_sign_in(user)
+ end
+
+ it 'sees the release' do
+ visit project_releases_path(project)
+
+ expect(page).to have_content(release.name)
+ expect(page).to have_content(release.tag)
+ end
+
+ context 'when there is a link as an asset' do
+ let!(:release_link) { create(:release_link, release: release, url: url ) }
+ let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
+
+ it 'sees the link' do
+ visit project_releases_path(project)
+
+ page.within('.js-assets-list') do
+ expect(page).to have_link release_link.name, href: release_link.url
+ expect(page).not_to have_content('(external source)')
+ end
+ end
+
+ context 'when url points to external resource' do
+ let(:url) { 'http://google.com/download' }
+
+ it 'sees that the link is external resource' do
+ visit project_releases_path(project)
+
+ page.within('.js-assets-list') do
+ expect(page).to have_content('(external source)')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/settings/operations_settings_spec.rb b/spec/features/projects/settings/operations_settings_spec.rb
new file mode 100644
index 00000000000..06290c67c70
--- /dev/null
+++ b/spec/features/projects/settings/operations_settings_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Projects > Settings > For a forked project', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:role) { :maintainer }
+
+ before do
+ sign_in(user)
+ project.add_role(user, role)
+ end
+
+ describe 'Sidebar > Operations' do
+ it 'renders the settings link in the sidebar' do
+ visit project_path(project)
+ wait_for_requests
+
+ expect(page).to have_selector('a[title="Operations"]', visible: false)
+ end
+ end
+end
diff --git a/spec/features/projects/settings/user_tags_project_spec.rb b/spec/features/projects/settings/user_tags_project_spec.rb
index 9357215ae6f..e3f06c042b9 100644
--- a/spec/features/projects/settings/user_tags_project_spec.rb
+++ b/spec/features/projects/settings/user_tags_project_spec.rb
@@ -9,13 +9,13 @@ describe 'Projects > Settings > User tags a project' do
visit edit_project_path(project)
end
- it 'sets project tags' do
- fill_in 'Tags', with: 'tag1, tag2'
+ it 'sets project topics' do
+ fill_in 'Topics', with: 'topic1, topic2'
page.within '.general-settings' do
click_button 'Save changes'
end
- expect(find_field('Tags').value).to eq 'tag1, tag2'
+ expect(find_field('Topics').value).to eq 'topic1, topic2'
end
end
diff --git a/spec/features/projects/tags/user_edits_tags_spec.rb b/spec/features/projects/tags/user_edits_tags_spec.rb
new file mode 100644
index 00000000000..ebb2844d17f
--- /dev/null
+++ b/spec/features/projects/tags/user_edits_tags_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Project > Tags', :js do
+ include DropzoneHelper
+
+ let(:user) { create(:user) }
+ let(:role) { :developer }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ sign_in(user)
+ project.add_role(user, role)
+ end
+
+ describe 'when opening project tags' do
+ before do
+ visit project_tags_path(project)
+ end
+
+ context 'page with tags list' do
+ it 'shows tag name' do
+ page.within first('.tags > .content-list > li') do
+ expect(page.find('.row-main-content')).to have_content 'v1.1.0 Version 1.1.0'
+ end
+ end
+
+ it 'shows tag edit button' do
+ page.within first('.tags > .content-list > li') do
+ edit_btn = page.find('.row-fixed-content.controls a.btn-edit')
+
+ expect(edit_btn['href']).to have_content '/tags/v1.1.0/release/edit'
+ end
+ end
+ end
+
+ context 'edit tag release notes' do
+ before do
+ find('.tags > .content-list > li:first-child .row-fixed-content.controls a.btn-edit').click
+ end
+
+ it 'shows tag name header' do
+ page.within('.content') do
+ expect(page.find('.sub-header-block')).to have_content 'Release notes for tag v1.1.0'
+ end
+ end
+
+ it 'shows release notes form' do
+ page.within('.content') do
+ expect(page).to have_selector('form.release-form')
+ end
+ end
+
+ it 'toolbar buttons on release notes form are functional' do
+ page.within('.content form.release-form') do
+ note_textarea = page.find('.js-gfm-input')
+
+ # Click on Bold button
+ page.find('.md-header-toolbar button.toolbar-btn:first-child').click
+
+ expect(note_textarea.value).to eq('****')
+ end
+ end
+
+ it 'release notes form shows "Attach a file" button', :js do
+ page.within('.content form.release-form') do
+ expect(page).to have_button('Attach a file')
+ expect(page).not_to have_selector('.uploading-progress-container', visible: true)
+ end
+ end
+
+ it 'shows "Attaching a file" message on uploading 1 file', :js do
+ slow_requests do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
+
+ expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching a file -')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index b56bb272b46..4cb49ab02e2 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -5,7 +5,7 @@ describe 'Project' do
include MobileHelpers
describe 'creating from template' do
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
let(:template) { Gitlab::ProjectTemplate.find(:rails) }
before do
@@ -99,6 +99,30 @@ describe 'Project' do
end
end
+ describe 'project topics' do
+ let(:project) { create(:project, :repository) }
+ let(:path) { project_path(project) }
+
+ before do
+ sign_in(create(:admin))
+ visit path
+ end
+
+ it 'shows project topics' do
+ project.update_attribute(:tag_list, 'topic1')
+ visit path
+ expect(page).to have_css('.project-topic-list')
+ expect(page).to have_content('topic1')
+ end
+
+ it 'shows up to 3 project tags' do
+ project.update_attribute(:tag_list, 'topic1, topic2, topic3, topic4')
+ visit path
+ expect(page).to have_css('.project-topic-list')
+ expect(page).to have_content('topic1, topic2, topic3 + 1 more')
+ end
+ end
+
describe 'copy clone URL to clipboard', :js do
let(:project) { create(:project, :repository) }
let(:path) { project_path(project) }
@@ -146,7 +170,7 @@ describe 'Project' do
describe 'showing information about source of a project fork' do
let(:user) { create(:user) }
- let(:base_project) { create(:project, :public, :repository) }
+ let(:base_project) { create(:project, :public, :repository) }
let(:forked_project) { fork_project(base_project, user, repository: true) }
before do
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 63c38a25f4b..0aff916ec83 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -97,7 +97,7 @@ describe 'Protected Branches', :js do
set_protected_branch_name('some-branch')
click_on "Protect"
- within(".protected-branches-list") { expect(page).to have_content('branch was removed') }
+ within(".protected-branches-list") { expect(page).to have_content('branch was deleted') }
end
end
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 09de983f669..6eae0be4b9f 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -236,7 +236,7 @@ describe 'Runners' do
it 'group runners are available' do
visit project_runners_path(project)
- expect(page).to have_content 'Available group Runners : 1'
+ expect(page).to have_content 'Available group Runners: 1'
expect(page).to have_content 'group-runner'
end
@@ -279,7 +279,7 @@ describe 'Runners' do
visit group_settings_ci_cd_path(group)
expect(page).not_to have_content 'This group does not provide any group Runners yet'
- expect(page).to have_content 'Available group Runners : 1'
+ expect(page).to have_content 'Available group Runners: 1'
expect(page).to have_content 'group-runner'
end
diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb
index 3ca1303bda6..ff679034a36 100644
--- a/spec/features/security/admin_access_spec.rb
+++ b/spec/features/security/admin_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Admin::Projects" do
+describe "Admin::Projects" do
include AccessMatchers
describe "GET /admin/projects" do
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
index 0c893e65d9c..07cddc92ac4 100644
--- a/spec/features/security/dashboard_access_spec.rb
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Dashboard access" do
+describe "Dashboard access" do
include AccessMatchers
describe "GET /dashboard" do
diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index 41eb7b26578..a198e65046f 100644
--- a/spec/features/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Profile access" do
+describe "Profile access" do
include AccessMatchers
describe "GET /profile/keys" do
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 001e6c10eb2..843dbcd5b4d 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Internal Project Access" do
+describe "Internal Project Access" do
include AccessMatchers
set(:project) { create(:project, :internal, :repository) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index c6618355eea..cf0837c1e67 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Private Project Access" do
+describe "Private Project Access" do
include AccessMatchers
set(:project) { create(:project, :private, :repository, public_builds: false) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 3717dc13f1e..7e1b735fd3d 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Public Project Access" do
+describe "Public Project Access" do
include AccessMatchers
set(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
index b87eb86b88b..0c58fdf2f12 100644
--- a/spec/features/security/project/snippet/internal_access_spec.rb
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Internal Project Snippets Access" do
+describe "Internal Project Snippets Access" do
include AccessMatchers
let(:project) { create(:project, :internal) }
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
index ead91d9a5fa..420f1938763 100644
--- a/spec/features/security/project/snippet/private_access_spec.rb
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-describe "Private Project Snippets Access" do
+describe "Private Project Snippets Access" do
include AccessMatchers
let(:project) { create(:project, :private) }
- let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
+ let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
subject { project_snippets_path(project) }
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index 9bab3a474b8..6c75902c6e9 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Public Project Snippets Access" do
+describe "Public Project Snippets Access" do
include AccessMatchers
let(:project) { create(:project, :public) }
diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb
index 8748230fa0c..3708f0ee477 100644
--- a/spec/features/users/overview_spec.rb
+++ b/spec/features/users/overview_spec.rb
@@ -96,7 +96,7 @@ describe 'Overview tab on a user profile', :js do
it 'it shows an empty project list with an info message' do
page.within('.projects-block') do
expect(page).to have_selector('.loading', visible: false)
- expect(page).to have_content('No projects found')
+ expect(page).to have_content('This user doesn\'t have any personal projects')
expect(page).not_to have_selector('.project-row')
end
end
diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb
index 5b2e7605c4d..84df1016594 100644
--- a/spec/features/users/terms_spec.rb
+++ b/spec/features/users/terms_spec.rb
@@ -76,7 +76,7 @@ describe 'Users > Terms' do
project.add_developer(user)
end
- it 'redirects to terms and back to where the user was going' do
+ it 'redirects to terms and back to where the user was going' do
visit project_path(project)
enforce_terms
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
index 16c0d418d98..367ca43bdfe 100644
--- a/spec/finders/groups_finder_spec.rb
+++ b/spec/finders/groups_finder_spec.rb
@@ -8,22 +8,22 @@ describe GroupsFinder do
using RSpec::Parameterized::TableSyntax
where(:user_type, :params, :results) do
- nil | { all_available: true } | %i(public_group user_public_group)
- nil | { all_available: false } | %i(public_group user_public_group)
+ nil | { all_available: true } | %i(public_group user_public_group)
+ nil | { all_available: false } | %i(public_group user_public_group)
nil | {} | %i(public_group user_public_group)
- :regular | { all_available: true } | %i(public_group internal_group user_public_group user_internal_group
- user_private_group)
- :regular | { all_available: false } | %i(user_public_group user_internal_group user_private_group)
+ :regular | { all_available: true } | %i(public_group internal_group user_public_group user_internal_group
+ user_private_group)
+ :regular | { all_available: false } | %i(user_public_group user_internal_group user_private_group)
:regular | {} | %i(public_group internal_group user_public_group user_internal_group user_private_group)
- :external | { all_available: true } | %i(public_group user_public_group user_internal_group user_private_group)
- :external | { all_available: false } | %i(user_public_group user_internal_group user_private_group)
+ :external | { all_available: true } | %i(public_group user_public_group user_internal_group user_private_group)
+ :external | { all_available: false } | %i(user_public_group user_internal_group user_private_group)
:external | {} | %i(public_group user_public_group user_internal_group user_private_group)
- :admin | { all_available: true } | %i(public_group internal_group private_group user_public_group
- user_internal_group user_private_group)
- :admin | { all_available: false } | %i(user_public_group user_internal_group user_private_group)
+ :admin | { all_available: true } | %i(public_group internal_group private_group user_public_group
+ user_internal_group user_private_group)
+ :admin | { all_available: false } | %i(user_public_group user_internal_group user_private_group)
:admin | {} | %i(public_group internal_group private_group user_public_group user_internal_group
user_private_group)
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 80f7232f282..682fae06434 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -174,9 +174,13 @@ describe IssuesFinder do
context 'filtering by upcoming milestone' do
let(:params) { { milestone_title: Milestone::Upcoming.name } }
+ let!(:group) { create(:group, :public) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+
let(:project_no_upcoming_milestones) { create(:project, :public) }
let(:project_next_1_1) { create(:project, :public) }
let(:project_next_8_8) { create(:project, :public) }
+ let(:project_in_group) { create(:project, :public, namespace: group) }
let(:yesterday) { Date.today - 1.day }
let(:tomorrow) { Date.today + 1.day }
@@ -187,21 +191,22 @@ describe IssuesFinder do
[
create(:milestone, :closed, project: project_no_upcoming_milestones),
create(:milestone, project: project_next_1_1, title: '1.1', due_date: two_days_from_now),
- create(:milestone, project: project_next_1_1, title: '8.8', due_date: ten_days_from_now),
- create(:milestone, project: project_next_8_8, title: '1.1', due_date: yesterday),
- create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow)
+ create(:milestone, project: project_next_1_1, title: '8.9', due_date: ten_days_from_now),
+ create(:milestone, project: project_next_8_8, title: '1.2', due_date: yesterday),
+ create(:milestone, project: project_next_8_8, title: '8.8', due_date: tomorrow),
+ create(:milestone, group: group, title: '9.9', due_date: tomorrow)
]
end
before do
milestones.each do |milestone|
- create(:issue, project: milestone.project, milestone: milestone, author: user, assignees: [user])
+ create(:issue, project: milestone.project || project_in_group, milestone: milestone, author: user, assignees: [user])
end
end
- it 'returns issues in the upcoming milestone for each project' do
- expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8')
- expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now)
+ it 'returns issues in the upcoming milestone for each project or group' do
+ expect(issues.map { |issue| issue.milestone.title }).to contain_exactly('1.1', '8.8', '9.9')
+ expect(issues.map { |issue| issue.milestone.due_date }).to contain_exactly(tomorrow, two_days_from_now, tomorrow)
end
end
diff --git a/spec/finders/merge_request_target_project_finder_spec.rb b/spec/finders/merge_request_target_project_finder_spec.rb
index f302cf80ce8..d26a75179de 100644
--- a/spec/finders/merge_request_target_project_finder_spec.rb
+++ b/spec/finders/merge_request_target_project_finder_spec.rb
@@ -7,7 +7,7 @@ describe MergeRequestTargetProjectFinder do
subject(:finder) { described_class.new(current_user: user, source_project: forked_project) }
shared_examples 'finding related projects' do
- it 'finds sibling projects and base project' do
+ it 'finds sibling projects and base project' do
other_fork
expect(finder.execute).to contain_exactly(base_project, other_fork, forked_project)
diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb
index 656d120311a..ecffbb9e197 100644
--- a/spec/finders/milestones_finder_spec.rb
+++ b/spec/finders/milestones_finder_spec.rb
@@ -69,6 +69,12 @@ describe MilestonesFinder do
expect(result.to_a).to contain_exactly(milestone_1)
end
+
+ it 'filters by search_title' do
+ result = described_class.new(params.merge(search_title: 'one t')).execute
+
+ expect(result.to_a).to contain_exactly(milestone_1)
+ end
end
describe '#find_by' do
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index b51f1955ac4..0a685152cf9 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -121,7 +121,7 @@ describe NotesFinder do
let(:note1) { create :note_on_commit, project: project }
let(:note2) { create :note_on_commit, project: project }
let(:commit) { note1.noteable }
- let(:params) { { target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
+ let(:params) { { target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
before do
note1
diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb
index 60d02b12054..35279906854 100644
--- a/spec/finders/projects/serverless/functions_finder_spec.rb
+++ b/spec/finders/projects/serverless/functions_finder_spec.rb
@@ -29,15 +29,34 @@ describe Projects::Serverless::FunctionsFinder do
context 'has knative installed' do
let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
+ let(:finder) { described_class.new(project.clusters) }
it 'there are no functions' do
- expect(described_class.new(project.clusters).execute).to be_empty
+ expect(finder.execute).to be_empty
end
it 'there are functions', :use_clean_rails_memory_store_caching do
- stub_reactive_cache(knative, services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"])
+ stub_kubeclient_service_pods
+ stub_reactive_cache(knative,
+ {
+ services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
+ pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
+ })
- expect(described_class.new(project.clusters).execute).not_to be_empty
+ 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,
+ {
+ services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
+ pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
+ })
+
+ result = finder.service(cluster.environment_scope, cluster.project.name)
+ expect(result).not_to be_empty
+ expect(result["metadata"]["name"]).to be_eql(cluster.project.name)
end
end
end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 590e838f13e..ac866e49fcd 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -137,7 +137,7 @@ describe ProjectsFinder do
end
describe 'filter by trending' do
- let!(:trending_project) { create(:trending_project, project: public_project) }
+ let!(:trending_project) { create(:trending_project, project: public_project) }
let(:params) { { trending: true } }
it { is_expected.to eq([public_project]) }
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index dfeeb3040c6..134fb5f2c04 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -107,7 +107,7 @@ describe SnippetsFinder do
context 'filter by project' do
let(:user) { create :user }
let(:group) { create :group, :public }
- let(:project1) { create(:project, :public, group: group) }
+ let(:project1) { create(:project, :public, group: group) }
before do
@snippet1 = create(:project_snippet, :private, project: project1)
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 193ab6821a5..1bd39a46830 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -120,7 +120,9 @@
"rebase_path": { "type": ["string", "null"] },
"squash": { "type": "boolean" },
"test_reports_path": { "type": ["string", "null"] },
- "can_receive_suggestion": { "type": "boolean" }
+ "can_receive_suggestion": { "type": "boolean" },
+ "source_branch_protected": { "type": "boolean" },
+ "conflicts_docs_path": { "type": ["string", "null"] }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/error_tracking/error.json b/spec/fixtures/api/schemas/error_tracking/error.json
new file mode 100644
index 00000000000..df2c02d7d5d
--- /dev/null
+++ b/spec/fixtures/api/schemas/error_tracking/error.json
@@ -0,0 +1,21 @@
+{
+ "type": "object",
+ "required" : [
+ "external_url",
+ "last_seen",
+ "message",
+ "type"
+ ],
+ "properties" : {
+ "id": { "type": "string"},
+ "first_seen": { "type": "string", "format": "date-time" },
+ "last_seen": { "type": "string", "format": "date-time" },
+ "type": { "type": "string" },
+ "message": { "type": "string" },
+ "culprit": { "type": "string" },
+ "count": { "type": "integer"},
+ "external_url": { "type": "string" },
+ "user_count": { "type": "integer"}
+ },
+ "additionalProperties": true
+}
diff --git a/spec/fixtures/api/schemas/error_tracking/index.json b/spec/fixtures/api/schemas/error_tracking/index.json
new file mode 100644
index 00000000000..d3abc29ffa7
--- /dev/null
+++ b/spec/fixtures/api/schemas/error_tracking/index.json
@@ -0,0 +1,15 @@
+{
+ "type": "object",
+ "required": [
+ "external_url",
+ "errors"
+ ],
+ "properties": {
+ "external_url": { "type": ["string", "null"] },
+ "errors": {
+ "type": "array",
+ "items": { "$ref": "error.json" }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/registry/repository.json b/spec/fixtures/api/schemas/registry/repository.json
index 4175642eb00..e0fd4620c43 100644
--- a/spec/fixtures/api/schemas/registry/repository.json
+++ b/spec/fixtures/api/schemas/registry/repository.json
@@ -2,20 +2,27 @@
"type": "object",
"required" : [
"id",
+ "name",
"path",
"location",
- "tags_path"
+ "created_at"
],
"properties" : {
"id": {
"type": "integer"
},
+ "name": {
+ "type": "string"
+ },
"path": {
"type": "string"
},
"location": {
"type": "string"
},
+ "created_at": {
+ "type": "date-time"
+ },
"tags_path": {
"type": "string"
},
diff --git a/spec/fixtures/api/schemas/registry/tag.json b/spec/fixtures/api/schemas/registry/tag.json
index 3a2c88791e1..48f8402b65b 100644
--- a/spec/fixtures/api/schemas/registry/tag.json
+++ b/spec/fixtures/api/schemas/registry/tag.json
@@ -2,15 +2,22 @@
"type": "object",
"required" : [
"name",
+ "path",
"location"
],
"properties" : {
"name": {
"type": "string"
},
+ "path": {
+ "type": "string"
+ },
"location": {
"type": "string"
},
+ "digest": {
+ "type": "string"
+ },
"revision": {
"type": "string"
},
diff --git a/spec/fixtures/api/schemas/release.json b/spec/fixtures/api/schemas/release.json
index 844405c3acd..86f0f27606c 100644
--- a/spec/fixtures/api/schemas/release.json
+++ b/spec/fixtures/api/schemas/release.json
@@ -12,6 +12,17 @@
},
"author": {
"oneOf": [{ "type": "null" }, { "$ref": "public_api/v4/user/basic.json" }]
+ },
+ "assets": {
+ "count": { "type": "integer" },
+ "links": { "$ref": "release/links.json" },
+ "sources": {
+ "type": "array",
+ "items": {
+ "format": "zip",
+ "url": "string"
+ }
+ }
}
},
"additionalProperties": false
diff --git a/spec/fixtures/api/schemas/release/link.json b/spec/fixtures/api/schemas/release/link.json
new file mode 100644
index 00000000000..97347cb91cc
--- /dev/null
+++ b/spec/fixtures/api/schemas/release/link.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "required": ["name", "url"],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "url": { "type": "string" },
+ "external": { "type": "boolean" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/release/links.json b/spec/fixtures/api/schemas/release/links.json
new file mode 100644
index 00000000000..766bc7ce20a
--- /dev/null
+++ b/spec/fixtures/api/schemas/release/links.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "link.json" }
+}
diff --git a/spec/fixtures/csv_comma.csv b/spec/fixtures/csv_comma.csv
new file mode 100644
index 00000000000..e477a27d243
--- /dev/null
+++ b/spec/fixtures/csv_comma.csv
@@ -0,0 +1,4 @@
+title,description
+Issue in 中文,Test description
+"Hello","World"
+"Title with quote""",Description
diff --git a/spec/fixtures/csv_semicolon.csv b/spec/fixtures/csv_semicolon.csv
new file mode 100644
index 00000000000..679797489e2
--- /dev/null
+++ b/spec/fixtures/csv_semicolon.csv
@@ -0,0 +1,5 @@
+title;description
+Issue in 中文;Test description
+Title with, comma;"Description"
+
+"Hello";"World"
diff --git a/spec/fixtures/csv_tab.csv b/spec/fixtures/csv_tab.csv
new file mode 100644
index 00000000000..f801794ea9c
--- /dev/null
+++ b/spec/fixtures/csv_tab.csv
@@ -0,0 +1,4 @@
+title description
+Issue in 中文 Test description
+ "Error Row"
+"Hello" "World"
diff --git a/spec/fixtures/emails/merge_request_multiple_patches.eml b/spec/fixtures/emails/merge_request_multiple_patches.eml
index 311b99a525d..7d2e0cd4e50 100644
--- a/spec/fixtures/emails/merge_request_multiple_patches.eml
+++ b/spec/fixtures/emails/merge_request_multiple_patches.eml
@@ -1,5 +1,5 @@
From: "Jake the Dog" <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Subject: new-branch-with-a-patch
Date: Wed, 31 Oct 2018 17:27:52 +0100
X-Mailer: MailMate (1.12r5523)
diff --git a/spec/fixtures/emails/merge_request_with_conflicting_patch.eml b/spec/fixtures/emails/merge_request_with_conflicting_patch.eml
index ddfdfe9e24a..5c9eda640bc 100644
--- a/spec/fixtures/emails/merge_request_with_conflicting_patch.eml
+++ b/spec/fixtures/emails/merge_request_with_conflicting_patch.eml
@@ -1,5 +1,5 @@
From: "Jake the Dog" <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Subject: feature
Date: Wed, 31 Oct 2018 17:27:52 +0100
X-Mailer: MailMate (1.12r5523)
diff --git a/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml b/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml
index 965658721cd..9fabfc23e3b 100644
--- a/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml
+++ b/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml
@@ -1,5 +1,5 @@
From: "Jake the Dog" <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Subject: new-branch-with-a-patch
Date: Wed, 24 Oct 2018 16:39:49 +0200
X-Mailer: MailMate (1.12r5523)
diff --git a/spec/fixtures/emails/valid_merge_request_with_patch.eml b/spec/fixtures/emails/valid_merge_request_with_patch.eml
index 143fa77d1fa..e0f406639a3 100644
--- a/spec/fixtures/emails/valid_merge_request_with_patch.eml
+++ b/spec/fixtures/emails/valid_merge_request_with_patch.eml
@@ -1,5 +1,5 @@
From: "Jake the Dog" <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Subject: new-branch-with-a-patch
Date: Wed, 24 Oct 2018 16:39:49 +0200
X-Mailer: MailMate (1.12r5523)
diff --git a/spec/fixtures/emails/valid_new_issue.eml b/spec/fixtures/emails/valid_new_issue.eml
index 3cf53a656a5..7d63016ed04 100644
--- a/spec/fixtures/emails/valid_new_issue.eml
+++ b/spec/fixtures/emails/valid_new_issue.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-issue@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: New Issue by email
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_issue_empty.eml b/spec/fixtures/emails/valid_new_issue_empty.eml
index fc1d52a3f42..58a6ef29d69 100644
--- a/spec/fixtures/emails/valid_new_issue_empty.eml
+++ b/spec/fixtures/emails/valid_new_issue_empty.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-issue@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: New Issue by email
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_issue_legacy.eml b/spec/fixtures/emails/valid_new_issue_legacy.eml
new file mode 100644
index 00000000000..3cf53a656a5
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_issue_legacy.eml
@@ -0,0 +1,23 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+The reply by email functionality should be extended to allow creating a new issue by email.
+
+* Allow an admin to specify which project the issue should be created under by checking the sender domain.
+* Possibly allow the use of regular expression matches within the subject/body to specify which project the issue should be created under.
diff --git a/spec/fixtures/emails/valid_new_issue_with_quote.eml b/spec/fixtures/emails/valid_new_issue_with_quote.eml
index 0caf8ed4e9e..3a9b9dbbba5 100644
--- a/spec/fixtures/emails/valid_new_issue_with_quote.eml
+++ b/spec/fixtures/emails/valid_new_issue_with_quote.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-issue@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: New Issue by email
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_merge_request.eml b/spec/fixtures/emails/valid_new_merge_request.eml
index 729df674604..e12843ea76b 100644
--- a/spec/fixtures/emails/valid_new_merge_request.eml
+++ b/spec/fixtures/emails/valid_new_merge_request.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
-Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: feature
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_merge_request_legacy.eml b/spec/fixtures/emails/valid_new_merge_request_legacy.eml
new file mode 100644
index 00000000000..b6cf064af19
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_merge_request_legacy.eml
@@ -0,0 +1,20 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: feature
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+Merge request description
diff --git a/spec/fixtures/emails/valid_new_merge_request_no_description.eml b/spec/fixtures/emails/valid_new_merge_request_no_description.eml
index 480675a6d7e..3ac0ea191a9 100644
--- a/spec/fixtures/emails/valid_new_merge_request_no_description.eml
+++ b/spec/fixtures/emails/valid_new_merge_request_no_description.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: feature
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/valid_new_merge_request_no_subject.eml b/spec/fixtures/emails/valid_new_merge_request_no_subject.eml
index 27eb1b7d922..c2735ccb08a 100644
--- a/spec/fixtures/emails/valid_new_merge_request_no_subject.eml
+++ b/spec/fixtures/emails/valid_new_merge_request_no_subject.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject:
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/wrong_incoming_email_token.eml b/spec/fixtures/emails/wrong_issue_incoming_email_token.eml
index 0994c2f7775..d3ba6943a90 100644
--- a/spec/fixtures/emails/wrong_incoming_email_token.eml
+++ b/spec/fixtures/emails/wrong_issue_incoming_email_token.eml
@@ -1,11 +1,11 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
-Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
-To: incoming+gitlabhq/gitlabhq+bad_token@appmail.adventuretime.ooo
+To: incoming+gitlabhq-gitlabhq-project_id-bad_token-issue@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: New Issue by email
Mime-Version: 1.0
diff --git a/spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml b/spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml
new file mode 100644
index 00000000000..c7b758b8f1f
--- /dev/null
+++ b/spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml
@@ -0,0 +1,18 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq-gitlabhq-project_id-bad_token-merge-request@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
diff --git a/spec/fixtures/malicious.bundle b/spec/fixtures/malicious.bundle
new file mode 100644
index 00000000000..7ba47932906
--- /dev/null
+++ b/spec/fixtures/malicious.bundle
@@ -0,0 +1 @@
+gitdir: foo.git
diff --git a/spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json
new file mode 100644
index 00000000000..ce66f562175
--- /dev/null
+++ b/spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json
@@ -0,0 +1,178 @@
+[
+ {
+ "category": "dependency_scanning",
+ "name": "io.netty/netty - CVE-2014-3488",
+ "message": "DoS by CPU exhaustion when using malicious SSL packets",
+ "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
+ "severity": "Unknown",
+ "solution": "Upgrade to the latest version",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "app/pom.xml",
+ "dependency": {
+ "package": {
+ "name": "io.netty/netty"
+ },
+ "version": "3.9.1.Final"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
+ "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
+ "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2014-3488",
+ "value": "CVE-2014-3488",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://bugzilla.redhat.com/CVE-2014-3488"
+ },
+ {
+ "url": "http://netty.io/news/2014/06/11/3.html"
+ },
+ {
+ "url": "https://github.com/netty/netty/issues/2562"
+ }
+ ],
+ "priority": "Unknown",
+ "file": "app/pom.xml",
+ "url": "https://bugzilla.redhat.com/CVE-2014-3488",
+ "tool": "gemnasium"
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "Django - CVE-2017-12794",
+ "message": "Possible XSS in traceback section of technical 500 debug page",
+ "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version or apply patch.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "app/requirements.txt",
+ "dependency": {
+ "package": {
+ "name": "Django"
+ },
+ "version": "1.11.3"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
+ "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
+ "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2017-12794",
+ "value": "CVE-2017-12794",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
+ }
+ ],
+ "priority": "Unknown",
+ "file": "app/requirements.txt",
+ "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
+ "tool": "gemnasium"
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "nokogiri - USN-3424-1",
+ "message": "Vulnerabilities in libxml2",
+ "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
+ "value": "06565b64-486d-4326-b906-890d9915804d",
+ "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
+ },
+ {
+ "type": "usn",
+ "name": "USN-3424-1",
+ "value": "USN-3424-1",
+ "url": "https://usn.ubuntu.com/3424-1/"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
+ }
+ ],
+ "priority": "Unknown",
+ "file": "rails/Gemfile.lock",
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
+ "tool": "gemnasium"
+ },
+ {
+ "category": "dependency_scanning",
+ "name": "ffi - CVE-2018-1000201",
+ "message": "ruby-ffi DDL loading issue on Windows OS",
+ "cve": "ffi:1.9.18:CVE-2018-1000201",
+ "severity": "High",
+ "solution": "upgrade to \u003e= 1.9.24",
+ "scanner": {
+ "id": "bundler_audit",
+ "name": "bundler-audit"
+ },
+ "location": {
+ "file": "sast-sample-rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "ffi"
+ },
+ "version": "1.9.18"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "cve",
+ "name": "CVE-2018-1000201",
+ "value": "CVE-2018-1000201",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
+ }
+ ],
+ "priority": "High",
+ "file": "sast-sample-rails/Gemfile.lock",
+ "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
+ "tool": "bundler_audit"
+ }
+]
diff --git a/spec/fixtures/security-reports/deprecated/gl-sast-report.json b/spec/fixtures/security-reports/deprecated/gl-sast-report.json
new file mode 100644
index 00000000000..a85b9be8b5f
--- /dev/null
+++ b/spec/fixtures/security-reports/deprecated/gl-sast-report.json
@@ -0,0 +1,944 @@
+[
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 1,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 47,
+ "end_line": 47,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken2"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 47,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 41,
+ "end_line": 41,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken1"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 41,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 11,
+ "end_line": 11
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 11,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 12,
+ "end_line": 12
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 12,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 13,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 14,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Pickle library appears to be in use, possible security issue.",
+ "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 15,
+ "end_line": 15
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B301",
+ "value": "B301"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/imports/imports-aliases.py",
+ "line": 15,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "ECB mode is insecure",
+ "message": "ECB mode is insecure",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-ECB_MODE",
+ "value": "ECB_MODE",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Cipher with no integrity",
+ "message": "Cipher with no integrity",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-CIPHER_INTEGRITY",
+ "value": "CIPHER_INTEGRITY",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
+ }
+ ],
+ "priority": "Medium",
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 14,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 10,
+ "end_line": 10
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "line": 10,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports.py",
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports.py",
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 22,
+ "end_line": 22
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B106",
+ "value": "B106",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 22,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'root'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 5,
+ "end_line": 5
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 5,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: ''",
+ "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 13,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 23,
+ "end_line": 23
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 23,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 24,
+ "end_line": 24
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "line": 24,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-function.py",
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-function.py",
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 7,
+ "end_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
+ "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B602",
+ "value": "B602",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 1,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-from.py",
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 7,
+ "end_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with loads module.",
+ "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
+ "file": "python/imports/imports-aliases.py",
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "c/subdir/utils.c",
+ "line": 4,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
+ "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
+ "confidence": "Low",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-362",
+ "value": "362",
+ "url": "https://cwe.mitre.org/data/definitions/362.html"
+ }
+ ],
+ "file": "c/subdir/utils.c",
+ "line": 8,
+ "url": "https://cwe.mitre.org/data/definitions/362.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "cplusplus/src/hello.cpp",
+ "line": 6,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
+ "confidence": "Low",
+ "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
+ "file": "cplusplus/src/hello.cpp",
+ "line": 7,
+ "url": "https://cwe.mitre.org/data/definitions/120.html",
+ "tool": "flawfinder"
+ }
+]
diff --git a/spec/fixtures/security-reports/feature-branch.zip b/spec/fixtures/security-reports/feature-branch.zip
index 730ce3dc5f8..dd49f4e9e1d 100644
--- a/spec/fixtures/security-reports/feature-branch.zip
+++ b/spec/fixtures/security-reports/feature-branch.zip
Binary files differ
diff --git a/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json
index 9840382df6f..6f89d20d4bf 100644
--- a/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json
+++ b/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json
@@ -1,18 +1,16 @@
{
- "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583",
- "unapproved": [
- "CVE-2017-15650"
- ],
- "vulnerabilities": [
- {
- "featurename": "musl",
- "featureversion": "1.1.14-r15",
- "vulnerability": "CVE-2017-15650",
- "namespace": "alpine:v3.4",
- "description": "",
- "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650",
- "severity": "Medium",
- "fixedby": "1.1.14-r16"
- }
- ]
+ "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583",
+ "unapproved": ["CVE-2017-15650"],
+ "vulnerabilities": [
+ {
+ "featurename": "musl",
+ "featureversion": "1.1.14-r15",
+ "vulnerability": "CVE-2017-15650",
+ "namespace": "alpine:v3.4",
+ "description": "",
+ "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650",
+ "severity": "Medium",
+ "fixedby": "1.1.14-r16"
+ }
+ ]
}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
index ce66f562175..8555be6618c 100644
--- a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
+++ b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
@@ -1,178 +1,181 @@
-[
- {
- "category": "dependency_scanning",
- "name": "io.netty/netty - CVE-2014-3488",
- "message": "DoS by CPU exhaustion when using malicious SSL packets",
- "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
- "severity": "Unknown",
- "solution": "Upgrade to the latest version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/pom.xml",
- "dependency": {
- "package": {
- "name": "io.netty/netty"
+{
+ "version": "1.3",
+ "vulnerabilities": [
+ {
+ "category": "dependency_scanning",
+ "name": "io.netty/netty - CVE-2014-3488",
+ "message": "DoS by CPU exhaustion when using malicious SSL packets",
+ "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
+ "severity": "Unknown",
+ "solution": "Upgrade to the latest version",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "app/pom.xml",
+ "dependency": {
+ "package": {
+ "name": "io.netty/netty"
+ },
+ "version": "3.9.1.Final"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
+ "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
+ "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2014-3488",
+ "value": "CVE-2014-3488",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://bugzilla.redhat.com/CVE-2014-3488"
},
- "version": "3.9.1.Final"
- }
+ {
+ "url": "http://netty.io/news/2014/06/11/3.html"
+ },
+ {
+ "url": "https://github.com/netty/netty/issues/2562"
+ }
+ ],
+ "priority": "Unknown",
+ "file": "app/pom.xml",
+ "url": "https://bugzilla.redhat.com/CVE-2014-3488",
+ "tool": "gemnasium"
},
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2014-3488",
- "value": "CVE-2014-3488",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
- }
- ],
- "links": [
- {
- "url": "https://bugzilla.redhat.com/CVE-2014-3488"
+ {
+ "category": "dependency_scanning",
+ "name": "Django - CVE-2017-12794",
+ "message": "Possible XSS in traceback section of technical 500 debug page",
+ "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version or apply patch.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
},
- {
- "url": "http://netty.io/news/2014/06/11/3.html"
+ "location": {
+ "file": "app/requirements.txt",
+ "dependency": {
+ "package": {
+ "name": "Django"
+ },
+ "version": "1.11.3"
+ }
},
- {
- "url": "https://github.com/netty/netty/issues/2562"
- }
- ],
- "priority": "Unknown",
- "file": "app/pom.xml",
- "url": "https://bugzilla.redhat.com/CVE-2014-3488",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "Django - CVE-2017-12794",
- "message": "Possible XSS in traceback section of technical 500 debug page",
- "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
- "severity": "Unknown",
- "solution": "Upgrade to latest version or apply patch.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/requirements.txt",
- "dependency": {
- "package": {
- "name": "Django"
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
+ "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
+ "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
},
- "version": "1.11.3"
- }
+ {
+ "type": "cve",
+ "name": "CVE-2017-12794",
+ "value": "CVE-2017-12794",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
+ }
+ ],
+ "priority": "Unknown",
+ "file": "app/requirements.txt",
+ "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
+ "tool": "gemnasium"
},
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
- "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
- "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
+ {
+ "category": "dependency_scanning",
+ "name": "nokogiri - USN-3424-1",
+ "message": "Vulnerabilities in libxml2",
+ "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
},
- {
- "type": "cve",
- "name": "CVE-2017-12794",
- "value": "CVE-2017-12794",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
- }
- ],
- "links": [
- {
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
- }
- ],
- "priority": "Unknown",
- "file": "app/requirements.txt",
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "nokogiri - USN-3424-1",
- "message": "Vulnerabilities in libxml2",
- "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
+ "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"
},
- "version": "1.8.0"
- }
+ {
+ "type": "usn",
+ "name": "USN-3424-1",
+ "value": "USN-3424-1",
+ "url": "https://usn.ubuntu.com/3424-1/"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
+ }
+ ],
+ "priority": "Unknown",
+ "file": "rails/Gemfile.lock",
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
+ "tool": "gemnasium"
},
- "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"
+ {
+ "category": "dependency_scanning",
+ "name": "ffi - CVE-2018-1000201",
+ "message": "ruby-ffi DDL loading issue on Windows OS",
+ "cve": "ffi:1.9.18:CVE-2018-1000201",
+ "severity": "High",
+ "solution": "upgrade to \u003e= 1.9.24",
+ "scanner": {
+ "id": "bundler_audit",
+ "name": "bundler-audit"
},
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ],
- "priority": "Unknown",
- "file": "rails/Gemfile.lock",
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "ffi - CVE-2018-1000201",
- "message": "ruby-ffi DDL loading issue on Windows OS",
- "cve": "ffi:1.9.18:CVE-2018-1000201",
- "severity": "High",
- "solution": "upgrade to \u003e= 1.9.24",
- "scanner": {
- "id": "bundler_audit",
- "name": "bundler-audit"
- },
- "location": {
+ "location": {
+ "file": "sast-sample-rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "ffi"
+ },
+ "version": "1.9.18"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "cve",
+ "name": "CVE-2018-1000201",
+ "value": "CVE-2018-1000201",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
+ }
+ ],
+ "priority": "High",
"file": "sast-sample-rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "ffi"
- },
- "version": "1.9.18"
- }
- },
- "identifiers": [
- {
- "type": "cve",
- "name": "CVE-2018-1000201",
- "value": "CVE-2018-1000201",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
- }
- ],
- "links": [
- {
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
- }
- ],
- "priority": "High",
- "file": "sast-sample-rails/Gemfile.lock",
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
- "tool": "bundler_audit"
- }
-]
+ "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
+ "tool": "bundler_audit"
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json b/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json
index c1d20fa02fa..5fd81fd69bd 100644
--- a/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json
+++ b/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json
@@ -1,16 +1,12 @@
{
"licenses": [
{
- "count": 13,
- "name": "MIT"
- },
- {
- "count": 2,
- "name": "New BSD"
+ "count": 1,
+ "name": "WTFPL"
},
{
"count": 1,
- "name": "LGPL"
+ "name": "MIT"
}
],
"dependencies": [
@@ -20,107 +16,9 @@
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
- "name": "bundler",
- "url": "http://bundler.io",
- "description": "The best way to manage your application's dependencies",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "concurrent-ruby",
- "url": "http://www.concurrent-ruby.com",
- "description": "Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "connection_pool",
- "url": "https://github.com/mperham/connection_pool",
- "description": "Generic connection pool for Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "mini_portile2",
- "url": "http://github.com/flavorjones/mini_portile",
- "description": "Simplistic port-like solution for developers",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "mustermann",
- "url": "https://github.com/sinatra/mustermann",
- "description": "Your personal string matching expert.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "nokogiri",
- "url": "http://nokogiri.org",
- "description": "Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "New BSD",
- "url": "http://opensource.org/licenses/BSD-3-Clause"
- },
- "dependency": {
- "name": "pg",
- "url": "https://bitbucket.org/ged/ruby-pg",
- "description": "Pg is the Ruby interface to the {PostgreSQL RDBMS}[http://www.postgresql.org/]",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "New BSD",
- "url": "http://opensource.org/licenses/BSD-3-Clause"
- },
- "dependency": {
- "name": "puma",
- "url": "http://puma.io",
- "description": "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications",
+ "name": "actioncable",
+ "url": "http://rubyonrails.org",
+ "description": "WebSocket framework for Rails.",
"pathes": [
"."
]
@@ -128,111 +26,13 @@
},
{
"license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rack",
- "url": "https://rack.github.io/",
- "description": "a modular Ruby webserver interface",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rack-protection",
- "url": "http://github.com/sinatra/sinatra/tree/master/rack-protection",
- "description": "Protect against typical web attacks, works with all Rack apps, including Rails.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "redis",
- "url": "https://github.com/redis/redis-rb",
- "description": "A Ruby client library for Redis",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "LGPL",
- "url": "http://www.gnu.org/licenses/lgpl.txt"
- },
- "dependency": {
- "name": "sidekiq",
- "url": "http://sidekiq.org",
- "description": "Simple, efficient background processing for Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sinatra",
- "url": "http://www.sinatrarb.com/",
- "description": "Classy web-development dressed in a DSL",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "slim",
- "url": "http://slim-lang.com/",
- "description": "Slim is a template language.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "temple",
- "url": "https://github.com/judofyr/temple",
- "description": "Template compilation framework in Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
+ "name": "WTFPL",
+ "url": "http://www.wtfpl.net/"
},
"dependency": {
- "name": "tilt",
- "url": "http://github.com/rtomayko/tilt/",
- "description": "Generic interface to multiple Ruby template engines",
+ "name": "wtfpl_init",
+ "url": "https://rubygems.org/gems/wtfpl_init",
+ "description": "Download WTFPL license file and rename to LICENSE.md or something",
"pathes": [
"."
]
diff --git a/spec/fixtures/security-reports/feature-branch/gl-sast-report.json b/spec/fixtures/security-reports/feature-branch/gl-sast-report.json
index a85b9be8b5f..4bef3d22f70 100644
--- a/spec/fixtures/security-reports/feature-branch/gl-sast-report.json
+++ b/spec/fixtures/security-reports/feature-branch/gl-sast-report.json
@@ -1,944 +1,947 @@
-[
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+{
+ "version": "1.2",
+ "vulnerabilities": [
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
"file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 1,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
+ "line": 1,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 47,
+ "end_line": 47,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken2"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
"file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 47,
- "end_line": 47,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken2"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 47,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
+ "line": 47,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 41,
+ "end_line": 41,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken1"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
"file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 41,
- "end_line": 41,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken1"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 41,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 41,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 11,
+ "end_line": 11
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 11,
- "end_line": 11
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 11,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 11,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 12,
+ "end_line": 12
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 12,
- "end_line": 12
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 12,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 12,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 13,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 13,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 14,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Pickle library appears to be in use, possible security issue.",
- "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 14,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Pickle library appears to be in use, possible security issue.",
+ "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 15,
+ "end_line": 15
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B301",
+ "value": "B301"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 15,
- "end_line": 15
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B301",
- "value": "B301"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 15,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "ECB mode is insecure",
- "message": "ECB mode is insecure",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
+ "line": 15,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "ECB mode is insecure",
+ "message": "ECB mode is insecure",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-ECB_MODE",
+ "value": "ECB_MODE",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
+ }
+ ],
+ "priority": "Medium",
"file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-ECB_MODE",
- "value": "ECB_MODE",
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Cipher with no integrity",
- "message": "Cipher with no integrity",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Cipher with no integrity",
+ "message": "Cipher with no integrity",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-CIPHER_INTEGRITY",
+ "value": "CIPHER_INTEGRITY",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
+ }
+ ],
+ "priority": "Medium",
"file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-CIPHER_INTEGRITY",
- "value": "CIPHER_INTEGRITY",
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
"file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 14,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 14,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 10,
+ "end_line": 10
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
"file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 10,
- "end_line": 10
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 10,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 10,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-aliases.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 22,
+ "end_line": 22
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B106",
+ "value": "B106",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 22,
- "end_line": 22
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B106",
- "value": "B106",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 22,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'root'",
- "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 22,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'root'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 5,
+ "end_line": 5
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 5,
- "end_line": 5
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 5,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: ''",
- "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 5,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: ''",
+ "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 13,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 13,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 23,
+ "end_line": 23
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 23,
- "end_line": 23
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 23,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 23,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 24,
+ "end_line": 24
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 24,
- "end_line": 24
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 24,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 24,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-function.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-function.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 7,
+ "end_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-from.py",
- "start_line": 7,
- "end_line": 7
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
- "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
+ "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B602",
+ "value": "B602",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-aliases.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B602",
- "value": "B602",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-from.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 1,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-from.py",
- "start_line": 1,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 7,
+ "end_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-aliases.py",
- "start_line": 7,
- "end_line": 8
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with loads module.",
- "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with loads module.",
+ "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-aliases.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
"file": "c/subdir/utils.c",
- "start_line": 4
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 4,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
- "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
- "confidence": "Low",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
+ "line": 4,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
+ "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
+ "confidence": "Low",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-362",
+ "value": "362",
+ "url": "https://cwe.mitre.org/data/definitions/362.html"
+ }
+ ],
"file": "c/subdir/utils.c",
- "start_line": 8
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-362",
- "value": "362",
- "url": "https://cwe.mitre.org/data/definitions/362.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 8,
- "url": "https://cwe.mitre.org/data/definitions/362.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
+ "line": 8,
+ "url": "https://cwe.mitre.org/data/definitions/362.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
"file": "cplusplus/src/hello.cpp",
- "start_line": 6
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 6,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
- "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
- "confidence": "Low",
- "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
+ "line": 6,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
+ "confidence": "Low",
+ "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
"file": "cplusplus/src/hello.cpp",
- "start_line": 7
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 7,
- "url": "https://cwe.mitre.org/data/definitions/120.html",
- "tool": "flawfinder"
- }
-]
+ "line": 7,
+ "url": "https://cwe.mitre.org/data/definitions/120.html",
+ "tool": "flawfinder"
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/master.zip b/spec/fixtures/security-reports/master.zip
index 4684aecb738..2261b5a1674 100644
--- a/spec/fixtures/security-reports/master.zip
+++ b/spec/fixtures/security-reports/master.zip
Binary files differ
diff --git a/spec/fixtures/security-reports/master/gl-container-scanning-report.json b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
index 500c19e3abb..68c6099836b 100644
--- a/spec/fixtures/security-reports/master/gl-container-scanning-report.json
+++ b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
@@ -1,18 +1,92 @@
{
- "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583",
- "unapproved": [
- "CVE-2017-15651"
- ],
- "vulnerabilities": [
- {
- "featurename": "musl",
- "featureversion": "1.1.14-r15",
- "vulnerability": "CVE-2017-15651",
- "namespace": "alpine:v3.4",
- "description": "",
- "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15651",
- "severity": "Medium",
- "fixedby": "1.1.14-r16"
- }
- ]
+ "image": "registry.gitlab.com/groulot/container-scanning-test/master:5f21de6956aee99ddb68ae49498662d9872f50ff",
+ "unapproved": [
+ "CVE-2017-18018",
+ "CVE-2016-2781",
+ "CVE-2017-12424",
+ "CVE-2007-5686",
+ "CVE-2013-4235"
+ ],
+ "vulnerabilities": [
+ {
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2017-18269",
+ "namespace": "debian:9",
+ "description": "SSE2-optimized memmove implementation problem.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2017-18269",
+ "severity": "Defcon1",
+ "fixedby": "2.24-11+deb9u4"
+ },
+ {
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2017-16997",
+ "namespace": "debian:9",
+ "description": "elf/dl-load.c in the GNU C Library (aka glibc or libc6) 2.19 through 2.26 mishandles RPATH and RUNPATH containing $ORIGIN for a privileged (setuid or AT_SECURE) program, which allows local users to gain privileges via a Trojan horse library in the current working directory, related to the fillin_rpath and decompose_rpath functions. This is associated with misinterpretion of an empty RPATH/RUNPATH token as the \"./\" directory. NOTE: this configuration of RPATH/RUNPATH for a privileged program is apparently very uncommon; most likely, no such program is shipped with any common Linux distribution.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2017-16997",
+ "severity": "Critical",
+ "fixedby": ""
+ },
+ {
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2018-1000001",
+ "namespace": "debian:9",
+ "description": "In glibc 2.26 and earlier there is confusion in the usage of getcwd() by realpath() which can be used to write before the destination buffer leading to a buffer underflow and potential code execution.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2018-1000001",
+ "severity": "High",
+ "fixedby": ""
+ },
+ {
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2016-10228",
+ "namespace": "debian:9",
+ "description": "The iconv program in the GNU C Library (aka glibc or libc6) 2.25 and earlier, when invoked with the -c option, enters an infinite loop when processing invalid multi-byte input sequences, leading to a denial of service.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2016-10228",
+ "severity": "Medium",
+ "fixedby": ""
+ },
+ {
+ "featurename": "elfutils",
+ "featureversion": "0.168-1",
+ "vulnerability": "CVE-2018-18520",
+ "namespace": "debian:9",
+ "description": "An Invalid Memory Address Dereference exists in the function elf_end in libelf in elfutils through v0.174. Although eu-size is intended to support ar files inside ar files, handle_ar in size.c closes the outer ar file before handling all inner entries. The vulnerability allows attackers to cause a denial of service (application crash) with a crafted ELF file.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2018-18520",
+ "severity": "Low",
+ "fixedby": ""
+ },
+ {
+ "featurename": "glibc",
+ "featureversion": "2.24-11+deb9u3",
+ "vulnerability": "CVE-2010-4052",
+ "namespace": "debian:9",
+ "description": "Stack consumption vulnerability in the regcomp implementation in the GNU C Library (aka glibc or libc6) through 2.11.3, and 2.12.x through 2.12.2, allows context-dependent attackers to cause a denial of service (resource exhaustion) via a regular expression containing adjacent repetition operators, as demonstrated by a {10,}{10,}{10,}{10,} sequence in the proftpd.gnu.c exploit for ProFTPD.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2010-4052",
+ "severity": "Negligible",
+ "fixedby": ""
+ },
+ {
+ "featurename": "nettle",
+ "featureversion": "3.3-1",
+ "vulnerability": "CVE-2018-16869",
+ "namespace": "debian:9",
+ "description": "A Bleichenbacher type side-channel based padding oracle attack was found in the way nettle handles endian conversion of RSA decrypted PKCS#1 v1.5 data. An attacker who is able to run a process on the same physical core as the victim process, could use this flaw extract plaintext or in some cases downgrade any TLS connections to a vulnerable server.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2018-16869",
+ "severity": "Unknown",
+ "fixedby": ""
+ },
+ {
+ "featurename": "perl",
+ "featureversion": "5.24.1-3+deb9u4",
+ "vulnerability": "CVE-2018-18311",
+ "namespace": "debian:9",
+ "description": "Perl before 5.26.3 and 5.28.x before 5.28.1 has a buffer overflow via a crafted regular expression that triggers invalid write operations.",
+ "link": "https://security-tracker.debian.org/tracker/CVE-2018-18311",
+ "severity": "Unknown",
+ "fixedby": "5.24.1-3+deb9u5"
+ }
+ ]
}
diff --git a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
index ce66f562175..8555be6618c 100644
--- a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
+++ b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
@@ -1,178 +1,181 @@
-[
- {
- "category": "dependency_scanning",
- "name": "io.netty/netty - CVE-2014-3488",
- "message": "DoS by CPU exhaustion when using malicious SSL packets",
- "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
- "severity": "Unknown",
- "solution": "Upgrade to the latest version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/pom.xml",
- "dependency": {
- "package": {
- "name": "io.netty/netty"
+{
+ "version": "1.3",
+ "vulnerabilities": [
+ {
+ "category": "dependency_scanning",
+ "name": "io.netty/netty - CVE-2014-3488",
+ "message": "DoS by CPU exhaustion when using malicious SSL packets",
+ "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
+ "severity": "Unknown",
+ "solution": "Upgrade to the latest version",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
+ },
+ "location": {
+ "file": "app/pom.xml",
+ "dependency": {
+ "package": {
+ "name": "io.netty/netty"
+ },
+ "version": "3.9.1.Final"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
+ "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
+ "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
+ },
+ {
+ "type": "cve",
+ "name": "CVE-2014-3488",
+ "value": "CVE-2014-3488",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://bugzilla.redhat.com/CVE-2014-3488"
},
- "version": "3.9.1.Final"
- }
+ {
+ "url": "http://netty.io/news/2014/06/11/3.html"
+ },
+ {
+ "url": "https://github.com/netty/netty/issues/2562"
+ }
+ ],
+ "priority": "Unknown",
+ "file": "app/pom.xml",
+ "url": "https://bugzilla.redhat.com/CVE-2014-3488",
+ "tool": "gemnasium"
},
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2014-3488",
- "value": "CVE-2014-3488",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
- }
- ],
- "links": [
- {
- "url": "https://bugzilla.redhat.com/CVE-2014-3488"
+ {
+ "category": "dependency_scanning",
+ "name": "Django - CVE-2017-12794",
+ "message": "Possible XSS in traceback section of technical 500 debug page",
+ "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version or apply patch.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
},
- {
- "url": "http://netty.io/news/2014/06/11/3.html"
+ "location": {
+ "file": "app/requirements.txt",
+ "dependency": {
+ "package": {
+ "name": "Django"
+ },
+ "version": "1.11.3"
+ }
},
- {
- "url": "https://github.com/netty/netty/issues/2562"
- }
- ],
- "priority": "Unknown",
- "file": "app/pom.xml",
- "url": "https://bugzilla.redhat.com/CVE-2014-3488",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "Django - CVE-2017-12794",
- "message": "Possible XSS in traceback section of technical 500 debug page",
- "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
- "severity": "Unknown",
- "solution": "Upgrade to latest version or apply patch.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/requirements.txt",
- "dependency": {
- "package": {
- "name": "Django"
+ "identifiers": [
+ {
+ "type": "gemnasium",
+ "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
+ "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
+ "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
},
- "version": "1.11.3"
- }
+ {
+ "type": "cve",
+ "name": "CVE-2017-12794",
+ "value": "CVE-2017-12794",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
+ }
+ ],
+ "priority": "Unknown",
+ "file": "app/requirements.txt",
+ "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
+ "tool": "gemnasium"
},
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
- "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
- "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
+ {
+ "category": "dependency_scanning",
+ "name": "nokogiri - USN-3424-1",
+ "message": "Vulnerabilities in libxml2",
+ "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
+ "severity": "Unknown",
+ "solution": "Upgrade to latest version.",
+ "scanner": {
+ "id": "gemnasium",
+ "name": "Gemnasium"
},
- {
- "type": "cve",
- "name": "CVE-2017-12794",
- "value": "CVE-2017-12794",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
- }
- ],
- "links": [
- {
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
- }
- ],
- "priority": "Unknown",
- "file": "app/requirements.txt",
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "nokogiri - USN-3424-1",
- "message": "Vulnerabilities in libxml2",
- "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
+ "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"
},
- "version": "1.8.0"
- }
+ {
+ "type": "usn",
+ "name": "USN-3424-1",
+ "value": "USN-3424-1",
+ "url": "https://usn.ubuntu.com/3424-1/"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
+ }
+ ],
+ "priority": "Unknown",
+ "file": "rails/Gemfile.lock",
+ "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
+ "tool": "gemnasium"
},
- "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"
+ {
+ "category": "dependency_scanning",
+ "name": "ffi - CVE-2018-1000201",
+ "message": "ruby-ffi DDL loading issue on Windows OS",
+ "cve": "ffi:1.9.18:CVE-2018-1000201",
+ "severity": "High",
+ "solution": "upgrade to \u003e= 1.9.24",
+ "scanner": {
+ "id": "bundler_audit",
+ "name": "bundler-audit"
},
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ],
- "priority": "Unknown",
- "file": "rails/Gemfile.lock",
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "ffi - CVE-2018-1000201",
- "message": "ruby-ffi DDL loading issue on Windows OS",
- "cve": "ffi:1.9.18:CVE-2018-1000201",
- "severity": "High",
- "solution": "upgrade to \u003e= 1.9.24",
- "scanner": {
- "id": "bundler_audit",
- "name": "bundler-audit"
- },
- "location": {
+ "location": {
+ "file": "sast-sample-rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "ffi"
+ },
+ "version": "1.9.18"
+ }
+ },
+ "identifiers": [
+ {
+ "type": "cve",
+ "name": "CVE-2018-1000201",
+ "value": "CVE-2018-1000201",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
+ }
+ ],
+ "priority": "High",
"file": "sast-sample-rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "ffi"
- },
- "version": "1.9.18"
- }
- },
- "identifiers": [
- {
- "type": "cve",
- "name": "CVE-2018-1000201",
- "value": "CVE-2018-1000201",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
- }
- ],
- "links": [
- {
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
- }
- ],
- "priority": "High",
- "file": "sast-sample-rails/Gemfile.lock",
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
- "tool": "bundler_audit"
- }
-]
+ "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
+ "tool": "bundler_audit"
+ }
+ ]
+}
diff --git a/spec/fixtures/security-reports/master/gl-license-management-report.json b/spec/fixtures/security-reports/master/gl-license-management-report.json
index fe91e4fb7ee..e0de6f58fdf 100644
--- a/spec/fixtures/security-reports/master/gl-license-management-report.json
+++ b/spec/fixtures/security-reports/master/gl-license-management-report.json
@@ -1,8 +1,20 @@
{
"licenses": [
{
- "count": 10,
+ "count": 52,
"name": "MIT"
+ },
+ {
+ "count": 3,
+ "name": "New BSD"
+ },
+ {
+ "count": 1,
+ "name": "Apache 2.0"
+ },
+ {
+ "count": 1,
+ "name": "unknown"
}
],
"dependencies": [
@@ -12,6 +24,369 @@
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
+ "name": "actioncable",
+ "url": "http://rubyonrails.org",
+ "description": "WebSocket framework for Rails.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "actionmailer",
+ "url": "http://rubyonrails.org",
+ "description": "Email composition, delivery, and receiving framework (part of Rails).",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "actionpack",
+ "url": "http://rubyonrails.org",
+ "description": "Web-flow and rendering framework putting the VC in MVC (part of Rails).",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "actionview",
+ "url": "http://rubyonrails.org",
+ "description": "Rendering framework putting the V in MVC (part of Rails).",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "activejob",
+ "url": "http://rubyonrails.org",
+ "description": "Job framework with pluggable queues.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "activemodel",
+ "url": "http://rubyonrails.org",
+ "description": "A toolkit for building modeling frameworks (part of Rails).",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "activerecord",
+ "url": "http://rubyonrails.org",
+ "description": "Object-relational mapper framework (part of Rails).",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "activesupport",
+ "url": "http://rubyonrails.org",
+ "description": "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "arel",
+ "url": "https://github.com/rails/arel",
+ "description": "Arel Really Exasperates Logicians Arel is a SQL AST manager for Ruby",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "builder",
+ "url": "http://onestepback.org",
+ "description": "Builders for MarkUp.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "bundler",
+ "url": "http://bundler.io",
+ "description": "The best way to manage your application's dependencies",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "coffee-rails",
+ "url": "https://github.com/rails/coffee-rails",
+ "description": "CoffeeScript adapter for the Rails asset pipeline.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "coffee-script",
+ "url": "http://github.com/josh/ruby-coffee-script",
+ "description": "Ruby CoffeeScript Compiler",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "coffee-script-source",
+ "url": "http://coffeescript.org",
+ "description": "The CoffeeScript Compiler",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "concurrent-ruby",
+ "url": "http://www.concurrent-ruby.com",
+ "description": "Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "crass",
+ "url": "https://github.com/rgrove/crass/",
+ "description": "CSS parser based on the CSS Syntax Level 3 spec.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "erubis",
+ "url": "http://www.kuwata-lab.com/erubis/",
+ "description": "a fast and extensible eRuby implementation which supports multi-language",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "execjs",
+ "url": "https://github.com/rails/execjs",
+ "description": "Run JavaScript code from Ruby",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "New BSD",
+ "url": "http://opensource.org/licenses/BSD-3-Clause"
+ },
+ "dependency": {
+ "name": "ffi",
+ "url": "http://wiki.github.com/ffi/ffi",
+ "description": "Ruby FFI",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "globalid",
+ "url": "http://www.rubyonrails.org",
+ "description": "Refer to any model with a URI: gid://app/class/id",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "i18n",
+ "url": "http://github.com/svenfuchs/i18n",
+ "description": "New wave Internationalization support for Ruby",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "jbuilder",
+ "url": "https://github.com/rails/jbuilder",
+ "description": "Create JSON structures via a Builder-style DSL",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "loofah",
+ "description": "",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "mail",
+ "url": "https://github.com/mikel/mail",
+ "description": "Mail provides a nice Ruby DSL for making, sending and reading emails.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "method_source",
+ "url": "http://banisterfiend.wordpress.com",
+ "description": "retrieve the sourcecode for a method",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "mini_mime",
+ "url": "https://github.com/discourse/mini_mime",
+ "description": "A lightweight mime type lookup toy",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
"name": "mini_portile2",
"url": "http://github.com/flavorjones/mini_portile",
"description": "Simplistic port-like solution for developers",
@@ -26,9 +401,37 @@
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
- "name": "mustermann",
- "url": "https://github.com/sinatra/mustermann",
- "description": "Your personal string matching expert.",
+ "name": "minitest",
+ "url": "https://github.com/seattlerb/minitest",
+ "description": "minitest provides a complete suite of testing facilities supporting TDD, BDD, mocking, and benchmarking",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "multi_json",
+ "url": "http://github.com/intridea/multi_json",
+ "description": "A common interface to multiple JSON libraries.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "nio4r",
+ "url": "https://github.com/celluloid/nio4r",
+ "description": "NIO provides a high performance selector API for monitoring IO objects",
"pathes": [
"."
]
@@ -50,6 +453,20 @@
},
{
"license": {
+ "name": "New BSD",
+ "url": "http://opensource.org/licenses/BSD-3-Clause"
+ },
+ "dependency": {
+ "name": "puma",
+ "url": "http://puma.io",
+ "description": "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
@@ -68,9 +485,147 @@
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
- "name": "rack-protection",
- "url": "http://github.com/sinatra/sinatra/tree/master/rack-protection",
- "description": "Protect against typical web attacks, works with all Rack apps, including Rails.",
+ "name": "rack-test",
+ "url": "http://github.com/brynary/rack-test",
+ "description": "Simple testing API built on Rack",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rails",
+ "url": "http://rubyonrails.org",
+ "description": "Full-stack web application framework.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rails-dom-testing",
+ "url": "https://github.com/rails/rails-dom-testing",
+ "description": "Dom and Selector assertions for Rails applications",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rails-html-sanitizer",
+ "url": "https://github.com/rails/rails-html-sanitizer",
+ "description": "This gem is responsible to sanitize HTML fragments in Rails applications.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "railties",
+ "url": "http://rubyonrails.org",
+ "description": "Tools for creating, working with, and running Rails applications.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rake",
+ "url": "https://github.com/ruby/rake",
+ "description": "Rake is a Make-like program implemented in Ruby",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rb-fsevent",
+ "url": "http://rubygems.org/gems/rb-fsevent",
+ "description": "Very simple & usable FSEvents API",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "rb-inotify",
+ "url": "https://github.com/guard/rb-inotify",
+ "description": "A Ruby wrapper for Linux inotify, using FFI",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "unknown"
+ },
+ "dependency": {
+ "name": "ruby-bundler-rails",
+ "description": "",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "sass",
+ "url": "http://sass-lang.com/",
+ "description": "A powerful but elegant CSS compiler that makes CSS fun again.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "sass-listen",
+ "url": "https://github.com/sass/listen",
+ "description": "Fork of guard/listen",
"pathes": [
"."
]
@@ -82,9 +637,9 @@
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
- "name": "redis",
- "url": "https://github.com/redis/redis-rb",
- "description": "A Ruby client library for Redis",
+ "name": "sass-rails",
+ "url": "https://github.com/rails/sass-rails",
+ "description": "Sass adapter for the Rails asset pipeline.",
"pathes": [
"."
]
@@ -96,9 +651,9 @@
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
- "name": "sinatra",
- "url": "http://www.sinatrarb.com/",
- "description": "Classy web-development dressed in a DSL",
+ "name": "sprockets",
+ "url": "https://github.com/rails/sprockets",
+ "description": "Rack-based asset packaging system",
"pathes": [
"."
]
@@ -110,9 +665,23 @@
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
- "name": "slim",
- "url": "http://slim-lang.com/",
- "description": "Slim is a template language.",
+ "name": "sprockets-rails",
+ "url": "https://github.com/rails/sprockets-rails",
+ "description": "Sprockets Rails integration",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "New BSD",
+ "url": "http://opensource.org/licenses/BSD-3-Clause"
+ },
+ "dependency": {
+ "name": "sqlite3",
+ "url": "https://github.com/sparklemotion/sqlite3-ruby",
+ "description": "This module allows Ruby programs to interface with the SQLite3 database engine (http://www.sqlite.org)",
"pathes": [
"."
]
@@ -124,9 +693,23 @@
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
- "name": "temple",
- "url": "https://github.com/judofyr/temple",
- "description": "Template compilation framework in Ruby",
+ "name": "thor",
+ "url": "http://whatisthor.com/",
+ "description": "Thor is a toolkit for building powerful command-line interfaces.",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.txt"
+ },
+ "dependency": {
+ "name": "thread_safe",
+ "url": "https://github.com/ruby-concurrency/thread_safe",
+ "description": "Thread-safe collections and utilities for Ruby",
"pathes": [
"."
]
@@ -145,6 +728,90 @@
"."
]
}
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "turbolinks",
+ "url": "https://github.com/turbolinks/turbolinks",
+ "description": "Turbolinks makes navigating your web application faster",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "turbolinks-source",
+ "url": "https://github.com/turbolinks/turbolinks-source-gem",
+ "description": "Turbolinks JavaScript assets",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "tzinfo",
+ "url": "http://tzinfo.github.io",
+ "description": "Daylight savings aware timezone library",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "uglifier",
+ "url": "http://github.com/lautis/uglifier",
+ "description": "Ruby wrapper for UglifyJS JavaScript compressor",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "websocket-driver",
+ "url": "http://github.com/faye/websocket-driver-ruby",
+ "description": "WebSocket protocol handler with pluggable I/O",
+ "pathes": [
+ "."
+ ]
+ }
+ },
+ {
+ "license": {
+ "name": "MIT",
+ "url": "http://opensource.org/licenses/mit-license"
+ },
+ "dependency": {
+ "name": "websocket-extensions",
+ "url": "https://github.com/faye/websocket-extensions-ruby",
+ "description": "Generic extension manager for WebSocket connections",
+ "pathes": [
+ "."
+ ]
+ }
}
]
}
diff --git a/spec/fixtures/security-reports/master/gl-sast-report.json b/spec/fixtures/security-reports/master/gl-sast-report.json
index a85b9be8b5f..4bef3d22f70 100644
--- a/spec/fixtures/security-reports/master/gl-sast-report.json
+++ b/spec/fixtures/security-reports/master/gl-sast-report.json
@@ -1,944 +1,947 @@
-[
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+{
+ "version": "1.2",
+ "vulnerabilities": [
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
"file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 1,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
+ "line": 1,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 47,
+ "end_line": 47,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken2"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
"file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 47,
- "end_line": 47,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken2"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 47,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
+ "line": 47,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Predictable pseudorandom number generator",
+ "message": "Predictable pseudorandom number generator",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 41,
+ "end_line": 41,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "generateSecretToken1"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-PREDICTABLE_RANDOM",
+ "value": "PREDICTABLE_RANDOM",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
+ }
+ ],
+ "priority": "Medium",
"file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 41,
- "end_line": 41,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken1"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 41,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 41,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 11,
+ "end_line": 11
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 11,
- "end_line": 11
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 11,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 11,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 12,
+ "end_line": 12
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 12,
- "end_line": 12
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 12,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 12,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 13,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 13,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Use of insecure MD2, MD4, or MD5 hash function.",
+ "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B303",
+ "value": "B303"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 14,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Pickle library appears to be in use, possible security issue.",
- "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 14,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Pickle library appears to be in use, possible security issue.",
+ "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 15,
+ "end_line": 15
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B301",
+ "value": "B301"
+ }
+ ],
+ "priority": "Medium",
"file": "python/imports/imports-aliases.py",
- "start_line": 15,
- "end_line": 15
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B301",
- "value": "B301"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 15,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "ECB mode is insecure",
- "message": "ECB mode is insecure",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
+ "line": 15,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "name": "ECB mode is insecure",
+ "message": "ECB mode is insecure",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-ECB_MODE",
+ "value": "ECB_MODE",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
+ }
+ ],
+ "priority": "Medium",
"file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-ECB_MODE",
- "value": "ECB_MODE",
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Cipher with no integrity",
- "message": "Cipher with no integrity",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "name": "Cipher with no integrity",
+ "message": "Cipher with no integrity",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-CIPHER_INTEGRITY",
+ "value": "CIPHER_INTEGRITY",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
+ }
+ ],
+ "priority": "Medium",
"file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-CIPHER_INTEGRITY",
- "value": "CIPHER_INTEGRITY",
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 29,
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
+ "tool": "find_sec_bugs"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 14,
+ "end_line": 14
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
"file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 14,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 14,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Probable insecure usage of temp file/directory.",
+ "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
+ "severity": "Medium",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-tmp.py",
+ "start_line": 10,
+ "end_line": 10
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B108",
+ "value": "B108",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
+ }
+ ],
+ "priority": "Medium",
"file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 10,
- "end_line": 10
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 10,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 10,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 1,
+ "end_line": 1
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-aliases.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 22,
+ "end_line": 22
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B106",
+ "value": "B106",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 22,
- "end_line": 22
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B106",
- "value": "B106",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 22,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'root'",
- "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 22,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'root'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 5,
+ "end_line": 5
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 5,
- "end_line": 5
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 5,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: ''",
- "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 5,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: ''",
+ "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 13,
+ "end_line": 13
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 13,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 13,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 23,
+ "end_line": 23
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 23,
- "end_line": 23
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 23,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 23,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Possible hardcoded password: 'blerg'",
+ "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
+ "severity": "Low",
+ "confidence": "Medium",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/hardcoded/hardcoded-passwords.py",
+ "start_line": 24,
+ "end_line": 24
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B105",
+ "value": "B105",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 24,
- "end_line": 24
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 24,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 24,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 4,
+ "end_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-function.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 4,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-function.py",
+ "start_line": 2,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-function.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 2,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 7,
+ "end_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-from.py",
- "start_line": 7,
- "end_line": 7
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
- "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
+ "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 9,
+ "end_line": 9
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B602",
+ "value": "B602",
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-aliases.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B602",
- "value": "B602",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 9,
+ "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with subprocess module.",
+ "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-from.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with Popen module.",
+ "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-from.py",
+ "start_line": 1,
+ "end_line": 2
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B404",
+ "value": "B404"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-from.py",
- "start_line": 1,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 1,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with pickle module.",
+ "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 7,
+ "end_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-aliases.py",
- "start_line": 7,
- "end_line": 8
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with loads module.",
- "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
+ "line": 7,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Consider possible security implications associated with loads module.",
+ "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
+ "severity": "Low",
+ "confidence": "High",
+ "scanner": {
+ "id": "bandit",
+ "name": "Bandit"
+ },
+ "location": {
+ "file": "python/imports/imports-aliases.py",
+ "start_line": 6,
+ "end_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "bandit_test_id",
+ "name": "Bandit Test ID B403",
+ "value": "B403"
+ }
+ ],
+ "priority": "Low",
"file": "python/imports/imports-aliases.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
+ "line": 6,
+ "tool": "bandit"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 4
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
"file": "c/subdir/utils.c",
- "start_line": 4
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 4,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
- "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
- "confidence": "Low",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
+ "line": 4,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
+ "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
+ "confidence": "Low",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "c/subdir/utils.c",
+ "start_line": 8
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-362",
+ "value": "362",
+ "url": "https://cwe.mitre.org/data/definitions/362.html"
+ }
+ ],
"file": "c/subdir/utils.c",
- "start_line": 8
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-362",
- "value": "362",
- "url": "https://cwe.mitre.org/data/definitions/362.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 8,
- "url": "https://cwe.mitre.org/data/definitions/362.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
+ "line": 8,
+ "url": "https://cwe.mitre.org/data/definitions/362.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
+ "confidence": "Low",
+ "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 6
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-119",
+ "value": "119",
+ "url": "https://cwe.mitre.org/data/definitions/119.html"
+ },
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
"file": "cplusplus/src/hello.cpp",
- "start_line": 6
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 6,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
- "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
- "confidence": "Low",
- "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
+ "line": 6,
+ "url": "https://cwe.mitre.org/data/definitions/119.html",
+ "tool": "flawfinder"
+ },
+ {
+ "category": "sast",
+ "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
+ "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
+ "confidence": "Low",
+ "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
+ "scanner": {
+ "id": "flawfinder",
+ "name": "Flawfinder"
+ },
+ "location": {
+ "file": "cplusplus/src/hello.cpp",
+ "start_line": 7
+ },
+ "identifiers": [
+ {
+ "type": "cwe",
+ "name": "CWE-120",
+ "value": "120",
+ "url": "https://cwe.mitre.org/data/definitions/120.html"
+ }
+ ],
"file": "cplusplus/src/hello.cpp",
- "start_line": 7
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 7,
- "url": "https://cwe.mitre.org/data/definitions/120.html",
- "tool": "flawfinder"
- }
-]
+ "line": 7,
+ "url": "https://cwe.mitre.org/data/definitions/120.html",
+ "tool": "flawfinder"
+ }
+ ]
+}
diff --git a/spec/fixtures/sentry/issues_sample_response.json b/spec/fixtures/sentry/issues_sample_response.json
new file mode 100644
index 00000000000..ed22499cfa1
--- /dev/null
+++ b/spec/fixtures/sentry/issues_sample_response.json
@@ -0,0 +1,42 @@
+[{
+ "lastSeen": "2018-12-31T12:00:11Z",
+ "numComments": 0,
+ "userCount": 0,
+ "stats": {
+ "24h": [
+ [
+ 1546437600,
+ 0
+ ]
+ ]
+ },
+ "culprit": "sentry.tasks.reports.deliver_organization_user_report",
+ "title": "gaierror: [Errno -2] Name or service not known",
+ "id": "11",
+ "assignedTo": null,
+ "logger": null,
+ "type": "error",
+ "annotations": [],
+ "metadata": {
+ "type": "gaierror",
+ "value": "[Errno -2] Name or service not known"
+ },
+ "status": "unresolved",
+ "subscriptionDetails": null,
+ "isPublic": false,
+ "hasSeen": false,
+ "shortId": "INTERNAL-4",
+ "shareId": null,
+ "firstSeen": "2018-12-17T12:00:14Z",
+ "count": "21",
+ "permalink": "35.228.54.90/sentry/internal/issues/11/",
+ "level": "error",
+ "isSubscribed": true,
+ "isBookmarked": false,
+ "project": {
+ "slug": "internal",
+ "id": "1",
+ "name": "Internal"
+ },
+ "statusDetails": {}
+ }]
diff --git a/spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap
new file mode 100644
index 00000000000..26eae2d5e61
--- /dev/null
+++ b/spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`File row header component adds multiple ellipsises after 40 characters 1`] = `
+<div
+ class="file-row-header bg-white sticky-top p-2 js-file-row-header"
+>
+ <span
+ class="bold"
+ >
+ app/assets/javascripts/…/…/diffs/notes
+ </span>
+</div>
+`;
+
+exports[`File row header component renders file path 1`] = `
+<div
+ class="file-row-header bg-white sticky-top p-2 js-file-row-header"
+>
+ <span
+ class="bold"
+ >
+ app/assets
+ </span>
+</div>
+`;
+
+exports[`File row header component trucates path after 40 characters 1`] = `
+<div
+ class="file-row-header bg-white sticky-top p-2 js-file-row-header"
+>
+ <span
+ class="bold"
+ >
+ app/assets/javascripts/merge_requests
+ </span>
+</div>
+`;
diff --git a/spec/frontend/vue_shared/components/file_row_header_spec.js b/spec/frontend/vue_shared/components/file_row_header_spec.js
new file mode 100644
index 00000000000..80f4c275dcc
--- /dev/null
+++ b/spec/frontend/vue_shared/components/file_row_header_spec.js
@@ -0,0 +1,36 @@
+import { shallowMount } from '@vue/test-utils';
+import FileRowHeader from '~/vue_shared/components/file_row_header.vue';
+
+describe('File row header component', () => {
+ let vm;
+
+ function createComponent(path) {
+ vm = shallowMount(FileRowHeader, {
+ propsData: {
+ path,
+ },
+ });
+ }
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders file path', () => {
+ createComponent('app/assets');
+
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it('trucates path after 40 characters', () => {
+ createComponent('app/assets/javascripts/merge_requests');
+
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it('adds multiple ellipsises after 40 characters', () => {
+ createComponent('app/assets/javascripts/merge_requests/widget/diffs/notes');
+
+ expect(vm.element).toMatchSnapshot();
+ });
+});
diff --git a/spec/graphql/resolvers/project_pipelines_resolver_spec.rb b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb
index 407ca2f9d78..6862ae8a5ed 100644
--- a/spec/graphql/resolvers/project_pipelines_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_pipelines_resolver_spec.rb
@@ -4,7 +4,7 @@ describe Resolvers::ProjectPipelinesResolver do
include GraphqlHelpers
set(:project) { create(:project) }
- set(:pipeline) { create(:ci_pipeline, project: project) }
+ set(:pipeline) { create(:ci_pipeline, project: project) }
set(:other_pipeline) { create(:ci_pipeline) }
let(:current_user) { create(:user) }
diff --git a/spec/graphql/types/permission_types/base_permission_type_spec.rb b/spec/graphql/types/permission_types/base_permission_type_spec.rb
index a7e51797047..0ee8b883d51 100644
--- a/spec/graphql/types/permission_types/base_permission_type_spec.rb
+++ b/spec/graphql/types/permission_types/base_permission_type_spec.rb
@@ -8,7 +8,7 @@ describe Types::PermissionTypes::BasePermissionType do
Class.new(described_class) do
graphql_name 'TestClass'
- permission_field :do_stuff, resolve: -> (_, _, _) { true }
+ permission_field :do_stuff, resolve: -> (_, _, _) { true }
ability_field(:read_issue)
abilities :admin_issue
end
diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb
index 927153adc5b..4288412eda3 100644
--- a/spec/graphql/types/permission_types/project_spec.rb
+++ b/spec/graphql/types/permission_types/project_spec.rb
@@ -5,7 +5,7 @@ describe Types::PermissionTypes::Project do
expected_permissions = [
:change_namespace, :change_visibility_level, :rename_project, :remove_project, :archive_project,
:remove_fork_project, :remove_pages, :read_project, :create_merge_request_in,
- :read_wiki, :read_project_member, :create_issue, :upload_file, :read_cycle_analytics,
+ :read_wiki, :read_project_member, :create_issue, :upload_file, :read_cycle_analytics,
:download_code, :download_wiki_code, :fork_project, :create_project_snippet,
:read_commit_status, :request_access, :create_pipeline, :create_pipeline_schedule,
:create_merge_request_from, :create_wiki, :push_code, :create_deployment, :push_to_delete_protected_branch,
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 61d4c42665a..01d71abfac9 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -10,7 +10,7 @@ describe GitlabSchema.types['Project'] do
it 'authorizes the merge request' do
expect(described_class.fields['mergeRequest'])
- .to require_graphql_authorizations(:read_merge_request)
+ .to require_graphql_authorizations(:read_merge_request)
end
end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 4135f31e051..b81249a1e29 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -168,6 +168,21 @@ describe ApplicationHelper do
end
end
+ describe '#client_class_list' do
+ it 'returns string containing CSS classes representing client browser and platform' do
+ class_list = helper.client_class_list
+ expect(class_list).to eq('gl-browser-generic gl-platform-other')
+ end
+ end
+
+ describe '#client_js_flags' do
+ it 'returns map containing JS flags representing client browser and platform' do
+ flags_list = helper.client_js_flags
+ expect(flags_list[:isGeneric]).to eq(true)
+ expect(flags_list[:isOther]).to eq(true)
+ end
+ end
+
describe '#autocomplete_data_sources' do
let(:project) { create(:project) }
let(:noteable_type) { Issue }
diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb
new file mode 100644
index 00000000000..705523f1110
--- /dev/null
+++ b/spec/helpers/application_settings_helper_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ApplicationSettingsHelper do
+ context 'when all protocols in use' do
+ before do
+ stub_application_setting(enabled_git_access_protocol: '')
+ end
+
+ it { expect(all_protocols_enabled?).to be_truthy }
+ it { expect(http_enabled?).to be_truthy }
+ it { expect(ssh_enabled?).to be_truthy }
+ end
+
+ context 'when SSH is only in use' do
+ before do
+ stub_application_setting(enabled_git_access_protocol: 'ssh')
+ end
+
+ it { expect(all_protocols_enabled?).to be_falsey }
+ it { expect(http_enabled?).to be_falsey }
+ it { expect(ssh_enabled?).to be_truthy }
+ end
+
+ shared_examples 'when HTTP protocol is in use' do |protocol|
+ before do
+ allow(Gitlab.config.gitlab).to receive(:protocol).and_return(protocol)
+ stub_application_setting(enabled_git_access_protocol: 'http')
+ end
+
+ it { expect(all_protocols_enabled?).to be_falsey }
+ it { expect(http_enabled?).to be_truthy }
+ it { expect(ssh_enabled?).to be_falsey }
+ end
+
+ it_behaves_like 'when HTTP protocol is in use', 'https'
+ it_behaves_like 'when HTTP protocol is in use', 'http'
+end
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
index 9c0e55739d6..824b3ab4fc1 100644
--- a/spec/helpers/commits_helper_spec.rb
+++ b/spec/helpers/commits_helper_spec.rb
@@ -21,7 +21,7 @@ describe CommitsHelper do
expect(helper.commit_author_link(commit))
.to include('Foo &lt;script&gt;')
expect(helper.commit_author_link(commit, avatar: true))
- .to include('commit-author-name', 'Foo &lt;script&gt;')
+ .to include('commit-author-name', 'js-user-link', 'Foo &lt;script&gt;')
end
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 81231cca085..412ec910f3a 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -5,8 +5,8 @@ describe IssuablesHelper do
let(:label2) { build_stubbed(:label) }
describe '#users_dropdown_label' do
- let(:user) { build_stubbed(:user) }
- let(:user2) { build_stubbed(:user) }
+ let(:user) { build_stubbed(:user) }
+ let(:user2) { build_stubbed(:user) }
it 'returns unassigned' do
expect(users_dropdown_label([])).to eq('Unassigned')
@@ -22,7 +22,7 @@ describe IssuablesHelper do
end
describe '#group_dropdown_label' do
- let(:group) { create(:group) }
+ let(:group) { create(:group) }
let(:default) { 'default label' }
it 'returns default group label when group_id is nil' do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 8bb2e234e9a..039143eb8d7 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -148,7 +148,7 @@ describe IssuesHelper do
end
describe "when passing a discussion" do
- let(:diff_note) { create(:diff_note_on_merge_request) }
+ let(:diff_note) { create(:diff_note_on_merge_request) }
let(:merge_request) { diff_note.noteable }
let(:discussion) { diff_note.to_discussion }
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 21461e46cf4..0715f34dafe 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -185,8 +185,8 @@ describe NotesHelper do
context 'for a non-diff discussion' do
let(:discussion) { create(:discussion_note_on_commit, project: project).to_discussion }
- it 'returns the commit path' do
- expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit))
+ it 'returns the commit path with the note anchor' do
+ expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: "note_#{discussion.first_note.id}"))
end
end
end
diff --git a/spec/helpers/projects/error_tracking_helper_spec.rb b/spec/helpers/projects/error_tracking_helper_spec.rb
new file mode 100644
index 00000000000..7516a636c93
--- /dev/null
+++ b/spec/helpers/projects/error_tracking_helper_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::ErrorTrackingHelper do
+ include Gitlab::Routing.url_helpers
+
+ set(:project) { create(:project) }
+
+ describe '#error_tracking_data' do
+ let(:setting_path) { project_settings_operations_path(project) }
+
+ let(:index_path) do
+ project_error_tracking_index_path(project, format: :json)
+ end
+
+ context 'without error_tracking_setting' do
+ it 'returns frontend configuration' do
+ expect(error_tracking_data(project)).to eq(
+ 'index-path' => index_path,
+ 'enable-error-tracking-link' => setting_path,
+ 'error-tracking-enabled' => 'false',
+ "illustration-path" => "/images/illustrations/cluster_popover.svg"
+ )
+ end
+ end
+
+ context 'with error_tracking_setting' do
+ let(:error_tracking_setting) do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ context 'when enabled' do
+ before do
+ error_tracking_setting.update!(enabled: true)
+ end
+
+ it 'show error tracking enabled' do
+ expect(error_tracking_data(project)).to include(
+ 'error-tracking-enabled' => 'true'
+ )
+ end
+ end
+
+ context 'when disabled' do
+ before do
+ error_tracking_setting.update!(enabled: false)
+ end
+
+ it 'show error tracking not enabled' do
+ expect(error_tracking_data(project)).to include(
+ 'error-tracking-enabled' => 'false'
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index edd680ee1d1..88b5d87f087 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -629,4 +629,103 @@ describe ProjectsHelper do
end
end
end
+
+ describe '#show_auto_devops_implicitly_enabled_banner?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:user) { create(:user) }
+
+ let(:feature_visibilities) do
+ {
+ enabled: ProjectFeature::ENABLED,
+ disabled: ProjectFeature::DISABLED
+ }
+ end
+
+ where(:global_setting, :project_setting, :builds_visibility, :gitlab_ci_yml, :user_access, :result) do
+ # With ADO implicitly enabled scenarios
+ true | nil | :disabled | true | :developer | false
+ true | nil | :disabled | true | :maintainer | false
+ true | nil | :disabled | true | :owner | false
+
+ true | nil | :disabled | false | :developer | false
+ true | nil | :disabled | false | :maintainer | false
+ true | nil | :disabled | false | :owner | false
+
+ true | nil | :enabled | true | :developer | false
+ true | nil | :enabled | true | :maintainer | false
+ true | nil | :enabled | true | :owner | false
+
+ true | nil | :enabled | false | :developer | false
+ true | nil | :enabled | false | :maintainer | true
+ true | nil | :enabled | false | :owner | true
+
+ # With ADO enabled scenarios
+ true | true | :disabled | true | :developer | false
+ true | true | :disabled | true | :maintainer | false
+ true | true | :disabled | true | :owner | false
+
+ true | true | :disabled | false | :developer | false
+ true | true | :disabled | false | :maintainer | false
+ true | true | :disabled | false | :owner | false
+
+ true | true | :enabled | true | :developer | false
+ true | true | :enabled | true | :maintainer | false
+ true | true | :enabled | true | :owner | false
+
+ true | true | :enabled | false | :developer | false
+ true | true | :enabled | false | :maintainer | false
+ true | true | :enabled | false | :owner | false
+
+ # With ADO disabled scenarios
+ true | false | :disabled | true | :developer | false
+ true | false | :disabled | true | :maintainer | false
+ true | false | :disabled | true | :owner | false
+
+ true | false | :disabled | false | :developer | false
+ true | false | :disabled | false | :maintainer | false
+ true | false | :disabled | false | :owner | false
+
+ true | false | :enabled | true | :developer | false
+ true | false | :enabled | true | :maintainer | false
+ true | false | :enabled | true | :owner | false
+
+ true | false | :enabled | false | :developer | false
+ true | false | :enabled | false | :maintainer | false
+ true | false | :enabled | false | :owner | false
+ end
+
+ def grant_user_access(project, user, access)
+ case access
+ when :developer, :maintainer
+ project.add_user(user, access)
+ when :owner
+ project.namespace.update(owner: user)
+ end
+ end
+
+ with_them do
+ let(:project) do
+ if project_setting.nil?
+ create(:project, :repository)
+ else
+ create(:project, :repository, :auto_devops)
+ end
+ end
+
+ before do
+ stub_application_setting(auto_devops_enabled: global_setting)
+
+ allow_any_instance_of(Repository).to receive(:gitlab_ci_yml).and_return(gitlab_ci_yml)
+
+ grant_user_access(project, user, user_access)
+ project.project_feature.update_attribute(:builds_access_level, feature_visibilities[builds_visibility])
+ project.auto_devops.update_attribute(:enabled, project_setting) unless project_setting.nil?
+ end
+
+ subject { helper.show_auto_devops_implicitly_enabled_banner?(project, user) }
+
+ it { is_expected.to eq(result) }
+ end
+ end
end
diff --git a/spec/helpers/runners_helper_spec.rb b/spec/helpers/runners_helper_spec.rb
index a4a483e68a8..bf00841fcb6 100644
--- a/spec/helpers/runners_helper_spec.rb
+++ b/spec/helpers/runners_helper_spec.rb
@@ -15,4 +15,40 @@ describe RunnersHelper do
runner = FactoryBot.build(:ci_runner, contacted_at: 1.second.ago, active: true)
expect(runner_status_icon(runner)).to include("Runner is online")
end
+
+ describe '#runner_contacted_at' do
+ let(:contacted_at_stored) { 1.hour.ago.change(usec: 0) }
+ let(:contacted_at_cached) { 1.second.ago.change(usec: 0) }
+ let(:runner) { create(:ci_runner, contacted_at: contacted_at_stored) }
+
+ before do
+ runner.cache_attributes(contacted_at: contacted_at_cached)
+ end
+
+ context 'without sorting' do
+ it 'returns cached value' do
+ expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
+ end
+ end
+
+ context 'with sorting set to created_date' do
+ before do
+ controller.params[:sort] = 'created_date'
+ end
+
+ it 'returns cached value' do
+ expect(runner_contacted_at(runner)).to eq(contacted_at_cached)
+ end
+ end
+
+ context 'with sorting set to contacted_asc' do
+ before do
+ controller.params[:sort] = 'contacted_asc'
+ end
+
+ it 'returns stored value' do
+ expect(runner_contacted_at(runner)).to eq(contacted_at_stored)
+ end
+ end
+ end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 4945749f524..9cff0291250 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -18,7 +18,7 @@ describe SearchHelper do
end
context "with a standard user" do
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
before do
allow(self).to receive(:current_user).and_return(user)
diff --git a/spec/helpers/user_callouts_helper_spec.rb b/spec/helpers/user_callouts_helper_spec.rb
index 27455705d23..8fa479a4474 100644
--- a/spec/helpers/user_callouts_helper_spec.rb
+++ b/spec/helpers/user_callouts_helper_spec.rb
@@ -44,4 +44,13 @@ describe UserCalloutsHelper do
it { is_expected.to be false }
end
end
+
+ describe '.render_flash_user_callout' do
+ it 'renders the flash_user_callout partial' do
+ expect(helper).to receive(:render)
+ .with(/flash_user_callout/, flash_type: :warning, message: 'foo', feature_name: 'bar')
+
+ helper.render_flash_user_callout(:warning, 'foo', 'bar')
+ end
+ end
end
diff --git a/spec/javascripts/behaviors/copy_as_gfm_spec.js b/spec/javascripts/behaviors/copy_as_gfm_spec.js
index cf8c1b77861..6179a02ce16 100644
--- a/spec/javascripts/behaviors/copy_as_gfm_spec.js
+++ b/spec/javascripts/behaviors/copy_as_gfm_spec.js
@@ -87,7 +87,7 @@ describe('CopyAsGFM', () => {
spyOn(window, 'getSelection').and.returnValue(selection);
simulateCopy();
- const expectedGFM = '- List Item1\n- List Item2';
+ const expectedGFM = '* List Item1\n\n* List Item2';
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
});
@@ -97,7 +97,7 @@ describe('CopyAsGFM', () => {
spyOn(window, 'getSelection').and.returnValue(selection);
simulateCopy();
- const expectedGFM = '1. List Item1\n1. List Item2';
+ const expectedGFM = '1. List Item1\n\n1. List Item2';
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
});
diff --git a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
index b709b937180..fe827bb1e18 100644
--- a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -186,7 +186,7 @@ describe('ShortcutsIssuable', function() {
it('adds the quoted selection to the input', () => {
ShortcutsIssuable.replyWithSelectedText(true);
- expect($(FORM_SELECTOR).val()).toBe('> _Selected text._\n\n');
+ expect($(FORM_SELECTOR).val()).toBe('> *Selected text.*\n\n');
});
it('triggers `focus`', () => {
diff --git a/spec/javascripts/blob_edit/blob_bundle_spec.js b/spec/javascripts/blob_edit/blob_bundle_spec.js
index 57f60a4a3dd..48af0148e3f 100644
--- a/spec/javascripts/blob_edit/blob_bundle_spec.js
+++ b/spec/javascripts/blob_edit/blob_bundle_spec.js
@@ -1,18 +1,11 @@
import blobBundle from '~/blob_edit/blob_bundle';
import $ from 'jquery';
-window.ace = {
- config: {
- set: () => {},
- loadModule: () => {},
- },
- edit: () => ({ focus: () => {} }),
-};
-
-describe('EditBlob', () => {
+describe('BlobBundle', () => {
beforeEach(() => {
+ spyOnDependency(blobBundle, 'EditBlob').and.stub();
setFixtures(`
- <div class="js-edit-blob-form">
+ <div class="js-edit-blob-form" data-blob-filename="blah">
<button class="js-commit-button"></button>
<a class="btn btn-cancel" href="#"></a>
</div>`);
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index 1ca55549094..dfce2656e4c 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -62,6 +62,7 @@ describe('Clusters Store', () => {
ingressHelpPath: null,
status: mockResponseData.status,
statusReason: mockResponseData.status_reason,
+ rbac: false,
applications: {
helm: {
title: 'Helm Tiller',
diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js
index 8b2ca6506c4..50e45f48af3 100644
--- a/spec/javascripts/diffs/components/commit_item_spec.js
+++ b/spec/javascripts/diffs/components/commit_item_spec.js
@@ -80,6 +80,8 @@ describe('diffs/components/commit_item', () => {
expect(trimText(committerElement.textContent)).toEqual(expectedText);
expect(nameElement).toHaveAttr('href', commit.author.web_url);
expect(nameElement).toHaveText(commit.author.name);
+ expect(nameElement).toHaveClass('js-user-link');
+ expect(nameElement.dataset.userId).toEqual(commit.author.id.toString());
});
describe('without commit description', () => {
diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
index 7237274eb43..53b9ac22fc0 100644
--- a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
@@ -1 +1,34 @@
// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import CompareVersionsDropdown from '~/diffs/components/compare_versions_dropdown.vue';
+import diffsMockData from '../mock_data/merge_request_diffs';
+
+describe('CompareVersionsDropdown', () => {
+ let wrapper;
+ const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
+
+ const factory = (options = {}) => {
+ const localVue = createLocalVue();
+
+ wrapper = shallowMount(CompareVersionsDropdown, { localVue, ...options });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render a correct base version link', () => {
+ factory({
+ propsData: {
+ baseVersionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ otherVersions: diffsMockData.slice(1),
+ targetBranch,
+ },
+ });
+
+ const links = wrapper.findAll('a');
+ const lastLink = links.wrappers[links.length - 1];
+
+ expect(lastLink.attributes('href')).toEqual(wrapper.props('baseVersionPath'));
+ });
+});
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index 75c66e9ca82..2f0385454d7 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -22,10 +22,10 @@ describe('CompareVersions', () => {
const treeListBtn = vm.$el.querySelector('.js-toggle-tree-list');
expect(treeListBtn).not.toBeNull();
- expect(treeListBtn.dataset.originalTitle).toBe('Toggle file browser');
+ expect(treeListBtn.dataset.originalTitle).toBe('Hide file browser');
expect(treeListBtn.querySelectorAll('svg use').length).not.toBe(0);
expect(treeListBtn.querySelector('svg use').getAttribute('xlink:href')).toContain(
- '#hamburger',
+ '#collapse-left',
);
});
@@ -51,15 +51,6 @@ describe('CompareVersions', () => {
});
});
- it('should render whitespace toggle button with correct attributes', () => {
- const whitespaceBtn = vm.$el.querySelector('.qa-toggle-whitespace');
- const href = vm.toggleWhitespacePath;
-
- expect(whitespaceBtn).not.toBeNull();
- expect(whitespaceBtn.getAttribute('href')).toEqual(href);
- expect(whitespaceBtn.innerHTML).toContain('Hide whitespace changes');
- });
-
it('should render view types buttons with correct values', () => {
const inlineBtn = vm.$el.querySelector('#inline-diff-btn');
const parallelBtn = vm.$el.querySelector('#parallel-diff-btn');
@@ -100,27 +91,9 @@ describe('CompareVersions', () => {
});
});
- describe('isWhitespaceVisible', () => {
- const originalHref = window.location.href;
-
- afterEach(() => {
- window.history.replaceState({}, null, originalHref);
- });
-
- it('should return "true" when no "w" flag is present in the URL (default)', () => {
- expect(vm.isWhitespaceVisible()).toBe(true);
- });
-
- it('should return "false" when the flag is set to "1" in the URL', () => {
- window.history.replaceState({}, null, '?w=1');
-
- expect(vm.isWhitespaceVisible()).toBe(false);
- });
-
- it('should return "true" when the flag is set to "0" in the URL', () => {
- window.history.replaceState({}, null, '?w=0');
-
- expect(vm.isWhitespaceVisible()).toBe(true);
+ describe('baseVersionPath', () => {
+ it('should be set correctly from mergeRequestDiff', () => {
+ expect(vm.baseVersionPath).toEqual(vm.mergeRequestDiff.base_version_path);
});
});
diff --git a/spec/javascripts/diffs/components/settings_dropdown_spec.js b/spec/javascripts/diffs/components/settings_dropdown_spec.js
new file mode 100644
index 00000000000..5031846cff0
--- /dev/null
+++ b/spec/javascripts/diffs/components/settings_dropdown_spec.js
@@ -0,0 +1,167 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import diffModule from '~/diffs/store/modules';
+import SettingsDropdown from '~/diffs/components/settings_dropdown.vue';
+import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
+
+describe('Diff settiings dropdown component', () => {
+ let vm;
+ let actions;
+
+ function createComponent(extendStore = () => {}) {
+ const localVue = createLocalVue();
+
+ localVue.use(Vuex);
+
+ const store = new Vuex.Store({
+ modules: {
+ diffs: {
+ namespaced: true,
+ actions,
+ state: diffModule().state,
+ getters: diffModule().getters,
+ },
+ },
+ });
+
+ extendStore(store);
+
+ vm = mount(SettingsDropdown, {
+ localVue,
+ store,
+ });
+ }
+
+ beforeEach(() => {
+ actions = {
+ setInlineDiffViewType: jasmine.createSpy('setInlineDiffViewType'),
+ setParallelDiffViewType: jasmine.createSpy('setParallelDiffViewType'),
+ setRenderTreeList: jasmine.createSpy('setRenderTreeList'),
+ setShowWhitespace: jasmine.createSpy('setShowWhitespace'),
+ };
+ });
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ describe('tree view buttons', () => {
+ it('list view button dispatches setRenderTreeList with false', () => {
+ createComponent();
+
+ vm.find('.js-list-view').trigger('click');
+
+ expect(actions.setRenderTreeList).toHaveBeenCalledWith(jasmine.anything(), false, undefined);
+ });
+
+ it('tree view button dispatches setRenderTreeList with true', () => {
+ createComponent();
+
+ vm.find('.js-tree-view').trigger('click');
+
+ expect(actions.setRenderTreeList).toHaveBeenCalledWith(jasmine.anything(), true, undefined);
+ });
+
+ it('sets list button as active when renderTreeList is false', () => {
+ createComponent(store => {
+ Object.assign(store.state.diffs, {
+ renderTreeList: false,
+ });
+ });
+
+ expect(vm.find('.js-list-view').classes('active')).toBe(true);
+ expect(vm.find('.js-tree-view').classes('active')).toBe(false);
+ });
+
+ it('sets tree button as active when renderTreeList is true', () => {
+ createComponent(store => {
+ Object.assign(store.state.diffs, {
+ renderTreeList: true,
+ });
+ });
+
+ expect(vm.find('.js-list-view').classes('active')).toBe(false);
+ expect(vm.find('.js-tree-view').classes('active')).toBe(true);
+ });
+ });
+
+ describe('compare changes', () => {
+ it('sets inline button as active', () => {
+ createComponent(store => {
+ Object.assign(store.state.diffs, {
+ diffViewType: INLINE_DIFF_VIEW_TYPE,
+ });
+ });
+
+ expect(vm.find('.js-inline-diff-button').classes('active')).toBe(true);
+ expect(vm.find('.js-parallel-diff-button').classes('active')).toBe(false);
+ });
+
+ it('sets parallel button as active', () => {
+ createComponent(store => {
+ Object.assign(store.state.diffs, {
+ diffViewType: PARALLEL_DIFF_VIEW_TYPE,
+ });
+ });
+
+ expect(vm.find('.js-inline-diff-button').classes('active')).toBe(false);
+ expect(vm.find('.js-parallel-diff-button').classes('active')).toBe(true);
+ });
+
+ it('calls setInlineDiffViewType when clicking inline button', () => {
+ createComponent();
+
+ vm.find('.js-inline-diff-button').trigger('click');
+
+ expect(actions.setInlineDiffViewType).toHaveBeenCalled();
+ });
+
+ it('calls setParallelDiffViewType when clicking parallel button', () => {
+ createComponent();
+
+ vm.find('.js-parallel-diff-button').trigger('click');
+
+ expect(actions.setParallelDiffViewType).toHaveBeenCalled();
+ });
+ });
+
+ describe('whitespace toggle', () => {
+ it('does not set as checked when showWhitespace is false', () => {
+ createComponent(store => {
+ Object.assign(store.state.diffs, {
+ showWhitespace: false,
+ });
+ });
+
+ expect(vm.find('#show-whitespace').element.checked).toBe(false);
+ });
+
+ it('sets as checked when showWhitespace is true', () => {
+ createComponent(store => {
+ Object.assign(store.state.diffs, {
+ showWhitespace: true,
+ });
+ });
+
+ expect(vm.find('#show-whitespace').element.checked).toBe(true);
+ });
+
+ it('calls setShowWhitespace on change', () => {
+ createComponent();
+
+ const checkbox = vm.find('#show-whitespace');
+
+ checkbox.element.checked = true;
+ checkbox.trigger('change');
+
+ expect(actions.setShowWhitespace).toHaveBeenCalledWith(
+ jasmine.anything(),
+ {
+ showWhitespace: true,
+ pushState: true,
+ },
+ undefined,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js
index a0b380adfd6..08b0b4f9e45 100644
--- a/spec/javascripts/diffs/components/tree_list_spec.js
+++ b/spec/javascripts/diffs/components/tree_list_spec.js
@@ -26,6 +26,8 @@ describe('Diffs tree list component', () => {
store.state.diffs.removedLines = 20;
store.state.diffs.diffFiles.push('test');
+ localStorage.removeItem('mr_diff_tree_list');
+
vm = mountComponentWithStore(Component, { store });
});
@@ -57,6 +59,7 @@ describe('Diffs tree list component', () => {
removedLines: 0,
tempFile: true,
type: 'blob',
+ parentPath: 'app',
},
app: {
key: 'app',
@@ -108,7 +111,7 @@ describe('Diffs tree list component', () => {
});
it('renders as file list when renderTreeList is false', done => {
- vm.renderTreeList = false;
+ vm.$store.state.diffs.renderTreeList = false;
vm.$nextTick(() => {
expect(vm.$el.querySelectorAll('.file-row').length).toBe(1);
@@ -118,42 +121,14 @@ describe('Diffs tree list component', () => {
});
it('renders file paths when renderTreeList is false', done => {
- vm.renderTreeList = false;
+ vm.$store.state.diffs.renderTreeList = false;
vm.$nextTick(() => {
- expect(vm.$el.querySelector('.file-row').textContent).toContain('app/index.js');
+ expect(vm.$el.querySelector('.file-row').textContent).toContain('index.js');
done();
});
});
-
- it('hides render buttons when input is focused', done => {
- const focusEvent = new Event('focus');
-
- vm.$el.querySelector('.form-control').dispatchEvent(focusEvent);
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.tree-list-view-toggle').style.display).toBe('none');
-
- done();
- });
- });
-
- it('shows render buttons when input is blurred', done => {
- const blurEvent = new Event('blur');
- vm.focusSearch = true;
-
- vm.$nextTick()
- .then(() => {
- vm.$el.querySelector('.form-control').dispatchEvent(blurEvent);
- })
- .then(vm.$nextTick)
- .then(() => {
- expect(vm.$el.querySelector('.tree-list-view-toggle').style.display).not.toBe('none');
- })
- .then(done)
- .catch(done.fail);
- });
});
describe('clearSearch', () => {
@@ -165,24 +140,4 @@ describe('Diffs tree list component', () => {
expect(vm.search).toBe('');
});
});
-
- describe('toggleRenderTreeList', () => {
- it('updates renderTreeList', () => {
- expect(vm.renderTreeList).toBe(true);
-
- vm.toggleRenderTreeList(false);
-
- expect(vm.renderTreeList).toBe(false);
- });
- });
-
- describe('toggleFocusSearch', () => {
- it('updates focusSearch', () => {
- expect(vm.focusSearch).toBe(false);
-
- vm.toggleFocusSearch(true);
-
- expect(vm.focusSearch).toBe(true);
- });
- });
});
diff --git a/spec/javascripts/diffs/mock_data/merge_request_diffs.js b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
index d72ad7818dd..4bbef146336 100644
--- a/spec/javascripts/diffs/mock_data/merge_request_diffs.js
+++ b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
@@ -1,42 +1,46 @@
export default [
{
- versionIndex: 4,
- createdAt: '2018-10-23T11:49:16.611Z',
- commitsCount: 4,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ version_index: 4,
+ created_at: '2018-10-23T11:49:16.611Z',
+ commits_count: 4,
latest: true,
- shortCommitSha: 'de7a8f7f',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
- comparePath:
+ short_commit_sha: 'de7a8f7f',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=de7a8f7f20c3ea2e0bef3ba01cfd41c21f6b4995',
},
{
- versionIndex: 3,
- createdAt: '2018-10-23T11:46:40.617Z',
- commitsCount: 3,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
+ version_index: 3,
+ created_at: '2018-10-23T11:46:40.617Z',
+ commits_count: 3,
latest: false,
- shortCommitSha: 'e78fc18f',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
- comparePath:
+ short_commit_sha: 'e78fc18f',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=e78fc18fa37acb2185c59ca94d4a964464feb50e',
},
{
- versionIndex: 2,
- createdAt: '2018-10-04T09:57:39.648Z',
- commitsCount: 2,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
+ version_index: 2,
+ created_at: '2018-10-04T09:57:39.648Z',
+ commits_count: 2,
latest: false,
- shortCommitSha: '48da7e7e',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
- comparePath:
+ short_commit_sha: '48da7e7e',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=48da7e7e9a99d41c852578bd9cb541ca4d864b3e',
},
{
- versionIndex: 1,
- createdAt: '2018-09-25T20:30:39.493Z',
- commitsCount: 1,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
+ version_index: 1,
+ created_at: '2018-09-25T20:30:39.493Z',
+ commits_count: 1,
latest: false,
- shortCommitSha: '47bac2ed',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
- comparePath:
+ short_commit_sha: '47bac2ed',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=47bac2ed972c5bee344c1cea159a22cd7f711dc0',
},
];
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index 033b5e86dbe..b53ae4cecfd 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -27,6 +27,8 @@ import actions, {
scrollToFile,
toggleShowTreeList,
renderFileForDiscussionId,
+ setRenderTreeList,
+ setShowWhitespace,
} from '~/diffs/store/actions';
import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
@@ -796,4 +798,55 @@ describe('DiffsStoreActions', () => {
expect(scrollToElement).not.toHaveBeenCalled();
});
});
+
+ describe('setRenderTreeList', () => {
+ it('commits SET_RENDER_TREE_LIST', done => {
+ testAction(
+ setRenderTreeList,
+ true,
+ {},
+ [{ type: types.SET_RENDER_TREE_LIST, payload: true }],
+ [],
+ done,
+ );
+ });
+
+ it('sets localStorage', () => {
+ spyOn(localStorage, 'setItem').and.stub();
+
+ setRenderTreeList({ commit() {} }, true);
+
+ expect(localStorage.setItem).toHaveBeenCalledWith('mr_diff_tree_list', true);
+ });
+ });
+
+ describe('setShowWhitespace', () => {
+ it('commits SET_SHOW_WHITESPACE', done => {
+ testAction(
+ setShowWhitespace,
+ { showWhitespace: true },
+ {},
+ [{ type: types.SET_SHOW_WHITESPACE, payload: true }],
+ [],
+ done,
+ );
+ });
+
+ it('sets localStorage', () => {
+ spyOn(localStorage, 'setItem').and.stub();
+
+ setShowWhitespace({ commit() {} }, { showWhitespace: true });
+
+ expect(localStorage.setItem).toHaveBeenCalledWith('mr_show_whitespace', true);
+ });
+
+ it('calls history pushState', () => {
+ spyOn(localStorage, 'setItem').and.stub();
+ spyOn(window.history, 'pushState').and.stub();
+
+ setShowWhitespace({ commit() {} }, { showWhitespace: true, pushState: true });
+
+ expect(window.history.pushState).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index 582535e0a53..190ca1230ca 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -230,15 +230,30 @@ describe('Diffs Module Getters', () => {
localState.treeEntries = {
file: {
type: 'blob',
+ path: 'file',
+ parentPath: '/',
+ tree: [],
},
tree: {
type: 'tree',
+ path: 'tree',
+ parentPath: '/',
+ tree: [],
},
};
expect(getters.allBlobs(localState)).toEqual([
{
- type: 'blob',
+ isHeader: true,
+ path: '/',
+ tree: [
+ {
+ parentPath: '/',
+ path: 'file',
+ tree: [],
+ type: 'blob',
+ },
+ ],
},
]);
});
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index d8733941181..a6f3f9b9dc3 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -628,4 +628,50 @@ describe('DiffsStoreMutations', () => {
expect(file.parallel_diff_lines[1].right.hasForm).toBe(false);
});
});
+
+ describe('SET_TREE_DATA', () => {
+ it('sets treeEntries and tree in state', () => {
+ const state = {
+ treeEntries: {},
+ tree: [],
+ };
+
+ mutations[types.SET_TREE_DATA](state, {
+ treeEntries: { file: { name: 'index.js' } },
+ tree: ['tree'],
+ });
+
+ expect(state.treeEntries).toEqual({
+ file: {
+ name: 'index.js',
+ },
+ });
+
+ expect(state.tree).toEqual(['tree']);
+ });
+ });
+
+ describe('SET_RENDER_TREE_LIST', () => {
+ it('sets renderTreeList', () => {
+ const state = {
+ renderTreeList: true,
+ };
+
+ mutations[types.SET_RENDER_TREE_LIST](state, false);
+
+ expect(state.renderTreeList).toBe(false);
+ });
+ });
+
+ describe('SET_SHOW_WHITESPACE', () => {
+ it('sets showWhitespace', () => {
+ const state = {
+ showWhitespace: true,
+ };
+
+ mutations[types.SET_SHOW_WHITESPACE](state, false);
+
+ expect(state.showWhitespace).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 4268634d302..c5e413a29d8 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -251,45 +251,40 @@ describe('DiffsStoreUtils', () => {
describe('trimFirstCharOfLineContent', () => {
it('trims the line when it starts with a space', () => {
expect(utils.trimFirstCharOfLineContent({ rich_text: ' diff' })).toEqual({
- discussions: [],
rich_text: 'diff',
});
});
it('trims the line when it starts with a +', () => {
expect(utils.trimFirstCharOfLineContent({ rich_text: '+diff' })).toEqual({
- discussions: [],
rich_text: 'diff',
});
});
it('trims the line when it starts with a -', () => {
expect(utils.trimFirstCharOfLineContent({ rich_text: '-diff' })).toEqual({
- discussions: [],
rich_text: 'diff',
});
});
it('does not trims the line when it starts with a letter', () => {
expect(utils.trimFirstCharOfLineContent({ rich_text: 'diff' })).toEqual({
- discussions: [],
rich_text: 'diff',
});
});
it('does not modify the provided object', () => {
const lineObj = {
- discussions: [],
rich_text: ' diff',
};
utils.trimFirstCharOfLineContent(lineObj);
- expect(lineObj).toEqual({ discussions: [], rich_text: ' diff' });
+ expect(lineObj).toEqual({ rich_text: ' diff' });
});
it('handles a undefined or null parameter', () => {
- expect(utils.trimFirstCharOfLineContent()).toEqual({ discussions: [] });
+ expect(utils.trimFirstCharOfLineContent()).toEqual({});
});
});
@@ -502,6 +497,7 @@ describe('DiffsStoreUtils', () => {
fileHash: 'test',
key: 'app/index.js',
name: 'index.js',
+ parentPath: 'app/',
path: 'app/index.js',
removedLines: 10,
tempFile: false,
@@ -522,6 +518,7 @@ describe('DiffsStoreUtils', () => {
fileHash: 'test',
key: 'app/test/index.js',
name: 'index.js',
+ parentPath: 'app/test/',
path: 'app/test/index.js',
removedLines: 0,
tempFile: true,
@@ -535,6 +532,7 @@ describe('DiffsStoreUtils', () => {
fileHash: 'test',
key: 'app/test/filepathneedstruncating.js',
name: 'filepathneedstruncating.js',
+ parentPath: 'app/test/',
path: 'app/test/filepathneedstruncating.js',
removedLines: 0,
tempFile: true,
@@ -548,6 +546,7 @@ describe('DiffsStoreUtils', () => {
},
{
key: 'package.json',
+ parentPath: '/',
path: 'package.json',
name: 'package.json',
type: 'blob',
@@ -597,4 +596,175 @@ describe('DiffsStoreUtils', () => {
expect(utils.getDiffMode({})).toBe('replaced');
});
});
+
+ describe('getLowestSingleFolder', () => {
+ it('returns path and tree of lowest single folder tree', () => {
+ const folder = {
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ name: 'javascripts',
+ type: 'tree',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ },
+ ],
+ },
+ ],
+ };
+ const { path, treeAcc } = utils.getLowestSingleFolder(folder);
+
+ expect(path).toEqual('app/javascripts');
+ expect(treeAcc).toEqual([
+ {
+ type: 'blob',
+ name: 'index.js',
+ },
+ ]);
+ });
+
+ it('returns passed in folders path & tree when more than tree exists', () => {
+ const folder = {
+ name: 'app',
+ type: 'tree',
+ tree: [
+ {
+ name: 'spec',
+ type: 'blob',
+ tree: [],
+ },
+ ],
+ };
+ const { path, treeAcc } = utils.getLowestSingleFolder(folder);
+
+ expect(path).toEqual('app');
+ expect(treeAcc).toBeNull();
+ });
+ });
+
+ describe('flattenTree', () => {
+ it('returns flattened directory structure', () => {
+ const tree = [
+ {
+ type: 'tree',
+ name: 'app',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ tree: [],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'ee',
+ tree: [
+ {
+ type: 'tree',
+ name: 'lib',
+ tree: [
+ {
+ type: 'tree',
+ name: 'ee',
+ tree: [
+ {
+ type: 'tree',
+ name: 'gitlab',
+ tree: [
+ {
+ type: 'tree',
+ name: 'checks',
+ tree: [
+ {
+ type: 'tree',
+ name: 'longtreenametomakepath',
+ tree: [
+ {
+ type: 'blob',
+ name: 'diff_check.rb',
+ tree: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'spec',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [],
+ },
+ {
+ type: 'blob',
+ name: 'index_spec.js',
+ tree: [],
+ },
+ ],
+ },
+ ];
+ const flattened = utils.flattenTree(tree);
+
+ expect(flattened).toEqual([
+ {
+ type: 'tree',
+ name: 'app/javascripts',
+ tree: [
+ {
+ type: 'blob',
+ name: 'index.js',
+ tree: [],
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'ee/lib/…/…/…/longtreenametomakepath',
+ tree: [
+ {
+ name: 'diff_check.rb',
+ tree: [],
+ type: 'blob',
+ },
+ ],
+ },
+ {
+ type: 'tree',
+ name: 'spec',
+ tree: [
+ {
+ type: 'tree',
+ name: 'javascripts',
+ tree: [],
+ },
+ {
+ type: 'blob',
+ name: 'index_spec.js',
+ tree: [],
+ },
+ ],
+ },
+ ]);
+ });
+ });
});
diff --git a/spec/javascripts/dirty_submit/dirty_submit_collection_spec.js b/spec/javascripts/dirty_submit/dirty_submit_collection_spec.js
index 08ffc44605f..47be0b3ce9d 100644
--- a/spec/javascripts/dirty_submit/dirty_submit_collection_spec.js
+++ b/spec/javascripts/dirty_submit/dirty_submit_collection_spec.js
@@ -1,5 +1,5 @@
import DirtySubmitCollection from '~/dirty_submit/dirty_submit_collection';
-import { setInput, createForm } from './helper';
+import { setInputValue, createForm } from './helper';
describe('DirtySubmitCollection', () => {
it('disables submits until there are changes', done => {
@@ -14,11 +14,11 @@ describe('DirtySubmitCollection', () => {
expect(submit.disabled).toBe(true);
- return setInput(input, `${originalValue} changes`)
+ return setInputValue(input, `${originalValue} changes`)
.then(() => {
expect(submit.disabled).toBe(false);
})
- .then(() => setInput(input, originalValue))
+ .then(() => setInputValue(input, originalValue))
.then(() => {
expect(submit.disabled).toBe(true);
})
diff --git a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js
index 093fec97951..ae2a785de52 100644
--- a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js
+++ b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js
@@ -1,14 +1,14 @@
import DirtySubmitForm from '~/dirty_submit/dirty_submit_form';
-import { setInput, createForm } from './helper';
+import { getInputValue, setInputValue, createForm } from './helper';
function expectToToggleDisableOnDirtyUpdate(submit, input) {
- const originalValue = input.value;
+ const originalValue = getInputValue(input);
expect(submit.disabled).toBe(true);
- return setInput(input, `${originalValue} changes`)
+ return setInputValue(input, `${originalValue} changes`)
.then(() => expect(submit.disabled).toBe(false))
- .then(() => setInput(input, originalValue))
+ .then(() => setInputValue(input, originalValue))
.then(() => expect(submit.disabled).toBe(true));
}
@@ -33,4 +33,24 @@ describe('DirtySubmitForm', () => {
.then(done)
.catch(done.fail);
});
+
+ it('disables submit until there are changes for radio inputs', done => {
+ const { form, input, submit } = createForm('radio');
+
+ new DirtySubmitForm(form); // eslint-disable-line no-new
+
+ return expectToToggleDisableOnDirtyUpdate(submit, input)
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('disables submit until there are changes for checkbox inputs', done => {
+ const { form, input, submit } = createForm('checkbox');
+
+ new DirtySubmitForm(form); // eslint-disable-line no-new
+
+ return expectToToggleDisableOnDirtyUpdate(submit, input)
+ .then(done)
+ .catch(done.fail);
+ });
});
diff --git a/spec/javascripts/dirty_submit/helper.js b/spec/javascripts/dirty_submit/helper.js
index 6d1e643553c..b51783cb915 100644
--- a/spec/javascripts/dirty_submit/helper.js
+++ b/spec/javascripts/dirty_submit/helper.js
@@ -1,25 +1,42 @@
import DirtySubmitForm from '~/dirty_submit/dirty_submit_form';
import setTimeoutPromiseHelper from '../helpers/set_timeout_promise_helper';
-export function setInput(element, value) {
- element.value = value;
+function isCheckableType(type) {
+ return /^(radio|checkbox)$/.test(type);
+}
+
+export function setInputValue(element, value) {
+ const { type } = element;
+ let eventType;
+
+ if (isCheckableType(type)) {
+ element.checked = !element.checked;
+ eventType = 'change';
+ } else {
+ element.value = value;
+ eventType = 'input';
+ }
element.dispatchEvent(
- new Event('input', {
+ new Event(eventType, {
bubbles: true,
- cancelable: true,
}),
);
return setTimeoutPromiseHelper(DirtySubmitForm.THROTTLE_DURATION);
}
-export function createForm() {
+export function getInputValue(input) {
+ return isCheckableType(input.type) ? input.checked : input.value;
+}
+
+export function createForm(type = 'text') {
const form = document.createElement('form');
form.innerHTML = `
- <input type="text" value="original" class="js-input" name="input" />
+ <input type="${type}" name="${type}" class="js-input"/>
<button type="submit" class="js-dirty-submit"></button>
`;
+
const input = form.querySelector('.js-input');
const submit = form.querySelector('.js-dirty-submit');
diff --git a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js
new file mode 100644
index 00000000000..08bbb390993
--- /dev/null
+++ b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js
@@ -0,0 +1,100 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
+import { GlButton, GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('ErrorTrackingList', () => {
+ let store;
+ let wrapper;
+
+ function mountComponent({ errorTrackingEnabled = true } = {}) {
+ wrapper = shallowMount(ErrorTrackingList, {
+ localVue,
+ store,
+ propsData: {
+ indexPath: '/path',
+ enableErrorTrackingLink: '/link',
+ errorTrackingEnabled,
+ illustrationPath: 'illustration/path',
+ },
+ });
+ }
+
+ beforeEach(() => {
+ const actions = {
+ getErrorList: () => {},
+ };
+
+ const state = {
+ errors: [],
+ loading: true,
+ };
+
+ store = new Vuex.Store({
+ actions,
+ state,
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('loading', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('shows spinner', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeTruthy();
+ expect(wrapper.find(GlTable).exists()).toBeFalsy();
+ expect(wrapper.find(GlButton).exists()).toBeFalsy();
+ });
+ });
+
+ describe('results', () => {
+ beforeEach(() => {
+ store.state.loading = false;
+
+ mountComponent();
+ });
+
+ it('shows table', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlTable).exists()).toBeTruthy();
+ expect(wrapper.find(GlButton).exists()).toBeTruthy();
+ });
+ });
+
+ describe('no results', () => {
+ beforeEach(() => {
+ store.state.loading = false;
+
+ mountComponent();
+ });
+
+ it('shows empty table', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlTable).exists()).toBeTruthy();
+ expect(wrapper.find(GlButton).exists()).toBeTruthy();
+ });
+ });
+
+ describe('error tracking feature disabled', () => {
+ beforeEach(() => {
+ mountComponent({ errorTrackingEnabled: false });
+ });
+
+ it('shows empty state', () => {
+ expect(wrapper.find(GlEmptyState).exists()).toBeTruthy();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlTable).exists()).toBeFalsy();
+ expect(wrapper.find(GlButton).exists()).toBeFalsy();
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking/store/mutation_spec.js b/spec/javascripts/error_tracking/store/mutation_spec.js
new file mode 100644
index 00000000000..8117104bdbc
--- /dev/null
+++ b/spec/javascripts/error_tracking/store/mutation_spec.js
@@ -0,0 +1,36 @@
+import mutations from '~/error_tracking/store/mutations';
+import * as types from '~/error_tracking/store/mutation_types';
+
+describe('Error tracking mutations', () => {
+ describe('SET_ERRORS', () => {
+ let state;
+
+ beforeEach(() => {
+ state = { errors: [] };
+ });
+
+ it('camelizes response', () => {
+ const errors = [
+ {
+ title: 'the title',
+ external_url: 'localhost:3456',
+ count: 100,
+ userCount: 10,
+ },
+ ];
+
+ mutations[types.SET_ERRORS](state, errors);
+
+ expect(state).toEqual({
+ errors: [
+ {
+ title: 'the title',
+ externalUrl: 'localhost:3456',
+ count: 100,
+ userCount: 10,
+ },
+ ],
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb
index d6b5349594d..433bb690a1c 100644
--- a/spec/javascripts/fixtures/jobs.rb
+++ b/spec/javascripts/fixtures/jobs.rb
@@ -14,8 +14,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
create(:ci_build, :scheduled,
pipeline: pipeline,
name: 'delayed job',
- stage: 'test',
- commands: 'test')
+ stage: 'test')
end
render_views
diff --git a/spec/javascripts/fixtures/oauth_remember_me.html.haml b/spec/javascripts/fixtures/oauth_remember_me.html.haml
index 7886e995e57..a5d7c4e816a 100644
--- a/spec/javascripts/fixtures/oauth_remember_me.html.haml
+++ b/spec/javascripts/fixtures/oauth_remember_me.html.haml
@@ -3,3 +3,4 @@
%a.oauth-login.twitter{ href: "http://example.com/" }
%a.oauth-login.github{ href: "http://example.com/" }
+ %a.oauth-login.facebook{ href: "http://example.com/?redirect_fragment=L1" }
diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb
new file mode 100644
index 00000000000..e90a58e8c54
--- /dev/null
+++ b/spec/javascripts/fixtures/sessions.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe 'Sessions (JavaScript fixtures)' do
+ include JavaScriptFixturesHelpers
+
+ before(:all) do
+ clean_frontend_fixtures('sessions/')
+ end
+
+ describe SessionsController, '(JavaScript fixtures)', type: :controller do
+ include DeviseHelpers
+
+ render_views
+
+ before do
+ set_devise_mapping(context: @request)
+ end
+
+ it 'sessions/new.html.raw' do |example|
+ get :new
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+ end
+end
diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js
index 6f414c8ccf1..a14031f43ed 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js
+++ b/spec/javascripts/gfm_auto_complete_spec.js
@@ -205,4 +205,40 @@ describe('GfmAutoComplete', function() {
expect(GfmAutoComplete.isLoading({ title: 'Foo' })).toBe(false);
});
});
+
+ describe('Issues.insertTemplateFunction', function() {
+ it('should return default template', function() {
+ expect(GfmAutoComplete.Issues.insertTemplateFunction({ id: 5, title: 'Some Issue' })).toBe(
+ '${atwho-at}${id}', // eslint-disable-line no-template-curly-in-string
+ );
+ });
+
+ it('should return reference when reference is set', function() {
+ expect(
+ GfmAutoComplete.Issues.insertTemplateFunction({
+ id: 5,
+ title: 'Some Issue',
+ reference: 'grp/proj#5',
+ }),
+ ).toBe('grp/proj#5');
+ });
+ });
+
+ describe('Issues.templateFunction', function() {
+ it('should return html with id and title', function() {
+ expect(GfmAutoComplete.Issues.templateFunction({ id: 5, title: 'Some Issue' })).toBe(
+ '<li><small>5</small> Some Issue</li>',
+ );
+ });
+
+ it('should replace id with reference if reference is set', function() {
+ expect(
+ GfmAutoComplete.Issues.templateFunction({
+ id: 5,
+ title: 'Some Issue',
+ reference: 'grp/proj#5',
+ }),
+ ).toBe('<li><small>grp/proj#5</small> Some Issue</li>');
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/file_finder/index_spec.js b/spec/javascripts/ide/components/file_finder/index_spec.js
index 4d934f92f72..15ef8c31f91 100644
--- a/spec/javascripts/ide/components/file_finder/index_spec.js
+++ b/spec/javascripts/ide/components/file_finder/index_spec.js
@@ -56,7 +56,6 @@ describe('IDE File finder item spec', () => {
it('renders list of blobs', () => {
expect(vm.$el.textContent).toContain('index.js');
- expect(vm.$el.textContent).toContain('component.js');
expect(vm.$el.textContent).not.toContain('folder');
});
diff --git a/spec/javascripts/ide/lib/decorations/controller_spec.js b/spec/javascripts/ide/lib/decorations/controller_spec.js
index a112361e0d1..4118774cca3 100644
--- a/spec/javascripts/ide/lib/decorations/controller_spec.js
+++ b/spec/javascripts/ide/lib/decorations/controller_spec.js
@@ -56,7 +56,7 @@ describe('Multi-file editor library decorations controller', () => {
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
expect(controller.decorations.size).toBe(1);
- expect(controller.decorations.keys().next().value).toBe('path--path');
+ expect(controller.decorations.keys().next().value).toBe('gitlab:path--path');
});
it('calls decorate method', () => {
@@ -90,7 +90,7 @@ describe('Multi-file editor library decorations controller', () => {
controller.decorate(model);
- expect(controller.editorDecorations.keys().next().value).toBe('path--path');
+ expect(controller.editorDecorations.keys().next().value).toBe('gitlab:path--path');
});
});
diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js
index b0bc16d7c64..3a02351460c 100644
--- a/spec/javascripts/jobs/components/sidebar_spec.js
+++ b/spec/javascripts/jobs/components/sidebar_spec.js
@@ -28,7 +28,7 @@ describe('Sidebar details block', () => {
store,
});
- expect(vm.$el.querySelector('.js-retry-job')).toBeNull();
+ expect(vm.$el.querySelector('.js-retry-button')).toBeNull();
});
});
@@ -70,7 +70,7 @@ describe('Sidebar details block', () => {
});
it('should render link to retry job', () => {
- expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path);
+ expect(vm.$el.querySelector('.js-retry-button').getAttribute('href')).toEqual(job.retry_path);
});
it('should render link to cancel job', () => {
diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js
index 4195d9d3680..7931b2af79f 100644
--- a/spec/javascripts/jobs/store/getters_spec.js
+++ b/spec/javascripts/jobs/store/getters_spec.js
@@ -8,30 +8,6 @@ describe('Job Store Getters', () => {
localState = state();
});
- describe('headerActions', () => {
- describe('with new issue path', () => {
- it('returns an array with action to create a new issue', () => {
- localState.job.new_issue_path = 'issues/new';
-
- expect(getters.headerActions(localState)).toEqual([
- {
- label: 'New issue',
- path: localState.job.new_issue_path,
- cssClass:
- 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
- type: 'link',
- },
- ]);
- });
- });
-
- describe('without new issue path', () => {
- it('returns an empty array', () => {
- expect(getters.headerActions(localState)).toEqual([]);
- });
- });
- });
-
describe('headerTime', () => {
describe('when the job has started key', () => {
it('returns started key', () => {
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 1ec1e8a8dd9..121c4040212 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -3,6 +3,25 @@ import * as commonUtils from '~/lib/utils/common_utils';
import MockAdapter from 'axios-mock-adapter';
import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data';
+const PIXEL_TOLERANCE = 0.2;
+
+/**
+ * Loads a data URL as the src of an
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image|Image}
+ * and resolves to that Image once loaded.
+ *
+ * @param url
+ * @returns {Promise}
+ */
+const urlToImage = url =>
+ new Promise(resolve => {
+ const img = new Image();
+ img.onload = function() {
+ resolve(img);
+ };
+ img.src = url;
+ });
+
describe('common_utils', () => {
describe('parseUrl', () => {
it('returns an anchor tag with url', () => {
@@ -347,20 +366,31 @@ describe('common_utils', () => {
});
describe('parseBoolean', () => {
+ const { parseBoolean } = commonUtils;
+
it('returns true for "true"', () => {
- expect(commonUtils.parseBoolean('true')).toEqual(true);
+ expect(parseBoolean('true')).toEqual(true);
});
it('returns false for "false"', () => {
- expect(commonUtils.parseBoolean('false')).toEqual(false);
+ expect(parseBoolean('false')).toEqual(false);
});
it('returns false for "something"', () => {
- expect(commonUtils.parseBoolean('something')).toEqual(false);
+ expect(parseBoolean('something')).toEqual(false);
});
it('returns false for null', () => {
- expect(commonUtils.parseBoolean(null)).toEqual(false);
+ expect(parseBoolean(null)).toEqual(false);
+ });
+
+ it('is idempotent', () => {
+ const input = ['true', 'false', 'something', null];
+ input.forEach(value => {
+ const result = parseBoolean(value);
+
+ expect(parseBoolean(result)).toBe(result);
+ });
});
});
@@ -502,8 +532,9 @@ describe('common_utils', () => {
it('should return the favicon with the overlay', done => {
commonUtils
.createOverlayIcon(faviconDataUrl, overlayDataUrl)
- .then(url => {
- expect(url).toEqual(faviconWithOverlayDataUrl);
+ .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)]))
+ .then(([actual, expected]) => {
+ expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE);
done();
})
.catch(done.fail);
@@ -525,10 +556,10 @@ describe('common_utils', () => {
it('should set page favicon to provided favicon overlay', done => {
commonUtils
.setFaviconOverlay(overlayDataUrl)
- .then(() => {
- expect(document.getElementById('favicon').getAttribute('href')).toEqual(
- faviconWithOverlayDataUrl,
- );
+ .then(() => document.getElementById('favicon').getAttribute('href'))
+ .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)]))
+ .then(([actual, expected]) => {
+ expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE);
done();
})
.catch(done.fail);
@@ -571,10 +602,10 @@ describe('common_utils', () => {
commonUtils
.setCiStatusFavicon(BUILD_URL)
- .then(() => {
- const favicon = document.getElementById('favicon');
-
- expect(favicon.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
+ .then(() => document.getElementById('favicon').getAttribute('href'))
+ .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)]))
+ .then(([actual, expected]) => {
+ expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE);
done();
})
.catch(done.fail);
@@ -716,4 +747,29 @@ describe('common_utils', () => {
expect(commonUtils.roundOffFloat(34567.14159, -5)).toBe(0);
});
});
+
+ describe('isInViewport', () => {
+ let el;
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ });
+
+ afterEach(() => {
+ document.body.removeChild(el);
+ });
+
+ it('returns true when provided `el` is in viewport', () => {
+ document.body.appendChild(el);
+
+ expect(commonUtils.isInViewport(el)).toBe(true);
+ });
+
+ it('returns false when provided `el` is not in viewport', () => {
+ el.setAttribute('style', 'position: absolute; top: -1000px; left: -1000px;');
+ document.body.appendChild(el);
+
+ expect(commonUtils.isInViewport(el)).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/datetime_utility_spec.js b/spec/javascripts/lib/utils/datetime_utility_spec.js
index bebe76f76c5..5327ec9d2a0 100644
--- a/spec/javascripts/lib/utils/datetime_utility_spec.js
+++ b/spec/javascripts/lib/utils/datetime_utility_spec.js
@@ -156,7 +156,7 @@ describe('getSundays', () => {
});
describe('getTimeframeWindowFrom', () => {
- it('returns array of date objects upto provided length start with provided startDate', () => {
+ it('returns array of date objects upto provided length (positive number) into the future starting from provided startDate', () => {
const startDate = new Date(2018, 0, 1);
const mockTimeframe = [
new Date(2018, 0, 1),
@@ -174,6 +174,25 @@ describe('getTimeframeWindowFrom', () => {
expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate());
});
});
+
+ it('returns array of date objects upto provided length (negative number) into the past starting from provided startDate', () => {
+ const startDate = new Date(2018, 0, 1);
+ const mockTimeframe = [
+ new Date(2018, 0, 1),
+ new Date(2017, 11, 1),
+ new Date(2017, 10, 1),
+ new Date(2017, 9, 1),
+ new Date(2017, 8, 1),
+ ];
+ const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, -5);
+
+ expect(timeframe.length).toBe(5);
+ timeframe.forEach((timeframeItem, index) => {
+ expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear());
+ expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth());
+ expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate());
+ });
+ });
});
describe('formatTime', () => {
@@ -376,3 +395,22 @@ describe('calculateRemainingMilliseconds', () => {
expect(milliseconds).toBe(0);
});
});
+
+describe('newDate', () => {
+ it('returns new date instance from existing date instance', () => {
+ const initialDate = new Date(2019, 0, 1);
+ const copiedDate = datetimeUtility.newDate(initialDate);
+
+ expect(copiedDate.getTime()).toBe(initialDate.getTime());
+
+ initialDate.setMonth(initialDate.getMonth() + 1);
+
+ expect(copiedDate.getTime()).not.toBe(initialDate.getTime());
+ });
+
+ it('returns date instance when provided date param is not of type date or is undefined', () => {
+ const initialDate = datetimeUtility.newDate();
+
+ expect(initialDate instanceof Date).toBe(true);
+ });
+});
diff --git a/spec/javascripts/lib/utils/text_markdown_spec.js b/spec/javascripts/lib/utils/text_markdown_spec.js
index f71d27eb4e4..df4029555bb 100644
--- a/spec/javascripts/lib/utils/text_markdown_spec.js
+++ b/spec/javascripts/lib/utils/text_markdown_spec.js
@@ -13,215 +13,296 @@ describe('init markdown', () => {
textArea.parentNode.removeChild(textArea);
});
- describe('without selection', () => {
- it('inserts the tag on an empty line', () => {
- const initialValue = '';
+ describe('textArea', () => {
+ describe('without selection', () => {
+ it('inserts the tag on an empty line', () => {
+ const initialValue = '';
- textArea.value = initialValue;
- textArea.selectionStart = 0;
- textArea.selectionEnd = 0;
-
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '*',
- blockTag: null,
- selected: '',
- wrap: false,
- });
-
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
-
- it('inserts the tag on a new line if the current one is not empty', () => {
- const initialValue = 'some text';
+ textArea.value = initialValue;
+ textArea.selectionStart = 0;
+ textArea.selectionEnd = 0;
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '*',
- blockTag: null,
- selected: '',
- wrap: false,
+ expect(textArea.value).toEqual(`${initialValue}* `);
});
- expect(textArea.value).toEqual(`${initialValue}\n* `);
- });
+ it('inserts the tag on a new line if the current one is not empty', () => {
+ const initialValue = 'some text';
- it('inserts the tag on the same line if the current line only contains spaces', () => {
- const initialValue = ' ';
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '*',
- blockTag: null,
- selected: '',
- wrap: false,
+ expect(textArea.value).toEqual(`${initialValue}\n* `);
});
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ it('inserts the tag on the same line if the current line only contains spaces', () => {
+ const initialValue = ' ';
- it('inserts the tag on the same line if the current line only contains tabs', () => {
- const initialValue = '\t\t\t';
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- textArea.value = initialValue;
- textArea.setSelectionRange(initialValue.length, initialValue.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '*',
- blockTag: null,
- selected: '',
- wrap: false,
+ expect(textArea.value).toEqual(`${initialValue}* `);
});
- expect(textArea.value).toEqual(`${initialValue}* `);
- });
+ it('inserts the tag on the same line if the current line only contains tabs', () => {
+ const initialValue = '\t\t\t';
- it('places the cursor inside the tags', () => {
- const start = 'lorem ';
- const end = ' ipsum';
- const tag = '*';
+ textArea.value = initialValue;
+ textArea.setSelectionRange(initialValue.length, initialValue.length);
- textArea.value = `${start}${end}`;
- textArea.setSelectionRange(start.length, start.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag,
- blockTag: null,
- selected: '',
- wrap: true,
+ expect(textArea.value).toEqual(`${initialValue}* `);
});
- expect(textArea.value).toEqual(`${start}**${end}`);
+ it('places the cursor inside the tags', () => {
+ const start = 'lorem ';
+ const end = ' ipsum';
+ const tag = '*';
- // cursor placement should be between tags
- expect(textArea.selectionStart).toBe(start.length + tag.length);
- });
- });
+ textArea.value = `${start}${end}`;
+ textArea.setSelectionRange(start.length, start.length);
- describe('with selection', () => {
- const text = 'initial selected value';
- const selected = 'selected';
- beforeEach(() => {
- textArea.value = text;
- const selectedIndex = text.indexOf(selected);
- textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
- });
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag,
+ blockTag: null,
+ selected: '',
+ wrap: true,
+ });
- it('applies the tag to the selected value', () => {
- const selectedIndex = text.indexOf(selected);
- const tag = '*';
+ expect(textArea.value).toEqual(`${start}**${end}`);
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag,
- blockTag: null,
- selected,
- wrap: true,
+ // cursor placement should be between tags
+ expect(textArea.selectionStart).toBe(start.length + tag.length);
});
-
- expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`));
-
- // cursor placement should be after selection + 2 tag lengths
- expect(textArea.selectionStart).toBe(selectedIndex + selected.length + 2 * tag.length);
});
- it('replaces the placeholder in the tag', () => {
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag: '[{text}](url)',
- blockTag: null,
- selected,
- wrap: false,
+ describe('with selection', () => {
+ const text = 'initial selected value';
+ const selected = 'selected';
+ beforeEach(() => {
+ textArea.value = text;
+ const selectedIndex = text.indexOf(selected);
+ textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
});
- expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`));
- });
+ it('applies the tag to the selected value', () => {
+ const selectedIndex = text.indexOf(selected);
+ const tag = '*';
- describe('and text to be selected', () => {
- const tag = '[{text}](url)';
- const select = 'url';
-
- it('selects the text', () => {
insertMarkdownText({
textArea,
text: textArea.value,
tag,
blockTag: null,
selected,
- wrap: false,
- select,
+ wrap: true,
});
- const expectedText = text.replace(selected, `[${selected}](url)`);
+ expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`));
- expect(textArea.value).toEqual(expectedText);
- expect(textArea.selectionStart).toEqual(expectedText.indexOf(select));
- expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length);
+ // cursor placement should be after selection + 2 tag lengths
+ expect(textArea.selectionStart).toBe(selectedIndex + selected.length + 2 * tag.length);
});
- it('selects the right text when multiple tags are present', () => {
- const initialValue = `${tag} ${tag} ${selected}`;
- textArea.value = initialValue;
- const selectedIndex = initialValue.indexOf(selected);
- textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
+ it('replaces the placeholder in the tag', () => {
insertMarkdownText({
textArea,
text: textArea.value,
- tag,
+ tag: '[{text}](url)',
blockTag: null,
selected,
wrap: false,
- select,
});
- const expectedText = initialValue.replace(selected, `[${selected}](url)`);
-
- expect(textArea.value).toEqual(expectedText);
- expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select));
- expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length);
+ expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`));
});
- it('should support selected urls', () => {
- const expectedUrl = 'http://www.gitlab.com';
- const expectedSelectionText = 'text';
- const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`;
- const initialValue = `text ${expectedUrl} text`;
+ describe('and text to be selected', () => {
+ const tag = '[{text}](url)';
+ const select = 'url';
+
+ it('selects the text', () => {
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag,
+ blockTag: null,
+ selected,
+ wrap: false,
+ select,
+ });
+
+ const expectedText = text.replace(selected, `[${selected}](url)`);
+
+ expect(textArea.value).toEqual(expectedText);
+ expect(textArea.selectionStart).toEqual(expectedText.indexOf(select));
+ expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length);
+ });
- textArea.value = initialValue;
- const selectedIndex = initialValue.indexOf(expectedUrl);
- textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length);
+ it('selects the right text when multiple tags are present', () => {
+ const initialValue = `${tag} ${tag} ${selected}`;
+ textArea.value = initialValue;
+ const selectedIndex = initialValue.indexOf(selected);
+ textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length);
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag,
+ blockTag: null,
+ selected,
+ wrap: false,
+ select,
+ });
+
+ const expectedText = initialValue.replace(selected, `[${selected}](url)`);
+
+ expect(textArea.value).toEqual(expectedText);
+ expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select));
+ expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length);
+ });
- insertMarkdownText({
- textArea,
- text: textArea.value,
- tag,
- blockTag: null,
- selected: expectedUrl,
- wrap: false,
- select,
+ it('should support selected urls', () => {
+ const expectedUrl = 'http://www.gitlab.com';
+ const expectedSelectionText = 'text';
+ const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`;
+ const initialValue = `text ${expectedUrl} text`;
+
+ textArea.value = initialValue;
+ const selectedIndex = initialValue.indexOf(expectedUrl);
+ textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length);
+
+ insertMarkdownText({
+ textArea,
+ text: textArea.value,
+ tag,
+ blockTag: null,
+ selected: expectedUrl,
+ wrap: false,
+ select,
+ });
+
+ expect(textArea.value).toEqual(expectedText);
+ expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1));
+ expect(textArea.selectionEnd).toEqual(
+ expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length,
+ );
});
+ });
+ });
+ });
+
+ describe('Ace Editor', () => {
+ let editor;
+
+ beforeEach(() => {
+ editor = {
+ getSelectionRange: () => ({
+ start: 0,
+ end: 0,
+ }),
+ getValue: () => 'this is text \n in two lines',
+ insert: () => {},
+ navigateLeft: () => {},
+ };
+ });
+
+ it('uses ace editor insert text when editor is passed in', () => {
+ spyOn(editor, 'insert');
- expect(textArea.value).toEqual(expectedText);
- expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1));
- expect(textArea.selectionEnd).toEqual(
- expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length,
- );
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: false,
+ editor,
+ });
+
+ expect(editor.insert).toHaveBeenCalled();
+ });
+
+ it('adds block tags on line above and below selection', () => {
+ spyOn(editor, 'insert');
+
+ const selected = 'this text \n is multiple \n lines';
+ const text = `before \n ${selected} \n after`;
+
+ insertMarkdownText({
+ text,
+ tag: '',
+ blockTag: '***',
+ selected,
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.insert).toHaveBeenCalledWith(`***\n${selected}\n***`);
+ });
+
+ it('uses ace editor to navigate back tag length when nothing is selected', () => {
+ spyOn(editor, 'navigateLeft');
+
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: '',
+ wrap: true,
+ editor,
});
+
+ expect(editor.navigateLeft).toHaveBeenCalledWith(1);
+ });
+
+ it('ace editor does not navigate back when there is selected text', () => {
+ spyOn(editor, 'navigateLeft');
+
+ insertMarkdownText({
+ text: editor.getValue,
+ tag: '*',
+ blockTag: null,
+ selected: 'foobar',
+ wrap: true,
+ editor,
+ });
+
+ expect(editor.navigateLeft).not.toHaveBeenCalled();
});
});
});
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js
index 92ebfc38722..0a266b19ea5 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
@@ -135,4 +135,20 @@ describe('text_utility', () => {
expect(textUtils.getFirstCharacterCapitalized(null)).toEqual('');
});
});
+
+ describe('truncatePathMiddleToLength', () => {
+ it('does not truncate text', () => {
+ expect(textUtils.truncatePathMiddleToLength('app/test', 50)).toEqual('app/test');
+ });
+
+ it('truncates middle of the path', () => {
+ expect(textUtils.truncatePathMiddleToLength('app/test/diff', 13)).toEqual('app/…/diff');
+ });
+
+ it('truncates multiple times in the middle of the path', () => {
+ expect(textUtils.truncatePathMiddleToLength('app/test/merge_request/diff', 13)).toEqual(
+ 'app/…/…/diff',
+ );
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/url_utility_spec.js b/spec/javascripts/lib/utils/url_utility_spec.js
index e4df8441793..381c7b2d0a6 100644
--- a/spec/javascripts/lib/utils/url_utility_spec.js
+++ b/spec/javascripts/lib/utils/url_utility_spec.js
@@ -1,4 +1,4 @@
-import { webIDEUrl, mergeUrlParams } from '~/lib/utils/url_utility';
+import * as urlUtils from '~/lib/utils/url_utility';
describe('URL utility', () => {
describe('webIDEUrl', () => {
@@ -8,7 +8,7 @@ describe('URL utility', () => {
describe('without relative_url_root', () => {
it('returns IDE path with route', () => {
- expect(webIDEUrl('/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
+ expect(urlUtils.webIDEUrl('/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
'/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
);
});
@@ -20,7 +20,7 @@ describe('URL utility', () => {
});
it('returns IDE path with route', () => {
- expect(webIDEUrl('/gitlab/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
+ expect(urlUtils.webIDEUrl('/gitlab/gitlab-org/gitlab-ce/merge_requests/1')).toBe(
'/gitlab/-/ide/project/gitlab-org/gitlab-ce/merge_requests/1',
);
});
@@ -29,23 +29,82 @@ describe('URL utility', () => {
describe('mergeUrlParams', () => {
it('adds w', () => {
- expect(mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
- expect(mergeUrlParams({ w: 1 }, '/path#frag')).toBe('/path?w=1#frag');
- expect(mergeUrlParams({ w: 1 }, 'https://host/path')).toBe('https://host/path?w=1');
- expect(mergeUrlParams({ w: 1 }, 'https://host/path#frag')).toBe('https://host/path?w=1#frag');
- expect(mergeUrlParams({ w: 1 }, 'https://h/p?k1=v1#frag')).toBe('https://h/p?k1=v1&w=1#frag');
+ expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
+ expect(urlUtils.mergeUrlParams({ w: 1 }, '/path#frag')).toBe('/path?w=1#frag');
+ expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path')).toBe('https://host/path?w=1');
+ expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://host/path#frag')).toBe(
+ 'https://host/path?w=1#frag',
+ );
+
+ expect(urlUtils.mergeUrlParams({ w: 1 }, 'https://h/p?k1=v1#frag')).toBe(
+ 'https://h/p?k1=v1&w=1#frag',
+ );
});
it('updates w', () => {
- expect(mergeUrlParams({ w: 1 }, '?k1=v1&w=0#frag')).toBe('?k1=v1&w=1#frag');
+ expect(urlUtils.mergeUrlParams({ w: 1 }, '?k1=v1&w=0#frag')).toBe('?k1=v1&w=1#frag');
});
it('adds multiple params', () => {
- expect(mergeUrlParams({ a: 1, b: 2, c: 3 }, '#frag')).toBe('?a=1&b=2&c=3#frag');
+ expect(urlUtils.mergeUrlParams({ a: 1, b: 2, c: 3 }, '#frag')).toBe('?a=1&b=2&c=3#frag');
});
it('adds and updates encoded params', () => {
- expect(mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag');
+ expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag');
+ });
+ });
+
+ describe('removeParams', () => {
+ describe('when url is passed', () => {
+ it('removes query param with encoded ampersand', () => {
+ const url = urlUtils.removeParams(['filter'], '/mail?filter=n%3Djoe%26l%3Dhome');
+
+ expect(url).toBe('/mail');
+ });
+
+ it('should remove param when url has no other params', () => {
+ const url = urlUtils.removeParams(['size'], '/feature/home?size=5');
+
+ expect(url).toBe('/feature/home');
+ });
+
+ it('should remove param when url has other params', () => {
+ const url = urlUtils.removeParams(['size'], '/feature/home?q=1&size=5&f=html');
+
+ expect(url).toBe('/feature/home?q=1&f=html');
+ });
+
+ it('should remove param and preserve fragment', () => {
+ const url = urlUtils.removeParams(['size'], '/feature/home?size=5#H2');
+
+ expect(url).toBe('/feature/home#H2');
+ });
+
+ it('should remove multiple params', () => {
+ const url = urlUtils.removeParams(['z', 'a'], '/home?z=11111&l=en_US&a=true#H2');
+
+ expect(url).toBe('/home?l=en_US#H2');
+ });
+ });
+ });
+
+ describe('setUrlFragment', () => {
+ it('should set fragment when url has no fragment', () => {
+ const url = urlUtils.setUrlFragment('/home/feature', 'usage');
+
+ expect(url).toBe('/home/feature#usage');
+ });
+
+ it('should set fragment when url has existing fragment', () => {
+ const url = urlUtils.setUrlFragment('/home/feature#overview', 'usage');
+
+ expect(url).toBe('/home/feature#usage');
+ });
+
+ it('should set fragment when given fragment includes #', () => {
+ const url = urlUtils.setUrlFragment('/home/feature#overview', '#install');
+
+ expect(url).toBe('/home/feature#install');
});
});
});
diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js
index 0d465510fd3..406527b08a3 100644
--- a/spec/javascripts/matchers.js
+++ b/spec/javascripts/matchers.js
@@ -1,3 +1,5 @@
+import pixelmatch from 'pixelmatch';
+
export default {
toContainText: () => ({
compare(vm, text) {
@@ -54,4 +56,41 @@ export default {
return result;
},
}),
+ toImageDiffEqual: () => {
+ const getImageData = img => {
+ const canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+ canvas.getContext('2d').drawImage(img, 0, 0);
+ return canvas.getContext('2d').getImageData(0, 0, img.width, img.height).data;
+ };
+
+ return {
+ compare(actual, expected, threshold = 0.1) {
+ if (actual.height !== expected.height || actual.width !== expected.width) {
+ return {
+ pass: false,
+ message: `Expected image dimensions (h x w) of ${expected.height}x${expected.width}.
+ Received an image with ${actual.height}x${actual.width}`,
+ };
+ }
+
+ const { width, height } = actual;
+ const differentPixels = pixelmatch(
+ getImageData(actual),
+ getImageData(expected),
+ null,
+ width,
+ height,
+ { threshold },
+ );
+
+ return {
+ pass: differentPixels < 20,
+ message: `${differentPixels} pixels differ more than ${threshold *
+ 100} percent between input and output.`,
+ };
+ },
+ };
+ },
};
diff --git a/spec/javascripts/notebook/cells/output/html_spec.js b/spec/javascripts/notebook/cells/output/html_spec.js
index bea62f54634..3ee404fb187 100644
--- a/spec/javascripts/notebook/cells/output/html_spec.js
+++ b/spec/javascripts/notebook/cells/output/html_spec.js
@@ -9,6 +9,8 @@ describe('html output cell', () => {
return new Component({
propsData: {
rawCode,
+ count: 0,
+ index: 0,
},
}).$mount();
}
diff --git a/spec/javascripts/notebook/cells/output/index_spec.js b/spec/javascripts/notebook/cells/output/index_spec.js
index feab7ad4212..005569f1c2d 100644
--- a/spec/javascripts/notebook/cells/output/index_spec.js
+++ b/spec/javascripts/notebook/cells/output/index_spec.js
@@ -10,7 +10,7 @@ describe('Output component', () => {
const createComponent = output => {
vm = new Component({
propsData: {
- output,
+ outputs: [].concat(output),
count: 1,
},
});
@@ -51,28 +51,21 @@ describe('Output component', () => {
it('renders as an image', () => {
expect(vm.$el.querySelector('img')).not.toBeNull();
});
-
- it('does not render the prompt', () => {
- expect(vm.$el.querySelector('.prompt span')).toBeNull();
- });
});
describe('html output', () => {
- beforeEach(done => {
+ it('renders raw HTML', () => {
createComponent(json.cells[4].outputs[0]);
- setTimeout(() => {
- done();
- });
- });
-
- it('renders raw HTML', () => {
expect(vm.$el.querySelector('p')).not.toBeNull();
- expect(vm.$el.textContent.trim()).toBe('test');
+ expect(vm.$el.querySelectorAll('p').length).toBe(1);
+ expect(vm.$el.textContent.trim()).toContain('test');
});
- it('does not render the prompt', () => {
- expect(vm.$el.querySelector('.prompt span')).toBeNull();
+ it('renders multiple raw HTML outputs', () => {
+ createComponent([json.cells[4].outputs[0], json.cells[4].outputs[0]]);
+
+ expect(vm.$el.querySelectorAll('p').length).toBe(2);
});
});
@@ -88,10 +81,6 @@ describe('Output component', () => {
it('renders as an svg', () => {
expect(vm.$el.querySelector('svg')).not.toBeNull();
});
-
- it('does not render the prompt', () => {
- expect(vm.$el.querySelector('.prompt span')).toBeNull();
- });
});
describe('default to plain text', () => {
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 3c57fe51352..362963ddaf4 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -223,7 +223,6 @@ describe('issue_comment_form component', () => {
'Comment & close issue',
);
- expect(vm.$el.querySelector('.js-note-discard')).toBeDefined();
done();
});
});
diff --git a/spec/javascripts/notes/components/discussion_counter_spec.js b/spec/javascripts/notes/components/discussion_counter_spec.js
index d09bc5037ef..fecc0d604b1 100644
--- a/spec/javascripts/notes/components/discussion_counter_spec.js
+++ b/spec/javascripts/notes/components/discussion_counter_spec.js
@@ -33,11 +33,13 @@ describe('DiscussionCounter component', () => {
...discussionMock,
id: discussionMock.id,
notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: true }],
+ resolved: true,
},
{
...discussionMock,
id: discussionMock.id + 1,
notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: false }],
+ resolved: false,
},
];
const firstDiscussionId = discussionMock.id + 1;
diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js
index 5efcab436e4..91dab58ba7f 100644
--- a/spec/javascripts/notes/components/discussion_filter_spec.js
+++ b/spec/javascripts/notes/components/discussion_filter_spec.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import createStore from '~/notes/stores';
import DiscussionFilter from '~/notes/components/discussion_filter.vue';
+import { DISCUSSION_FILTERS_DEFAULT_VALUE } from '~/notes/constants';
import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { discussionFiltersMock, discussionMock } from '../mock_data';
@@ -20,16 +21,14 @@ describe('DiscussionFilter component', () => {
},
];
const Component = Vue.extend(DiscussionFilter);
- const selectedValue = discussionFiltersMock[0].value;
+ const selectedValue = DISCUSSION_FILTERS_DEFAULT_VALUE;
+ const props = { filters: discussionFiltersMock, selectedValue };
store.state.discussions = discussions;
return mountComponentWithStore(Component, {
el: null,
store,
- props: {
- filters: discussionFiltersMock,
- selectedValue,
- },
+ props,
});
};
@@ -115,4 +114,41 @@ describe('DiscussionFilter component', () => {
});
});
});
+
+ describe('URL with Links to notes', () => {
+ afterEach(() => {
+ window.location.hash = '';
+ });
+
+ it('updates the filter when the URL links to a note', done => {
+ window.location.hash = `note_${discussionMock.notes[0].id}`;
+ vm.currentValue = discussionFiltersMock[2].value;
+ vm.handleLocationHash();
+
+ vm.$nextTick(() => {
+ expect(vm.currentValue).toEqual(DISCUSSION_FILTERS_DEFAULT_VALUE);
+ done();
+ });
+ });
+
+ it('does not update the filter when the current filter is "Show all activity"', done => {
+ window.location.hash = `note_${discussionMock.notes[0].id}`;
+ vm.handleLocationHash();
+
+ vm.$nextTick(() => {
+ expect(vm.currentValue).toEqual(DISCUSSION_FILTERS_DEFAULT_VALUE);
+ done();
+ });
+ });
+
+ it('only updates filter when the URL links to a note', done => {
+ window.location.hash = `testing123`;
+ vm.handleLocationHash();
+
+ vm.$nextTick(() => {
+ expect(vm.currentValue).toEqual(DISCUSSION_FILTERS_DEFAULT_VALUE);
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index f6c854e6def..b102b7aecf7 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -1,20 +1,19 @@
import Vue from 'vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import createStore from '~/notes/stores';
import noteActions from '~/notes/components/note_actions.vue';
import { userDataMock } from '../mock_data';
-describe('issue_note_actions component', () => {
- let vm;
+describe('noteActions', () => {
+ let wrapper;
let store;
- let Component;
beforeEach(() => {
- Component = Vue.extend(noteActions);
store = createStore();
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
describe('user is logged in', () => {
@@ -36,45 +35,57 @@ describe('issue_note_actions component', () => {
store.dispatch('setUserData', userDataMock);
- vm = new Component({
+ const localVue = createLocalVue();
+ wrapper = shallowMount(noteActions, {
store,
propsData: props,
- }).$mount();
+ localVue,
+ sync: false,
+ });
});
it('should render access level badge', () => {
- expect(vm.$el.querySelector('.note-role').textContent.trim()).toEqual(props.accessLevel);
+ expect(
+ wrapper
+ .find('.note-role')
+ .text()
+ .trim(),
+ ).toEqual(props.accessLevel);
});
it('should render emoji link', () => {
- expect(vm.$el.querySelector('.js-add-award')).toBeDefined();
+ expect(wrapper.find('.js-add-award').exists()).toBe(true);
});
describe('actions dropdown', () => {
it('should be possible to edit the comment', () => {
- expect(vm.$el.querySelector('.js-note-edit')).toBeDefined();
+ expect(wrapper.find('.js-note-edit').exists()).toBe(true);
});
it('should be possible to report abuse to GitLab', () => {
- expect(vm.$el.querySelector(`a[href="${props.reportAbusePath}"]`)).toBeDefined();
+ expect(wrapper.find(`a[href="${props.reportAbusePath}"]`).exists()).toBe(true);
});
it('should be possible to copy link to a note', () => {
- expect(vm.$el.querySelector('.js-btn-copy-note-link')).not.toBeNull();
+ expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(true);
});
it('should not show copy link action when `noteUrl` prop is empty', done => {
- vm.noteUrl = '';
+ wrapper.setProps({
+ ...props,
+ noteUrl: '',
+ });
+
Vue.nextTick()
.then(() => {
- expect(vm.$el.querySelector('.js-btn-copy-note-link')).toBeNull();
+ expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(false);
})
.then(done)
.catch(done.fail);
});
it('should be possible to delete comment', () => {
- expect(vm.$el.querySelector('.js-note-delete')).toBeDefined();
+ expect(wrapper.find('.js-note-delete').exists()).toBe(true);
});
});
});
@@ -96,18 +107,21 @@ describe('issue_note_actions component', () => {
reportAbusePath:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
};
- vm = new Component({
+ const localVue = createLocalVue();
+ wrapper = shallowMount(noteActions, {
store,
propsData: props,
- }).$mount();
+ localVue,
+ sync: false,
+ });
});
it('should not render emoji link', () => {
- expect(vm.$el.querySelector('.js-add-award')).toEqual(null);
+ expect(wrapper.find('.js-add-award').exists()).toBe(false);
});
it('should not render actions dropdown', () => {
- expect(vm.$el.querySelector('.more-actions')).toEqual(null);
+ expect(wrapper.find('.more-actions').exists()).toBe(false);
});
});
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index 3fbae82f16c..b6b2c7d60a5 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -179,11 +179,11 @@ describe('Notes Store mutations', () => {
diff_file: {
file_hash: 'a',
},
- truncated_diff_lines: ['a'],
+ truncated_diff_lines: [{ text: '+a', rich_text: '+<span>a</span>' }],
},
]);
- expect(state.discussions[0].truncated_diff_lines).toEqual(['a']);
+ expect(state.discussions[0].truncated_diff_lines).toEqual([{ rich_text: '<span>a</span>' }]);
});
it('adds empty truncated_diff_lines when not in discussion', () => {
@@ -420,9 +420,12 @@ describe('Notes Store mutations', () => {
],
};
- mutations.SET_DISCUSSION_DIFF_LINES(state, { discussionId: 1, diffLines: ['test'] });
+ mutations.SET_DISCUSSION_DIFF_LINES(state, {
+ discussionId: 1,
+ diffLines: [{ text: '+a', rich_text: '+<span>a</span>' }],
+ });
- expect(state.discussions[0].truncated_diff_lines).toEqual(['test']);
+ expect(state.discussions[0].truncated_diff_lines).toEqual([{ rich_text: '<span>a</span>' }]);
});
it('keeps reactivity of discussion', () => {
@@ -435,7 +438,10 @@ describe('Notes Store mutations', () => {
]);
const discussion = state.discussions[0];
- mutations.SET_DISCUSSION_DIFF_LINES(state, { discussionId: 1, diffLines: ['test'] });
+ mutations.SET_DISCUSSION_DIFF_LINES(state, {
+ discussionId: 1,
+ diffLines: [{ rich_text: '<span>a</span>' }],
+ });
discussion.expanded = true;
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
index 2caa266b85f..4125706a407 100644
--- a/spec/javascripts/oauth_remember_me_spec.js
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -20,6 +20,10 @@ describe('OAuthRememberMe', () => {
expect($('#oauth-container .oauth-login.github').attr('href')).toBe(
'http://example.com/?remember_me=1',
);
+
+ expect($('#oauth-container .oauth-login.facebook').attr('href')).toBe(
+ 'http://example.com/?redirect_fragment=L1&remember_me=1',
+ );
});
it('removes the "remember_me" query parameter from all OAuth login buttons', () => {
@@ -28,5 +32,8 @@ describe('OAuthRememberMe', () => {
expect($('#oauth-container .oauth-login.twitter').attr('href')).toBe('http://example.com/');
expect($('#oauth-container .oauth-login.github').attr('href')).toBe('http://example.com/');
+ expect($('#oauth-container .oauth-login.facebook').attr('href')).toBe(
+ 'http://example.com/?redirect_fragment=L1',
+ );
});
});
diff --git a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js
new file mode 100644
index 00000000000..7a8227479d4
--- /dev/null
+++ b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js
@@ -0,0 +1,61 @@
+import $ from 'jquery';
+import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment';
+
+describe('preserve_url_fragment', () => {
+ preloadFixtures('sessions/new.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('sessions/new.html.raw');
+ });
+
+ it('adds the url fragment to all login and sign up form actions', () => {
+ preserveUrlFragment('#L65');
+
+ expect($('#new_user').attr('action')).toBe('http://test.host/users/sign_in#L65');
+ expect($('#new_new_user').attr('action')).toBe('http://test.host/users#L65');
+ });
+
+ it('does not add an empty url fragment to login and sign up form actions', () => {
+ preserveUrlFragment();
+
+ expect($('#new_user').attr('action')).toBe('http://test.host/users/sign_in');
+ expect($('#new_new_user').attr('action')).toBe('http://test.host/users');
+ });
+
+ it('does not add an empty query parameter to OmniAuth login buttons', () => {
+ preserveUrlFragment();
+
+ expect($('#oauth-login-cas3').attr('href')).toBe('http://test.host/users/auth/cas3');
+
+ expect($('.omniauth-container #oauth-login-auth0').attr('href')).toBe(
+ 'http://test.host/users/auth/auth0',
+ );
+ });
+
+ describe('adds "redirect_fragment" query parameter to OmniAuth login buttons', () => {
+ it('when "remember_me" is not present', () => {
+ preserveUrlFragment('#L65');
+
+ expect($('#oauth-login-cas3').attr('href')).toBe(
+ 'http://test.host/users/auth/cas3?redirect_fragment=L65',
+ );
+
+ expect($('.omniauth-container #oauth-login-auth0').attr('href')).toBe(
+ 'http://test.host/users/auth/auth0?redirect_fragment=L65',
+ );
+ });
+
+ it('when "remember-me" is present', () => {
+ $('a.omniauth-btn').attr('href', (i, href) => `${href}?remember_me=1`);
+ preserveUrlFragment('#L65');
+
+ expect($('#oauth-login-cas3').attr('href')).toBe(
+ 'http://test.host/users/auth/cas3?remember_me=1&redirect_fragment=L65',
+ );
+
+ expect($('#oauth-login-auth0').attr('href')).toBe(
+ 'http://test.host/users/auth/auth0?remember_me=1&redirect_fragment=L65',
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js
index 29420216bc4..36b181f24ef 100644
--- a/spec/javascripts/releases/components/release_block_spec.js
+++ b/spec/javascripts/releases/components/release_block_spec.js
@@ -133,4 +133,20 @@ describe('Release block', () => {
release.assets.links[0].name,
);
});
+
+ it('renders author avatar', () => {
+ expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull();
+ });
+
+ describe('external label', () => {
+ it('renders external label when link is external', () => {
+ expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain('external source');
+ });
+
+ it('does not render external label when link is not external', () => {
+ expect(vm.$el.querySelector('.js-assets-list li:nth-child(2) a').textContent).not.toContain(
+ 'external source',
+ );
+ });
+ });
});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 96c0844f83c..b2b0a50911d 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -21,6 +21,16 @@ Vue.config.productionTip = false;
let hasVueWarnings = false;
Vue.config.warnHandler = (msg, vm, trace) => {
+ // The following workaround is necessary, so we are able to use setProps from Vue test utils
+ // see https://github.com/vuejs/vue-test-utils/issues/631#issuecomment-421108344
+ const currentStack = new Error().stack;
+ const isInVueTestUtils = currentStack
+ .split('\n')
+ .some(line => line.startsWith(' at VueWrapper.setProps ('));
+ if (isInVueTestUtils) {
+ return;
+ }
+
hasVueWarnings = true;
fail(`${msg}${trace}`);
};
@@ -187,6 +197,7 @@ if (process.env.BABEL_ENV === 'coverage') {
'./terminal/terminal_bundle.js',
'./users/users_bundle.js',
'./issue_show/index.js',
+ './pages/admin/application_settings/show/index.js',
];
describe('Uncovered files', function() {
diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js
index 6cf8dd81b36..b174a51c1a0 100644
--- a/spec/javascripts/user_popovers_spec.js
+++ b/spec/javascripts/user_popovers_spec.js
@@ -2,6 +2,9 @@ import initUserPopovers from '~/user_popovers';
import UsersCache from '~/lib/utils/users_cache';
describe('User Popovers', () => {
+ const fixtureTemplate = 'merge_requests/diff_comment.html.raw';
+ preloadFixtures(fixtureTemplate);
+
const selector = '.js-user-link';
const dummyUser = { name: 'root' };
@@ -15,11 +18,7 @@ describe('User Popovers', () => {
};
beforeEach(() => {
- setFixtures(`
- <a href="/root" data-user-id="1" class="js-user-link" data-username="root" data-original-title="" title="">
- Root
- </a>
- `);
+ loadFixtures(fixtureTemplate);
const usersCacheSpy = () => Promise.resolve(dummyUser);
spyOn(UsersCache, 'retrieveById').and.callFake(userId => usersCacheSpy(userId));
@@ -31,7 +30,9 @@ describe('User Popovers', () => {
});
it('Should Show+Hide Popover on mouseenter and mouseleave', done => {
- triggerEvent('mouseenter', document.querySelector(selector));
+ const targetLink = document.querySelector(selector);
+ const { userId } = targetLink.dataset;
+ triggerEvent('mouseenter', targetLink);
setTimeout(() => {
const shownPopover = document.querySelector('.popover');
@@ -39,9 +40,9 @@ describe('User Popovers', () => {
expect(shownPopover).not.toBeNull();
expect(shownPopover.innerHTML).toContain(dummyUser.name);
- expect(UsersCache.retrieveById).toHaveBeenCalledWith('1');
+ expect(UsersCache.retrieveById).toHaveBeenCalledWith(userId.toString());
- triggerEvent('mouseleave', document.querySelector(selector));
+ triggerEvent('mouseleave', targetLink);
setTimeout(() => {
// After Mouse leave it should be hidden now
@@ -52,13 +53,15 @@ describe('User Popovers', () => {
});
it('Should Not show a popover on short mouse over', done => {
- triggerEvent('mouseenter', document.querySelector(selector));
+ const targetLink = document.querySelector(selector);
+ const { userId } = targetLink.dataset;
+ triggerEvent('mouseenter', targetLink);
setTimeout(() => {
expect(document.querySelector('.popover')).toBeNull();
- expect(UsersCache.retrieveById).not.toHaveBeenCalledWith('1');
+ expect(UsersCache.retrieveById).not.toHaveBeenCalledWith(userId.toString());
- triggerEvent('mouseleave', document.querySelector(selector));
+ triggerEvent('mouseleave', targetLink);
done();
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index f9cd5c8bd3c..0ddbdf67d8b 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -1,89 +1,154 @@
-import Vue from 'vue';
-import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import $ from 'jquery';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
import { removeBreakLine } from 'spec/helpers/vue_component_helper';
describe('MRWidgetConflicts', () => {
- let Component;
let vm;
const path = '/conflicts';
+ function createComponent(propsData = {}) {
+ const localVue = createLocalVue();
+
+ vm = shallowMount(localVue.extend(ConflictsComponent), {
+ propsData,
+ });
+ }
+
beforeEach(() => {
- Component = Vue.extend(conflictsComponent);
+ spyOn($.fn, 'popover').and.callThrough();
});
afterEach(() => {
- vm.$destroy();
+ vm.destroy();
});
describe('when allowed to merge', () => {
beforeEach(() => {
- vm = mountComponent(Component, {
+ createComponent({
mr: {
canMerge: true,
conflictResolutionPath: path,
+ conflictsDocsPath: '',
},
});
});
it('should tell you about conflicts without bothering other people', () => {
- expect(vm.$el.textContent).toContain('There are merge conflicts');
- expect(vm.$el.textContent).not.toContain('ask someone with write access');
+ expect(vm.text()).toContain('There are merge conflicts');
+ expect(vm.text()).not.toContain('ask someone with write access');
});
it('should allow you to resolve the conflicts', () => {
- const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
+ const resolveButton = vm.find('.js-resolve-conflicts-button');
- expect(resolveButton.textContent).toContain('Resolve conflicts');
- expect(resolveButton.getAttribute('href')).toEqual(path);
+ expect(resolveButton.text()).toContain('Resolve conflicts');
+ expect(resolveButton.attributes('href')).toEqual(path);
});
it('should have merge buttons', () => {
- const mergeButton = vm.$el.querySelector('.js-disabled-merge-button');
- const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button');
+ const mergeLocallyButton = vm.find('.js-merge-locally-button');
- expect(mergeButton.textContent).toContain('Merge');
- expect(mergeButton.disabled).toBeTruthy();
- expect(mergeButton.classList.contains('btn-success')).toEqual(true);
- expect(mergeLocallyButton.textContent).toContain('Merge locally');
+ expect(mergeLocallyButton.text()).toContain('Merge locally');
});
});
describe('when user does not have permission to merge', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
+ it('should show proper message', () => {
+ createComponent({
mr: {
canMerge: false,
+ conflictsDocsPath: '',
},
});
- });
- it('should show proper message', () => {
- expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain(
- 'ask someone with write access',
- );
+ expect(
+ vm
+ .text()
+ .trim()
+ .replace(/\s\s+/g, ' '),
+ ).toContain('ask someone with write access');
});
it('should not have action buttons', () => {
- expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined();
- expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull();
- expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull();
+ createComponent({
+ mr: {
+ canMerge: false,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(vm.contains('.js-resolve-conflicts-button')).toBe(false);
+ expect(vm.contains('.js-merge-locally-button')).toBe(false);
+ });
+
+ it('should not have resolve button when no conflict resolution path', () => {
+ createComponent({
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: null,
+ conflictsDocsPath: '',
+ },
+ });
+
+ expect(vm.contains('.js-resolve-conflicts-button')).toBe(false);
});
});
describe('when fast-forward or semi-linear merge enabled', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
+ it('should tell you to rebase locally', () => {
+ createComponent({
mr: {
shouldBeRebased: true,
+ conflictsDocsPath: '',
},
});
- });
- it('should tell you to rebase locally', () => {
- expect(removeBreakLine(vm.$el.textContent).trim()).toContain(
+ expect(removeBreakLine(vm.text()).trim()).toContain(
'Fast-forward merge is not possible. To merge this request, first rebase locally.',
);
});
});
+
+ describe('when source branch protected', () => {
+ beforeEach(() => {
+ createComponent({
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: gl.TEST_HOST,
+ sourceBranchProtected: true,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('sets resolve button as disabled', () => {
+ expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe('disabled');
+ });
+
+ it('renders popover', () => {
+ expect($.fn.popover).toHaveBeenCalled();
+ });
+ });
+
+ describe('when source branch not protected', () => {
+ beforeEach(() => {
+ createComponent({
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: gl.TEST_HOST,
+ sourceBranchProtected: false,
+ conflictsDocsPath: '',
+ },
+ });
+ });
+
+ it('sets resolve button as disabled', () => {
+ expect(vm.find('.js-resolve-conflicts-button').attributes('disabled')).toBe(undefined);
+ });
+
+ it('renders popover', () => {
+ expect($.fn.popover).not.toHaveBeenCalled();
+ });
+ });
});
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 d46ad0acc9b..b9718a78fa4 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
@@ -121,14 +121,14 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
expect(vm.$el.innerText).toContain('to be merged automatically when the pipeline succeeds');
expect(vm.$el.innerText).toContain('The changes will be merged into');
expect(vm.$el.innerText).toContain(targetBranch);
- expect(vm.$el.innerText).toContain('The source branch will not be removed');
+ expect(vm.$el.innerText).toContain('The source branch will not be deleted');
expect(vm.$el.querySelector('.js-cancel-auto-merge').innerText).toContain(
'Cancel automatic merge',
);
expect(vm.$el.querySelector('.js-cancel-auto-merge').getAttribute('disabled')).toBeFalsy();
expect(vm.$el.querySelector('.js-remove-source-branch').innerText).toContain(
- 'Remove source branch',
+ 'Delete source branch',
);
expect(vm.$el.querySelector('.js-remove-source-branch').getAttribute('disabled')).toBeFalsy();
@@ -143,19 +143,19 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
});
});
- it('should show source branch will be removed text when it source branch set to remove', done => {
+ it('should show source branch will be deleted text when it source branch set to remove', done => {
vm.mr.shouldRemoveSourceBranch = true;
Vue.nextTick(() => {
const normalizedText = vm.$el.innerText.replace(/\s+/g, ' ');
- expect(normalizedText).toContain('The source branch will be removed');
- expect(normalizedText).not.toContain('The source branch will not be removed');
+ expect(normalizedText).toContain('The source branch will be deleted');
+ expect(normalizedText).not.toContain('The source branch will not be deleted');
done();
});
});
- it('should not show remove source branch button when user not able to remove source branch', done => {
+ it('should not show delete source branch button when user not able to delete source branch', done => {
vm.mr.currentUserId = 4;
Vue.nextTick(() => {
@@ -164,7 +164,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
});
});
- it('should disable remove source branch button when the action is in progress', done => {
+ it('should disable delete source branch button when the action is in progress', done => {
vm.isRemovingSourceBranch = true;
Vue.nextTick(() => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
index da5cb752c6f..1683da805b9 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
@@ -128,7 +128,7 @@ describe('MRWidgetMerged', () => {
new Promise(resolve => {
resolve({
data: {
- message: 'Branch was removed',
+ message: 'Branch was deleted',
},
});
}),
@@ -157,8 +157,8 @@ describe('MRWidgetMerged', () => {
expect(vm.$el.textContent).toContain(targetBranch);
});
- it('renders information about branch being removed', () => {
- expect(vm.$el.textContent).toContain('The source branch has been removed');
+ it('renders information about branch being deleted', () => {
+ expect(vm.$el.textContent).toContain('The source branch has been deleted');
});
it('shows revert and cherry-pick buttons', () => {
@@ -189,24 +189,24 @@ describe('MRWidgetMerged', () => {
expect(selectors.mergeCommitShaLink.href).toBe(vm.mr.mergeCommitPath);
});
- it('should not show source branch removed text', done => {
+ it('should not show source branch deleted text', done => {
vm.mr.sourceBranchRemoved = false;
Vue.nextTick(() => {
- expect(vm.$el.innerText).toContain('You can remove source branch now');
- expect(vm.$el.innerText).not.toContain('The source branch has been removed');
+ expect(vm.$el.innerText).toContain('You can delete the source branch now');
+ expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
done();
});
});
- it('should show source branch removing text', done => {
+ it('should show source branch deleting text', done => {
vm.mr.isRemovingSourceBranch = true;
vm.mr.sourceBranchRemoved = false;
Vue.nextTick(() => {
- expect(vm.$el.innerText).toContain('The source branch is being removed');
- expect(vm.$el.innerText).not.toContain('You can remove source branch now');
- expect(vm.$el.innerText).not.toContain('The source branch has been removed');
+ expect(vm.$el.innerText).toContain('The source branch is being deleted');
+ expect(vm.$el.innerText).not.toContain('You can delete the source branch now');
+ expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
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 2119a3b927a..e387367d1a2 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
@@ -1,11 +1,12 @@
import Vue from 'vue';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
+import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
const commitMessage = 'This is the commit message';
const commitMessageWithDescription = 'This is the commit message description';
-const createComponent = (customConfig = {}) => {
- const Component = Vue.extend(ReadyToMerge);
+const createTestMr = customConfig => {
const mr = {
isPipelineActive: false,
pipeline: null,
@@ -16,6 +17,7 @@ const createComponent = (customConfig = {}) => {
hasCI: false,
ciStatus: null,
sha: '12345678',
+ squash: false,
commitMessage,
commitMessageWithDescription,
shouldRemoveSourceBranch: true,
@@ -24,14 +26,23 @@ const createComponent = (customConfig = {}) => {
Object.assign(mr, customConfig.mr);
- const service = {
- merge() {},
- poll() {},
- };
+ return mr;
+};
+
+const createTestService = () => ({
+ merge() {},
+ poll() {},
+});
+
+const createComponent = (customConfig = {}) => {
+ const Component = Vue.extend(ReadyToMerge);
return new Component({
el: document.createElement('div'),
- propsData: { mr, service },
+ propsData: {
+ mr: createTestMr(customConfig),
+ service: createTestService(),
+ },
});
};
@@ -612,6 +623,47 @@ describe('ReadyToMerge', () => {
});
});
+ describe('Squash checkbox component', () => {
+ let wrapper;
+ const localVue = createLocalVue();
+
+ const createLocalComponent = (customConfig = {}) => {
+ wrapper = shallowMount(localVue.extend(ReadyToMerge), {
+ localVue,
+ propsData: {
+ mr: createTestMr(customConfig),
+ service: createTestService(),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findCheckboxElement = () => wrapper.find(SquashBeforeMerge);
+
+ it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => {
+ createLocalComponent({
+ mr: { commitsCount: 2, enableSquashBeforeMerge: true },
+ });
+
+ expect(findCheckboxElement().exists()).toBeTruthy();
+ });
+
+ it('should not be rendered when squash before merge is disabled', () => {
+ createLocalComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } });
+
+ expect(findCheckboxElement().exists()).toBeFalsy();
+ });
+
+ it('should not be rendered when there is only 1 commit', () => {
+ createLocalComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } });
+
+ expect(findCheckboxElement().exists()).toBeFalsy();
+ });
+ });
+
describe('Merge controls', () => {
describe('when allowed to merge', () => {
beforeEach(() => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
new file mode 100644
index 00000000000..d6d8eecfcb9
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -0,0 +1,100 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
+
+const localVue = createLocalVue();
+
+describe('Squash before merge component', () => {
+ let wrapper;
+
+ const createComponent = props => {
+ wrapper = shallowMount(localVue.extend(SquashBeforeMerge), {
+ localVue,
+ sync: false,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('checkbox', () => {
+ const findCheckbox = () => wrapper.find('.qa-squash-checkbox');
+
+ it('is unchecked if passed value prop is false', () => {
+ createComponent({
+ value: false,
+ });
+
+ expect(findCheckbox().element.checked).toBeFalsy();
+ });
+
+ it('is checked if passed value prop is true', () => {
+ createComponent({
+ value: true,
+ });
+
+ expect(findCheckbox().element.checked).toBeTruthy();
+ });
+
+ it('changes value on click', done => {
+ createComponent({
+ value: false,
+ });
+
+ findCheckbox().element.checked = true;
+
+ findCheckbox().trigger('change');
+
+ wrapper.vm.$nextTick(() => {
+ expect(findCheckbox().element.checked).toBeTruthy();
+ done();
+ });
+ });
+
+ it('is disabled if isDisabled prop is true', () => {
+ createComponent({
+ value: false,
+ isDisabled: true,
+ });
+
+ expect(findCheckbox().attributes('disabled')).toBeTruthy();
+ });
+ });
+
+ describe('about link', () => {
+ it('is not rendered if no help path is passed', () => {
+ createComponent({
+ value: false,
+ });
+
+ const aboutLink = wrapper.find('a');
+
+ expect(aboutLink.exists()).toBeFalsy();
+ });
+
+ it('is rendered if help path is passed', () => {
+ createComponent({
+ value: false,
+ helpPath: 'test-path',
+ });
+
+ const aboutLink = wrapper.find('a');
+
+ expect(aboutLink.exists()).toBeTruthy();
+ });
+
+ it('should have a correct help path if passed', () => {
+ createComponent({
+ value: false,
+ helpPath: 'test-path',
+ });
+
+ const aboutLink = wrapper.find('a');
+
+ expect(aboutLink.attributes('href')).toEqual('test-path');
+ });
+ });
+});
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 99b80df766a..ff08a46b922 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -5,7 +5,7 @@ import notify from '~/lib/utils/notify';
import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './mock_data';
-import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from '../lib/utils/mock_data';
+import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
const returnPromise = data =>
new Promise(resolve => {
@@ -340,17 +340,27 @@ describe('mrWidgetOptions', () => {
vm.mr.ciStatusFaviconPath = overlayDataUrl;
vm.setFaviconHelper()
.then(() => {
- expect(faviconElement.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
+ /*
+ It would be better if we'd could mock commonUtils.setFaviconURL
+ with a spy and test that it was called. We are doing the following
+ tests as a proxy to show that the function has been called
+ */
+ expect(faviconElement.getAttribute('href')).not.toEqual(null);
+ expect(faviconElement.getAttribute('href')).not.toEqual(overlayDataUrl);
+ expect(faviconElement.getAttribute('href')).not.toEqual(faviconDataUrl);
done();
})
.catch(done.fail);
});
- it('should not call setFavicon when there is no ciStatusFaviconPath', () => {
+ it('should not call setFavicon when there is no ciStatusFaviconPath', done => {
vm.mr.ciStatusFaviconPath = null;
- vm.setFaviconHelper();
-
- expect(faviconElement.getAttribute('href')).toEqual(null);
+ vm.setFaviconHelper()
+ .then(() => {
+ expect(faviconElement.getAttribute('href')).toEqual(null);
+ done();
+ })
+ .catch(done.fail);
});
});
@@ -453,7 +463,7 @@ describe('mrWidgetOptions', () => {
vm.$nextTick(() => {
const tooltip = vm.$el.querySelector('.fa-question-circle');
- expect(vm.$el.textContent).toContain('Removes source branch');
+ expect(vm.$el.textContent).toContain('Deletes source branch');
expect(tooltip.getAttribute('data-original-title')).toBe(
'A user with write access to the source branch selected this option',
);
@@ -468,8 +478,8 @@ describe('mrWidgetOptions', () => {
vm.mr.state = 'merged';
vm.$nextTick(() => {
- expect(vm.$el.textContent).toContain('The source branch has been removed');
- expect(vm.$el.textContent).not.toContain('Removes source branch');
+ expect(vm.$el.textContent).toContain('The source branch has been deleted');
+ expect(vm.$el.textContent).not.toContain('Deletes source branch');
done();
});
diff --git a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
index 61ef26cd080..b356ea85cad 100644
--- a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
+++ b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
@@ -76,4 +76,28 @@ describe('getStateKey', () => {
expect(bound()).toEqual('archived');
});
+
+ it('returns rebased state key', () => {
+ const context = {
+ mergeStatus: 'checked',
+ mergeWhenPipelineSucceeds: false,
+ canMerge: true,
+ onlyAllowMergeIfPipelineSucceeds: true,
+ isPipelineFailed: true,
+ hasMergeableDiscussionsState: false,
+ isPipelineBlocked: false,
+ canBeMerged: false,
+ shouldBeRebased: true,
+ };
+ const data = {
+ project_archived: false,
+ branch_missing: false,
+ commits_count: 2,
+ has_conflicts: false,
+ work_in_progress: false,
+ };
+ const bound = getStateKey.bind(context, data);
+
+ expect(bound()).toEqual('rebase');
+ });
});
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
index 67752c1c455..d1fd899c1a8 100644
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -3,7 +3,7 @@ import FileRow from '~/vue_shared/components/file_row.vue';
import { file } from 'spec/ide/helpers';
import mountComponent from '../../helpers/vue_mount_component_helper';
-describe('RepoFile', () => {
+describe('File row component', () => {
let vm;
function createComponent(propsData) {
@@ -72,39 +72,16 @@ describe('RepoFile', () => {
expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px');
});
- describe('outputText', () => {
- beforeEach(done => {
- createComponent({
- file: {
- ...file(),
- path: 'app/assets/index.js',
- },
- level: 0,
- });
-
- vm.displayTextKey = 'path';
-
- vm.$nextTick(done);
- });
-
- it('returns text if truncateStart is 0', done => {
- vm.truncateStart = 0;
-
- vm.$nextTick(() => {
- expect(vm.outputText).toBe('app/assets/index.js');
-
- done();
- });
+ it('renders header for file', () => {
+ createComponent({
+ file: {
+ isHeader: true,
+ path: 'app/assets',
+ tree: [],
+ },
+ level: 0,
});
- it('returns text truncated at start', done => {
- vm.truncateStart = 5;
-
- vm.$nextTick(() => {
- expect(vm.outputText).toBe('...ssets/index.js');
-
- done();
- });
- });
+ expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null);
});
});
diff --git a/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js
index b84b5ae67a8..3d251426b5a 100644
--- a/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js
+++ b/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js
@@ -88,4 +88,103 @@ describe('Filtered search dropdown', () => {
});
});
});
+
+ describe('with create mode enabled', () => {
+ describe('when there are no matches', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [
+ { title: 'One' },
+ { title: 'Two/three' },
+ { title: 'Three four' },
+ { title: 'Five' },
+ ],
+ filterKey: 'title',
+ showCreateMode: true,
+ });
+
+ vm.$el.querySelector('.js-filtered-dropdown-input').value = 'eleven';
+ vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input'));
+ });
+
+ it('renders a create button', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.js-dropdown-create-button')).not.toBeNull();
+ done();
+ });
+ });
+
+ it('renders computed button text', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.js-dropdown-create-button').textContent.trim()).toEqual(
+ 'Create eleven',
+ );
+ done();
+ });
+ });
+
+ describe('on click create button', () => {
+ it('emits createItem event with the filter', done => {
+ spyOn(vm, '$emit');
+ vm.$nextTick(() => {
+ vm.$el.querySelector('.js-dropdown-create-button').click();
+
+ expect(vm.$emit).toHaveBeenCalledWith('createItem', 'eleven');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('when there are matches', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [
+ { title: 'One' },
+ { title: 'Two/three' },
+ { title: 'Three four' },
+ { title: 'Five' },
+ ],
+ filterKey: 'title',
+ showCreateMode: true,
+ });
+
+ vm.$el.querySelector('.js-filtered-dropdown-input').value = 'one';
+ vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input'));
+ });
+
+ it('does not render a create button', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.js-dropdown-create-button')).toBeNull();
+ done();
+ });
+ });
+ });
+ });
+
+ describe('with create mode disabled', () => {
+ describe('when there are no matches', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ items: [
+ { title: 'One' },
+ { title: 'Two/three' },
+ { title: 'Three four' },
+ { title: 'Five' },
+ ],
+ filterKey: 'title',
+ });
+
+ vm.$el.querySelector('.js-filtered-dropdown-input').value = 'eleven';
+ vm.$el.querySelector('.js-filtered-dropdown-input').dispatchEvent(new Event('input'));
+ });
+
+ it('does not render a create button', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.js-dropdown-create-button')).toBeNull();
+ done();
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js b/spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js
new file mode 100644
index 00000000000..eb78d37db3e
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/gl_modal_vuex_spec.js
@@ -0,0 +1,151 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { GlModal } from '@gitlab/ui';
+import GlModalVuex from '~/vue_shared/components/gl_modal_vuex.vue';
+import createState from '~/vuex_shared/modules/modal/state';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+const TEST_SLOT = 'Lorem ipsum modal dolar sit.';
+const TEST_MODAL_ID = 'my-modal-id';
+const TEST_MODULE = 'myModal';
+
+describe('GlModalVuex', () => {
+ let wrapper;
+ let state;
+ let actions;
+
+ const factory = (options = {}) => {
+ const store = new Vuex.Store({
+ modules: {
+ [TEST_MODULE]: {
+ namespaced: true,
+ state,
+ actions,
+ },
+ },
+ });
+
+ const propsData = {
+ modalId: TEST_MODAL_ID,
+ modalModule: TEST_MODULE,
+ ...options.propsData,
+ };
+
+ wrapper = shallowMount(localVue.extend(GlModalVuex), {
+ ...options,
+ localVue,
+ store,
+ propsData,
+ });
+ };
+
+ beforeEach(() => {
+ state = createState();
+
+ actions = {
+ show: jasmine.createSpy('show'),
+ hide: jasmine.createSpy('hide'),
+ };
+ });
+
+ it('renders gl-modal', () => {
+ factory({
+ slots: {
+ default: `<div>${TEST_SLOT}</div>`,
+ },
+ });
+ const glModal = wrapper.find(GlModal);
+
+ expect(glModal.props('modalId')).toBe(TEST_MODAL_ID);
+ expect(glModal.text()).toContain(TEST_SLOT);
+ });
+
+ it('passes props through to gl-modal', () => {
+ const title = 'Test Title';
+ const okVariant = 'success';
+
+ factory({
+ propsData: {
+ title,
+ okTitle: title,
+ okVariant,
+ },
+ });
+ const glModal = wrapper.find(GlModal);
+
+ expect(glModal.attributes('title')).toEqual(title);
+ expect(glModal.attributes('oktitle')).toEqual(title);
+ expect(glModal.attributes('okvariant')).toEqual(okVariant);
+ });
+
+ it('passes listeners through to gl-modal', () => {
+ const ok = jasmine.createSpy('ok');
+
+ factory({
+ listeners: { ok },
+ });
+
+ const glModal = wrapper.find(GlModal);
+ glModal.vm.$emit('ok');
+
+ expect(ok).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls vuex action on show', () => {
+ expect(actions.show).not.toHaveBeenCalled();
+
+ factory();
+
+ const glModal = wrapper.find(GlModal);
+ glModal.vm.$emit('shown');
+
+ expect(actions.show).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls vuex action on hide', () => {
+ expect(actions.hide).not.toHaveBeenCalled();
+
+ factory();
+
+ const glModal = wrapper.find(GlModal);
+ glModal.vm.$emit('hidden');
+
+ expect(actions.hide).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls bootstrap show when isVisible changes', done => {
+ state.isVisible = false;
+
+ factory();
+ const rootEmit = spyOn(wrapper.vm.$root, '$emit');
+
+ state.isVisible = true;
+
+ localVue
+ .nextTick()
+ .then(() => {
+ expect(rootEmit).toHaveBeenCalledWith('bv::show::modal', TEST_MODAL_ID);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('calls bootstrap hide when isVisible changes', done => {
+ state.isVisible = true;
+
+ factory();
+ const rootEmit = spyOn(wrapper.vm.$root, '$emit');
+
+ state.isVisible = false;
+
+ localVue
+ .nextTick()
+ .then(() => {
+ expect(rootEmit).toHaveBeenCalledWith('bv::hide::modal', TEST_MODAL_ID);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
index 8187b3204b1..12ee804f668 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -31,6 +31,12 @@ describe('Suggestion Diff component', () => {
expect(header.innerHTML.includes('Suggested change')).toBe(true);
});
+ it('renders a help button', () => {
+ const helpBtn = vm.$el.querySelector('.js-help-btn');
+
+ expect(helpBtn).not.toBeNull();
+ });
+
it('renders an apply button', () => {
const applyBtn = vm.$el.querySelector('.qa-apply-btn');
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
index d4ed8f2f7a4..f87c2a92f47 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -34,8 +34,8 @@ describe('Suggestion Diff component', () => {
expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull();
});
- it('renders a diff table', () => {
- expect(vm.$el.querySelector('table.md-suggestion-diff')).not.toBeNull();
+ it('renders a diff table with syntax highlighting', () => {
+ expect(vm.$el.querySelector('.md-suggestion-diff.js-syntax-highlight.code')).not.toBeNull();
});
it('renders the oldLineNumber', () => {
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
index ab1b747c360..33be63a3a1e 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
@@ -29,7 +29,7 @@ const generateLine = content => {
const generateMockLines = () => {
const line1 = generateLine('Line 1');
const line2 = generateLine('Line 2');
- const line3 = generateLine('Line 3');
+ const line3 = generateLine('- Line 3');
const container = document.createElement('div');
container.appendChild(line1);
@@ -61,7 +61,7 @@ describe('Suggestion component', () => {
describe('mounted', () => {
it('renders a flash container', () => {
- expect(vm.$el.querySelector('.flash-container')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-suggestions-flash')).not.toBeNull();
});
it('renders a container for suggestions', () => {
@@ -80,7 +80,7 @@ describe('Suggestion component', () => {
const expectedReturn = [
{ content: 'Line 1\n', lineNumber: 1 },
{ content: 'Line 2\n', lineNumber: 2 },
- { content: 'Line 3\n', lineNumber: 3 },
+ { content: '- Line 3\n', lineNumber: 3 },
];
expect(vm.extractNewLines(generateMockLines())).toEqual(expectedReturn);
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js
new file mode 100644
index 00000000000..96bc3b0cc17
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_list_spec.js
@@ -0,0 +1,156 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import { TEST_HOST } from 'spec/test_constants';
+import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+
+const TEST_IMAGE_SIZE = 7;
+const TEST_BREAKPOINT = 5;
+const TEST_EMPTY_MESSAGE = 'Lorem ipsum empty';
+const DEFAULT_EMPTY_MESSAGE = 'None';
+
+const createUser = id => ({
+ id,
+ name: 'Lorem',
+ web_url: `${TEST_HOST}/${id}`,
+ avatar_url: `${TEST_HOST}/${id}/avatar`,
+});
+const createList = n =>
+ Array(n)
+ .fill(1)
+ .map((x, id) => createUser(id));
+
+const localVue = createLocalVue();
+
+describe('UserAvatarList', () => {
+ let props;
+ let wrapper;
+
+ const factory = (options = {}) => {
+ const propsData = {
+ ...props,
+ ...options.propsData,
+ };
+
+ wrapper = shallowMount(localVue.extend(UserAvatarList), {
+ ...options,
+ localVue,
+ propsData,
+ });
+ };
+
+ const clickButton = () => {
+ const button = wrapper.find(GlButton);
+ button.vm.$emit('click');
+ };
+
+ beforeEach(() => {
+ props = { imgSize: TEST_IMAGE_SIZE };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('empty text', () => {
+ it('shows when items are empty', () => {
+ factory({ propsData: { items: [] } });
+
+ expect(wrapper.text()).toContain(DEFAULT_EMPTY_MESSAGE);
+ });
+
+ it('does not show when items are not empty', () => {
+ factory({ propsData: { items: createList(1) } });
+
+ expect(wrapper.text()).not.toContain(DEFAULT_EMPTY_MESSAGE);
+ });
+
+ it('can be set in props', () => {
+ factory({ propsData: { items: [], emptyText: TEST_EMPTY_MESSAGE } });
+
+ expect(wrapper.text()).toContain(TEST_EMPTY_MESSAGE);
+ });
+ });
+
+ describe('with no breakpoint', () => {
+ beforeEach(() => {
+ props.breakpoint = 0;
+ });
+
+ it('renders avatars', () => {
+ const items = createList(20);
+ factory({ propsData: { items } });
+
+ const links = wrapper.findAll(UserAvatarLink);
+ const linkProps = links.wrappers.map(x => x.props());
+
+ expect(linkProps).toEqual(
+ items.map(x =>
+ jasmine.objectContaining({
+ linkHref: x.web_url,
+ imgSrc: x.avatar_url,
+ imgAlt: x.name,
+ tooltipText: x.name,
+ imgSize: TEST_IMAGE_SIZE,
+ }),
+ ),
+ );
+ });
+ });
+
+ describe('with breakpoint and length equal to breakpoint', () => {
+ beforeEach(() => {
+ props.breakpoint = TEST_BREAKPOINT;
+ props.items = createList(TEST_BREAKPOINT);
+ });
+
+ it('renders all avatars if length is <= breakpoint', () => {
+ factory();
+
+ const links = wrapper.findAll(UserAvatarLink);
+
+ expect(links.length).toEqual(props.items.length);
+ });
+
+ it('does not show button', () => {
+ factory();
+
+ expect(wrapper.find(GlButton).exists()).toBe(false);
+ });
+ });
+
+ describe('with breakpoint and length greater than breakpoint', () => {
+ beforeEach(() => {
+ props.breakpoint = TEST_BREAKPOINT;
+ props.items = createList(TEST_BREAKPOINT + 1);
+ });
+
+ it('renders avatars up to breakpoint', () => {
+ factory();
+
+ const links = wrapper.findAll(UserAvatarLink);
+
+ expect(links.length).toEqual(TEST_BREAKPOINT);
+ });
+
+ describe('with expand clicked', () => {
+ beforeEach(() => {
+ factory();
+ clickButton();
+ });
+
+ it('renders all avatars', () => {
+ const links = wrapper.findAll(UserAvatarLink);
+
+ expect(links.length).toEqual(props.items.length);
+ });
+
+ it('with collapse clicked, it renders avatars up to breakpoint', () => {
+ clickButton();
+ const links = wrapper.findAll(UserAvatarLink);
+
+ expect(links.length).toEqual(TEST_BREAKPOINT);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
index 25b6e3b6bc8..de3e0c149de 100644
--- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
@@ -17,14 +17,13 @@ const DEFAULT_PROPS = {
const UserPopover = Vue.extend(userPopover);
describe('User Popover Component', () => {
+ const fixtureTemplate = 'merge_requests/diff_comment.html.raw';
+ preloadFixtures(fixtureTemplate);
+
let vm;
beforeEach(() => {
- setFixtures(`
- <a href="/root" data-user-id="1" class="js-user-link" title="testuser">
- Root
- </a>
- `);
+ loadFixtures(fixtureTemplate);
});
afterEach(() => {
diff --git a/spec/javascripts/vuex_shared/modules/modal/actions_spec.js b/spec/javascripts/vuex_shared/modules/modal/actions_spec.js
new file mode 100644
index 00000000000..04f64663ae4
--- /dev/null
+++ b/spec/javascripts/vuex_shared/modules/modal/actions_spec.js
@@ -0,0 +1,31 @@
+import * as types from '~/vuex_shared/modules/modal/mutation_types';
+import * as actions from '~/vuex_shared/modules/modal/actions';
+import testAction from 'spec/helpers/vuex_action_helper';
+
+describe('Vuex ModalModule actions', () => {
+ describe('open', () => {
+ it('works', done => {
+ const data = { id: 7 };
+
+ testAction(actions.open, data, {}, [{ type: types.OPEN, payload: data }], [], done);
+ });
+ });
+
+ describe('close', () => {
+ it('works', done => {
+ testAction(actions.close, null, {}, [{ type: types.CLOSE }], [], done);
+ });
+ });
+
+ describe('show', () => {
+ it('works', done => {
+ testAction(actions.show, null, {}, [{ type: types.SHOW }], [], done);
+ });
+ });
+
+ describe('hide', () => {
+ it('works', done => {
+ testAction(actions.hide, null, {}, [{ type: types.HIDE }], [], done);
+ });
+ });
+});
diff --git a/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js b/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js
new file mode 100644
index 00000000000..d07f8ba1e65
--- /dev/null
+++ b/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js
@@ -0,0 +1,49 @@
+import mutations from '~/vuex_shared/modules/modal/mutations';
+import * as types from '~/vuex_shared/modules/modal/mutation_types';
+
+describe('Vuex ModalModule mutations', () => {
+ describe(types.SHOW, () => {
+ it('sets isVisible to true', () => {
+ const state = {
+ isVisible: false,
+ };
+
+ mutations[types.SHOW](state);
+
+ expect(state).toEqual({
+ isVisible: true,
+ });
+ });
+ });
+
+ describe(types.HIDE, () => {
+ it('sets isVisible to false', () => {
+ const state = {
+ isVisible: true,
+ };
+
+ mutations[types.HIDE](state);
+
+ expect(state).toEqual({
+ isVisible: false,
+ });
+ });
+ });
+
+ describe(types.OPEN, () => {
+ it('sets data and sets isVisible to true', () => {
+ const data = { id: 7 };
+ const state = {
+ isVisible: false,
+ data: null,
+ };
+
+ mutations[types.OPEN](state, data);
+
+ expect(state).toEqual({
+ isVisible: true,
+ data,
+ });
+ });
+ });
+});
diff --git a/spec/lib/api/api_spec.rb b/spec/lib/api/api_spec.rb
new file mode 100644
index 00000000000..ceef0b41e59
--- /dev/null
+++ b/spec/lib/api/api_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe API::API do
+ describe '.prefix' do
+ it 'has a prefix defined' do
+ expect(described_class.prefix).to eq :api
+ end
+ end
+
+ describe '.version' do
+ it 'uses most recent version of the API' do
+ expect(described_class.version).to eq 'v4'
+ end
+ end
+
+ describe '.versions' do
+ it 'returns all available versions' do
+ expect(described_class.versions).to eq %w[v3 v4]
+ end
+ end
+end
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index 0a7682d906b..2890aa4ae38 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -237,26 +237,89 @@ describe API::Helpers::Pagination do
.and_return({ page: 1, per_page: 2 })
end
- it 'returns appropriate amount of resources' do
- expect(subject.paginate(resource).count).to eq 2
+ shared_examples 'response with pagination headers' do
+ it 'adds appropriate headers' do
+ expect_header('X-Total', '3')
+ expect_header('X-Total-Pages', '2')
+ expect_header('X-Per-Page', '2')
+ expect_header('X-Page', '1')
+ expect_header('X-Next-Page', '2')
+ expect_header('X-Prev-Page', '')
+
+ expect_header('Link', anything) do |_key, val|
+ expect(val).to include('rel="first"')
+ expect(val).to include('rel="last"')
+ expect(val).to include('rel="next"')
+ expect(val).not_to include('rel="prev"')
+ end
+
+ subject.paginate(resource)
+ end
end
- it 'adds appropriate headers' do
- expect_header('X-Total', '3')
- expect_header('X-Total-Pages', '2')
- expect_header('X-Per-Page', '2')
- expect_header('X-Page', '1')
- expect_header('X-Next-Page', '2')
- expect_header('X-Prev-Page', '')
+ shared_examples 'paginated response' do
+ it 'returns appropriate amount of resources' do
+ expect(subject.paginate(resource).count).to eq 2
+ end
- expect_header('Link', anything) do |_key, val|
- expect(val).to include('rel="first"')
- expect(val).to include('rel="last"')
- expect(val).to include('rel="next"')
- expect(val).not_to include('rel="prev"')
+ it 'executes only one SELECT COUNT query' do
+ expect { subject.paginate(resource) }.to make_queries_matching(/SELECT COUNT/, 1)
end
+ end
- subject.paginate(resource)
+ context 'when the api_kaminari_count_with_limit feature flag is unset' do
+ it_behaves_like 'paginated response'
+ it_behaves_like 'response with pagination headers'
+ end
+
+ context 'when the api_kaminari_count_with_limit feature flag is disabled' do
+ before do
+ stub_feature_flags(api_kaminari_count_with_limit: false)
+ end
+
+ it_behaves_like 'paginated response'
+ it_behaves_like 'response with pagination headers'
+ end
+
+ context 'when the api_kaminari_count_with_limit feature flag is enabled' do
+ before do
+ stub_feature_flags(api_kaminari_count_with_limit: true)
+ end
+
+ context 'when resources count is less than MAX_COUNT_LIMIT' do
+ before do
+ stub_const("::Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT", 4)
+ end
+
+ it_behaves_like 'paginated response'
+ it_behaves_like 'response with pagination headers'
+ end
+
+ context 'when resources count is more than MAX_COUNT_LIMIT' do
+ before do
+ stub_const("::Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT", 2)
+ end
+
+ it_behaves_like 'paginated response'
+
+ it 'does not return the X-Total and X-Total-Pages headers' do
+ expect_no_header('X-Total')
+ expect_no_header('X-Total-Pages')
+ expect_header('X-Per-Page', '2')
+ expect_header('X-Page', '1')
+ expect_header('X-Next-Page', '2')
+ expect_header('X-Prev-Page', '')
+
+ expect_header('Link', anything) do |_key, val|
+ expect(val).to include('rel="first"')
+ expect(val).not_to include('rel="last"')
+ expect(val).to include('rel="next"')
+ expect(val).not_to include('rel="prev"')
+ end
+
+ subject.paginate(resource)
+ end
+ end
end
end
@@ -348,6 +411,10 @@ describe API::Helpers::Pagination do
expect(subject).to receive(:header).with(*args, &block)
end
+ def expect_no_header(*args, &block)
+ expect(subject).not_to receive(:header).with(*args)
+ end
+
def expect_message(method)
expect(subject).to receive(method)
.at_least(:once).and_return(value)
diff --git a/spec/lib/api/helpers/version_spec.rb b/spec/lib/api/helpers/version_spec.rb
new file mode 100644
index 00000000000..34006e0930b
--- /dev/null
+++ b/spec/lib/api/helpers/version_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe API::Helpers::Version do
+ describe '.new' do
+ it 'is possible to initialize it with existing API version' do
+ expect(described_class.new('v4').to_s).to eq 'v4'
+ end
+
+ it 'raises an error when unsupported API version is provided' do
+ expect { described_class.new('v111') }.to raise_error ArgumentError
+ end
+ end
+
+ describe '#root_path' do
+ it 'returns a root path of the API version' do
+ expect(described_class.new('v4').root_path).to eq '/api/v4'
+ end
+ end
+
+ describe '#root_url' do
+ it 'returns an URL for a root path for the API version' do
+ expect(described_class.new('v4').root_url)
+ .to eq 'http://localhost/api/v4'
+ end
+ end
+end
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index 1c73a936e17..e1aea82653d 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -150,32 +150,36 @@ describe API::Helpers do
end
describe '#send_git_blob' do
- context 'content disposition' do
- let(:repository) { double }
- let(:blob) { double(name: 'foobar') }
+ let(:repository) { double }
+ let(:blob) { double(name: 'foobar') }
- let(:send_git_blob) do
- subject.send(:send_git_blob, repository, blob)
- end
+ let(:send_git_blob) do
+ subject.send(:send_git_blob, repository, blob)
+ end
- before do
- allow(subject).to receive(:env).and_return({})
- allow(subject).to receive(:content_type)
- allow(subject).to receive(:header).and_return({})
- allow(Gitlab::Workhorse).to receive(:send_git_blob)
- end
+ before do
+ allow(subject).to receive(:env).and_return({})
+ allow(subject).to receive(:content_type)
+ allow(subject).to receive(:header).and_return({})
+ allow(Gitlab::Workhorse).to receive(:send_git_blob)
+ end
+
+ it 'sets Gitlab::Workhorse::DETECT_HEADER header' do
+ expect(send_git_blob[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
+ end
+ context 'content disposition' do
context 'when blob name is null' do
let(:blob) { double(name: nil) }
it 'returns only the disposition' do
- expect(send_git_blob['Content-Disposition']).to eq 'attachment'
+ expect(send_git_blob['Content-Disposition']).to eq 'inline'
end
end
context 'when blob name is not null' do
it 'returns disposition with the blob name' do
- expect(send_git_blob['Content-Disposition']).to eq 'attachment; filename="foobar"'
+ expect(send_git_blob['Content-Disposition']).to eq 'inline; filename="foobar"'
end
end
end
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index 9633caac788..ae1c881e1f6 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -266,6 +266,7 @@ describe Backup::Manager do
remote_directory: 'directory',
multipart_chunk_size: 104857600,
encryption: nil,
+ encryption_key: nil,
storage_class: nil
}
)
diff --git a/spec/lib/banzai/color_parser_spec.rb b/spec/lib/banzai/color_parser_spec.rb
index a1cb0c07b06..af2a8f215c1 100644
--- a/spec/lib/banzai/color_parser_spec.rb
+++ b/spec/lib/banzai/color_parser_spec.rb
@@ -57,7 +57,7 @@ describe Banzai::ColorParser do
context 'HSL format' do
[
- 'hsl(0,0%,0%)', 'hsl(0,100%,100%)',
+ 'hsl(0,0%,0%)', 'hsl(0,100%,100%)',
'hsl(540,0%,0%)', 'hsl(-720,0%,0%)',
'hsl(0deg,0%,0%)', 'hsl(0DEG,0%,0%)',
'hsl(0, 0%, 0%)', 'HSL(0,0%,0%)',
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index cbff2fdab14..4daf6be1bb7 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -205,7 +205,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
context 'cross-project URL reference' do
let(:namespace) { create(:namespace) }
let(:project2) { create(:project, :public, :repository, namespace: namespace) }
- let(:range) { CommitRange.new("#{commit1.id}...master", project) }
+ let(:range) { CommitRange.new("#{commit1.id}...master", project) }
let(:reference) { urls.project_compare_url(project2, from: commit1.id, to: 'master') }
before do
diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb
new file mode 100644
index 00000000000..2e50e4e2351
--- /dev/null
+++ b/spec/lib/banzai/filter/footnote_filter_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::FootnoteFilter do
+ include FilterSpecHelper
+
+ # first[^1] and second[^second]
+ # [^1]: one
+ # [^second]: two
+ let(:footnote) do
+ <<~EOF
+ <p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p>
+ <ol>
+ <li id="fn1">
+ <p>one <a href="#fnref1">↩</a></p>
+ </li>
+ <li id="fn2">
+ <p>two <a href="#fnref2">↩</a></p>
+ </li>
+ </ol>
+ EOF
+ end
+
+ let(:filtered_footnote) do
+ <<~EOF
+ <p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p>
+ <section class="footnotes"><ol>
+ <li id="fn1-#{identifier}">
+ <p>one <a href="#fnref1-#{identifier}" class="footnote-backref">↩</a></p>
+ </li>
+ <li id="fn2-#{identifier}">
+ <p>two <a href="#fnref2-#{identifier}" class="footnote-backref">↩</a></p>
+ </li>
+ </ol></section>
+ EOF
+ end
+
+ context 'when footnotes exist' do
+ let(:doc) { filter(footnote) }
+ let(:link_node) { doc.css('sup > a').first }
+ let(:identifier) { link_node[:id].delete_prefix('fnref1-') }
+
+ it 'properly adds the necessary ids and classes' do
+ expect(doc.to_html).to eq filtered_footnote
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 905fbb9434b..914c4e2d823 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -445,7 +445,7 @@ describe Banzai::Filter::IssueReferenceFilter do
end
describe '.references_in' do
- let(:merge_request) { create(:merge_request) }
+ let(:merge_request) { create(:merge_request) }
it 'yields valid references' do
expect do |b|
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 9cfdb9e53a2..108d7b43a26 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -237,7 +237,7 @@ describe Banzai::Filter::LabelReferenceFilter do
end
context 'References with html entities' do
- let!(:label) { create(:label, name: '&lt;html&gt;', project: project) }
+ let!(:label) { create(:label, name: '&lt;html&gt;', project: project) }
it 'links to a valid reference' do
doc = reference_filter('See ~"&lt;html&gt;"')
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
index 48140305e26..c68d49f9366 100644
--- a/spec/lib/banzai/filter/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -13,7 +13,7 @@ describe Banzai::Filter::ProjectReferenceFilter do
project.to_reference_with_postfix
end
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public) }
subject { project }
let(:subject_name) { "project" }
let(:reference) { get_reference(project) }
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 415ded05e6e..dad0a5535c0 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -353,5 +353,59 @@ describe Banzai::Filter::RelativeLinkFilter do
expect(doc.at_css('a')['href']).to eq 'http://example.com'
end
end
+
+ context 'to a personal snippet' do
+ let(:group) { nil }
+ let(:project) { nil }
+ let(:relative_path) { '/uploads/-/system/personal_snippet/6/674e4f07fbf0a7736c3439212896e51a/example.tar.gz' }
+
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
+
+ it 'rewrites the link correctly' do
+ doc = filter(link(relative_path))
+
+ expect(doc.at_css('a')['href']).to eq(absolute_path)
+ end
+ end
+
+ context 'with a relative URL root' do
+ let(:gitlab_root) { '/gitlab' }
+ let(:absolute_path) { Gitlab.config.gitlab.url + gitlab_root + relative_path }
+
+ before do
+ stub_config_setting(relative_url_root: gitlab_root)
+ end
+
+ context 'with an absolute URL' do
+ let(:only_path) { false }
+
+ it 'rewrites the link correctly' do
+ doc = filter(link(relative_path))
+
+ expect(doc.at_css('a')['href']).to eq(absolute_path)
+ end
+ end
+
+ it 'rewrites the link correctly' do
+ doc = filter(link(relative_path))
+
+ expect(doc.at_css('a')['href']).to eq(gitlab_root + relative_path)
+ end
+ end
+
+ it 'rewrites the link correctly' do
+ doc = filter(link(relative_path))
+
+ expect(doc.at_css('a')['href']).to eq(relative_path)
+ end
+
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'))
+
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+ end
end
end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 0b3c2390304..836af18e0b6 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -246,7 +246,7 @@ describe Banzai::Filter::SanitizationFilter do
'protocol-based JS injection: spaces and entities' => {
input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
- output: '<a href>foo</a>'
+ output: '<a href="">foo</a>'
},
'protocol whitespace' => {
@@ -300,5 +300,48 @@ describe Banzai::Filter::SanitizationFilter do
expect(act.to_html).to eq exp
end
+
+ describe 'footnotes' do
+ it 'allows correct footnote id property on links' do
+ exp = %q{<a href="#fn1" id="fnref1">foo/bar.md</a>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+
+ it 'allows correct footnote id property on li element' do
+ exp = %q{<ol><li id="fn1">footnote</li></ol>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+
+ it 'removes invalid id for footnote links' do
+ exp = %q{<a href="#fn1">link</a>}
+
+ %w[fnrefx test xfnref1].each do |id|
+ act = filter(%Q{<a href="#fn1" id="#{id}">link</a>})
+
+ expect(act.to_html).to eq exp
+ end
+ end
+
+ it 'removes invalid id for footnote li' do
+ exp = %q{<ol><li>footnote</li></ol>}
+
+ %w[fnx test xfn1].each do |id|
+ act = filter(%Q{<ol><li id="#{id}">footnote</li></ol>})
+
+ expect(act.to_html).to eq exp
+ end
+ end
+
+ it 'allows footnotes numbered higher than 9' do
+ exp = %q{<a href="#fn15" id="fnref15">link</a><ol><li id="fn15">footnote</li></ol>}
+ act = filter(exp)
+
+ expect(act.to_html).to eq exp
+ end
+ end
end
end
diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
index e9c7a2f352e..3634655c6a5 100644
--- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
@@ -25,4 +25,36 @@ describe Banzai::Pipeline::FullPipeline do
expect(result).to include(%{data-original='\"&gt;bad things'})
end
end
+
+ describe 'footnotes' do
+ let(:project) { create(:project, :public) }
+ let(:html) { described_class.to_html(footnote_markdown, project: project) }
+ let(:identifier) { html[/.*fnref1-(\d+).*/, 1] }
+ let(:footnote_markdown) do
+ <<~EOF
+ first[^1] and second[^second]
+ [^1]: one
+ [^second]: two
+ EOF
+ end
+
+ let(:filtered_footnote) do
+ <<~EOF
+ <p dir="auto">first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p>
+
+ <section class="footnotes"><ol>
+ <li id="fn1-#{identifier}">
+ <p>one <a href="#fnref1-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
+ </li>
+ <li id="fn2-#{identifier}">
+ <p>two <a href="#fnref2-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
+ </li>
+ </ol></section>
+ EOF
+ end
+
+ it 'properly adds the necessary ids and classes' do
+ expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote
+ end
+ end
end
diff --git a/spec/lib/bitbucket_server/client_spec.rb b/spec/lib/bitbucket_server/client_spec.rb
index 5de0a9a65b5..b021e69800a 100644
--- a/spec/lib/bitbucket_server/client_spec.rb
+++ b/spec/lib/bitbucket_server/client_spec.rb
@@ -17,12 +17,6 @@ describe BitbucketServer::Client do
subject.pull_requests(project, repo_slug)
end
-
- it 'throws an exception when connection fails' do
- allow(BitbucketServer::Collection).to receive(:new).and_raise(OpenSSL::SSL::SSLError)
-
- expect { subject.pull_requests(project, repo_slug) }.to raise_error(described_class::ServerError)
- end
end
describe '#activities' do
diff --git a/spec/lib/bitbucket_server/connection_spec.rb b/spec/lib/bitbucket_server/connection_spec.rb
index b5da4cb1a49..ab8a5b89608 100644
--- a/spec/lib/bitbucket_server/connection_spec.rb
+++ b/spec/lib/bitbucket_server/connection_spec.rb
@@ -26,6 +26,12 @@ describe BitbucketServer::Connection do
expect { subject.get(url) }.to raise_error(described_class::ConnectionError)
end
+
+ it 'throws an exception upon a network error' do
+ WebMock.stub_request(:get, url).with(headers: { 'Accept' => 'application/json' }).to_raise(OpenSSL::SSL::SSLError)
+
+ expect { subject.get(url) }.to raise_error(described_class::ConnectionError)
+ end
end
describe '#post' do
@@ -42,6 +48,12 @@ describe BitbucketServer::Connection do
expect { subject.post(url, payload) }.to raise_error(described_class::ConnectionError)
end
+
+ it 'throws an exception upon a network error' do
+ WebMock.stub_request(:post, url).with(headers: { 'Accept' => 'application/json' }).to_raise(OpenSSL::SSL::SSLError)
+
+ expect { subject.post(url, payload) }.to raise_error(described_class::ConnectionError)
+ end
end
describe '#delete' do
@@ -63,6 +75,12 @@ describe BitbucketServer::Connection do
expect { subject.delete(:branches, branch_path, payload) }.to raise_error(described_class::ConnectionError)
end
+
+ it 'throws an exception upon a network error' do
+ WebMock.stub_request(:delete, branch_url).with(headers: headers).to_raise(OpenSSL::SSL::SSLError)
+
+ expect { subject.delete(:branches, branch_path, payload) }.to raise_error(described_class::ConnectionError)
+ end
end
end
end
diff --git a/spec/lib/bitbucket_server/paginator_spec.rb b/spec/lib/bitbucket_server/paginator_spec.rb
index d268d4f23cf..eadd7f68bfb 100644
--- a/spec/lib/bitbucket_server/paginator_spec.rb
+++ b/spec/lib/bitbucket_server/paginator_spec.rb
@@ -30,6 +30,17 @@ describe BitbucketServer::Paginator do
expect { limited.items }.to raise_error(StopIteration)
end
+ it 'does not stop if limit is unspecified' do
+ stub_const("BitbucketServer::Paginator::PAGE_LENGTH", 1)
+ paginator = described_class.new(connection, 'http://more-data', :pull_request, page_offset: 0, limit: nil)
+ allow(paginator).to receive(:fetch_next_page).and_return(first_page, last_page)
+
+ expect(paginator.has_next_page?).to be_truthy
+ expect(paginator.items).to match(['item_1'])
+ expect(paginator.has_next_page?).to be_truthy
+ expect(paginator.items).to match(['item_2'])
+ end
+
it 'calls the connection with different offsets' do
expect(connection).to receive(:get).with('http://more-data', start: 0, limit: BitbucketServer::Paginator::PAGE_LENGTH).and_return(page_attrs)
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 9d56c62ae57..630732614b2 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -182,4 +182,18 @@ describe Feature do
expect(described_class.disabled?(:enabled_feature_flag)).to be_falsey
end
end
+
+ describe Feature::Target do
+ describe '#targets' do
+ let(:project) { create(:project) }
+ let(:user_name) { project.owner.username }
+
+ subject { described_class.new(user: user_name, project: project.full_path) }
+
+ it 'returns all found targets' do
+ expect(subject.targets).to be_an(Array)
+ expect(subject.targets).to eq([project.owner, project])
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/access/branch_protection_spec.rb b/spec/lib/gitlab/access/branch_protection_spec.rb
new file mode 100644
index 00000000000..7f2979e8e28
--- /dev/null
+++ b/spec/lib/gitlab/access/branch_protection_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Access::BranchProtection do
+ describe '#any?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true
+ Gitlab::Access::PROTECTION_FULL | true
+ end
+
+ with_them do
+ it { expect(described_class.new(level).any?).to eq(result) }
+ end
+ end
+
+ describe '#developer_can_push?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false
+ Gitlab::Access::PROTECTION_FULL | false
+ end
+
+ with_them do
+ it do
+ expect(described_class.new(level).developer_can_push?).to eq(result)
+ end
+ end
+ end
+
+ describe '#developer_can_merge?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true
+ Gitlab::Access::PROTECTION_FULL | false
+ end
+
+ with_them do
+ it do
+ expect(described_class.new(level).developer_can_merge?).to eq(result)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
index 528f1b4ec57..bf810d72f0e 100644
--- a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
@@ -23,7 +23,7 @@ describe Gitlab::Auth::OAuth::IdentityLinker do
end
context 'identity already linked to different user' do
- let!(:identity) { create(:identity, provider: provider, extern_uid: uid) }
+ let!(:identity) { create(:identity, provider: provider, extern_uid: uid) }
it "#changed? returns false" do
subject.link
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 4e4c8b215c2..1e2aebdc84b 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Auth::UserAuthFinders do
'rack.input' => ''
}
end
- let(:request) { Rack::Request.new(env) }
+ let(:request) { ActionDispatch::Request.new(env) }
def set_param(key, value)
request.update_param(key, value)
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 9ccd0b206cc..236808c0b69 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -380,7 +380,7 @@ describe Gitlab::Auth do
password: password,
password_confirmation: password)
end
- let(:username) { 'John' } # username isn't lowercase, test this
+ let(:username) { 'John' } # username isn't lowercase, test this
let(:password) { 'my-secret' }
it "finds user by valid login/password" do
diff --git a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
index ae4b53d62e6..947c99b860f 100644
--- a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories, :migration, schema: 20181218192239 do
+describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories, :migration, schema: 20181212171634 do
it_behaves_like 'backfill migration for project repositories', :legacy
end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
index 53c071f0268..510a0074554 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
@@ -2,6 +2,7 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
let(:group) { create(:group, name: 'foo', path: 'foo') }
@@ -34,6 +35,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
let!(:project_hashed_storage_2) { create(:project, name: 'bar', path: 'bar', namespace: group, storage_version: 2) }
let!(:project_legacy_storage_3) { create(:project, name: 'baz', path: 'baz', namespace: group, storage_version: 0) }
let!(:project_legacy_storage_4) { create(:project, name: 'zoo', path: 'zoo', namespace: group, storage_version: nil) }
+ let!(:project_legacy_storage_5) { create(:project, name: 'test', path: 'test', namespace: group, storage_version: nil) }
describe '.on_hashed_storage' do
it 'finds projects with repository on hashed storage' do
@@ -47,7 +49,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
it 'finds projects with repository on legacy storage' do
projects = described_class.on_legacy_storage.pluck(:id)
- expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id])
+ expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id, project_legacy_storage_5.id])
end
end
@@ -58,7 +60,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
projects = described_class.without_project_repository.pluck(:id)
- expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id)
+ expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id, project_legacy_storage_5.id)
end
end
@@ -78,17 +80,27 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
expect(project.disk_path).to eq(project_legacy_storage_3.disk_path)
end
+ it 'returns the correct disk_path using the route entry' do
+ project_legacy_storage_5.route.update(path: 'zoo/new-test')
+ project = described_class.find(project_legacy_storage_5.id)
+
+ expect(project.disk_path).to eq('zoo/new-test')
+ end
+
it 'raises OrphanedNamespaceError when any parent namespace does not exist' do
subgroup = create(:group, parent: group)
project_orphaned_namespace = create(:project, name: 'baz', path: 'baz', namespace: subgroup, storage_version: nil)
subgroup.update_column(:parent_id, Namespace.maximum(:id).succ)
project = described_class.find(project_orphaned_namespace.id)
+ project.route.destroy
+ subgroup.route.destroy
- expect { project.disk_path }
+ expect { project.reload.disk_path }
.to raise_error(Gitlab::BackgroundMigration::BackfillProjectRepositories::OrphanedNamespaceError)
end
end
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
index f92acf61682..f974dc8fda2 100644
--- a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
+++ b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys, :migration, schema: 20171005130944 do
context 'when GpgKey exists' do
- let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) }
+ let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
before do
GpgKeySubkey.destroy_all # rubocop: disable DestroyAll
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 1969aed51da..27281333348 100644
--- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
+++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do
describe '#perform' do
context 'when diff files can be deleted' do
@@ -71,3 +72,4 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
index 5dce3fcbcb6..bc71a90605a 100644
--- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
+++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do
include GitHelpers
@@ -324,3 +325,4 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
index 5432d270555..188969951a6 100644
--- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event, :migration, schema: 20170608152748 do
describe '#commit_title' do
it 'returns nil when there are no commits' do
@@ -429,3 +430,4 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migrati
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
index 021e1d14b18..ea8bdd48e72 100644
--- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete do
let(:migration) { described_class.new }
@@ -17,3 +18,4 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete d
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
index 2e77e80ee46..593486fc56c 100644
--- a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
+++ b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do
let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'move_snippet_files_test') }
let(:old_uploads_dir) { File.join('uploads', 'system', 'personal_snippet') }
@@ -70,3 +71,4 @@ describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do
"[an upload](#{path})"
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
index 4f1b01eed41..812e0cc6947 100644
--- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
@@ -3,90 +3,87 @@
require 'spec_helper'
describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :migration, schema: 20181022173835 do
+ include MigrationHelpers::ClusterHelpers
+
let(:migration) { described_class.new }
- let(:clusters) { create_list(:cluster, 10, :project, :provided_by_gcp) }
+ let(:clusters_table) { table(:clusters) }
+ let(:cluster_projects_table) { table(:cluster_projects) }
+ let(:cluster_kubernetes_namespaces_table) { table(:clusters_kubernetes_namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:provider_gcp_table) { table(:cluster_providers_gcp) }
+ let(:platform_kubernetes_table) { table(:cluster_platforms_kubernetes) }
before do
- clusters
+ create_cluster_project_list(10)
end
shared_examples 'consistent kubernetes namespace attributes' do
it 'should populate namespace and service account information' do
- subject
+ migration.perform
clusters_with_namespace.each do |cluster|
- project = cluster.project
- cluster_project = cluster.cluster_projects.first
+ cluster_project = cluster_projects_table.find_by(cluster_id: cluster.id)
+ project = projects_table.find(cluster_project.project_id)
+ kubernetes_namespace = cluster_kubernetes_namespaces_table.find_by(cluster_id: cluster.id)
namespace = "#{project.path}-#{project.id}"
- kubernetes_namespace = cluster.reload.kubernetes_namespace
expect(kubernetes_namespace).to be_present
- expect(kubernetes_namespace.cluster_project).to eq(cluster_project)
- expect(kubernetes_namespace.project).to eq(cluster_project.project)
- expect(kubernetes_namespace.cluster).to eq(cluster_project.cluster)
+ expect(kubernetes_namespace.cluster_project_id).to eq(cluster_project.id)
+ expect(kubernetes_namespace.project_id).to eq(cluster_project.project_id)
+ expect(kubernetes_namespace.cluster_id).to eq(cluster_project.cluster_id)
expect(kubernetes_namespace.namespace).to eq(namespace)
expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
end
end
end
- subject { migration.perform }
-
context 'when no Clusters::Project has a Clusters::KubernetesNamespace' do
- let(:cluster_projects) { Clusters::Project.all }
+ let(:cluster_projects) { cluster_projects_table.all }
it 'should create a Clusters::KubernetesNamespace per Clusters::Project' do
expect do
- subject
- end.to change(Clusters::KubernetesNamespace, :count).by(cluster_projects.count)
+ migration.perform
+ end.to change(Clusters::KubernetesNamespace, :count).by(cluster_projects_table.count)
end
it_behaves_like 'consistent kubernetes namespace attributes' do
- let(:clusters_with_namespace) { clusters }
+ let(:clusters_with_namespace) { clusters_table.all }
end
end
context 'when every Clusters::Project has Clusters::KubernetesNamespace' do
before do
- clusters.each do |cluster|
- create(:cluster_kubernetes_namespace,
- cluster_project: cluster.cluster_projects.first,
- cluster: cluster,
- project: cluster.project)
- end
+ create_kubernetes_namespace(clusters_table.all)
end
it 'should not create any Clusters::KubernetesNamespace' do
expect do
- subject
+ migration.perform
end.not_to change(Clusters::KubernetesNamespace, :count)
end
end
context 'when only some Clusters::Project have Clusters::KubernetesNamespace related' do
- let(:with_kubernetes_namespace) { clusters.first(6) }
- let(:with_no_kubernetes_namespace) { clusters.last(4) }
+ let(:with_kubernetes_namespace) { clusters_table.first(6) }
+ let(:with_no_kubernetes_namespace) { clusters_table.last(4) }
before do
- with_kubernetes_namespace.each do |cluster|
- create(:cluster_kubernetes_namespace,
- cluster_project: cluster.cluster_projects.first,
- cluster: cluster,
- project: cluster.project)
- end
+ create_kubernetes_namespace(with_kubernetes_namespace)
end
it 'creates limited number of Clusters::KubernetesNamespace' do
expect do
- subject
+ migration.perform
end.to change(Clusters::KubernetesNamespace, :count).by(with_no_kubernetes_namespace.count)
end
it 'should not modify clusters with Clusters::KubernetesNamespace' do
- subject
+ migration.perform
with_kubernetes_namespace.each do |cluster|
- expect(cluster.kubernetes_namespaces.count).to eq(1)
+ kubernetes_namespace = cluster_kubernetes_namespaces_table.where(cluster_id: cluster.id)
+ expect(kubernetes_namespace.count).to eq(1)
end
end
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 c7b272cd6ca..3e009fed0f1 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
@@ -2,9 +2,15 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration, schema: 20180916011959 do
let(:migration) { described_class.new }
+ before do
+ # This migration was created before we introduced metadata configs
+ stub_feature_flags(ci_build_metadata_config: false)
+ end
+
let!(:internal_pipeline) { create(:ci_pipeline, source: :web) }
let(:pipelines) { [internal_pipeline, unknown_pipeline].map(&:id) }
@@ -60,3 +66,4 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration
it_behaves_like 'no changes'
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/populate_import_state_spec.rb b/spec/lib/gitlab/background_migration/populate_import_state_spec.rb
index f9952ee5163..fcb869022de 100644
--- a/spec/lib/gitlab/background_migration/populate_import_state_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_import_state_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::BackgroundMigration::PopulateImportState, :migration, schema: 2
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
- let(:import_state) { table(:project_mirror_data) }
+ let(:import_state) { table(:project_mirror_data) }
before do
namespaces.create(id: 1, name: 'gitlab-org', path: 'gitlab-org')
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
index e99257e3481..ff1bd9f7850 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
@@ -1,5 +1,6 @@
require 'rails_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, :migration, schema: 20171128214150 do
# commits_count attribute is added in a next migration
before do
@@ -128,3 +129,4 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData,
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/rollback_import_state_data_spec.rb b/spec/lib/gitlab/background_migration/rollback_import_state_data_spec.rb
index 9f8c3bc220f..cef3b6e4568 100644
--- a/spec/lib/gitlab/background_migration/rollback_import_state_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/rollback_import_state_data_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::BackgroundMigration::RollbackImportStateData, :migration, schem
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
- let(:import_state) { table(:project_mirror_data) }
+ let(:import_state) { table(:project_mirror_data) }
before do
namespaces.create(id: 1, name: 'gitlab-org', path: 'gitlab-org')
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index 4ad69aeba43..8a83b76fd94 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -119,4 +119,48 @@ describe Gitlab::BackgroundMigration do
described_class.perform('Foo', [10, 20])
end
end
+
+ describe '.exists?' do
+ context 'when there are enqueued jobs present' do
+ let(:queue) do
+ [double(args: ['Foo', [10, 20]], queue: described_class.queue)]
+ end
+
+ before do
+ allow(Sidekiq::Queue).to receive(:new)
+ .with(described_class.queue)
+ .and_return(queue)
+ end
+
+ it 'returns true if specific job exists' do
+ expect(described_class.exists?('Foo')).to eq(true)
+ end
+
+ it 'returns false if specific job does not exist' do
+ expect(described_class.exists?('Bar')).to eq(false)
+ end
+ end
+
+ context 'when there are scheduled jobs present', :sidekiq, :redis do
+ before do
+ Sidekiq::Testing.disable! do
+ BackgroundMigrationWorker.perform_in(10.minutes, 'Foo')
+
+ expect(Sidekiq::ScheduledSet.new).to be_one
+ end
+ end
+
+ after do
+ Sidekiq::ScheduledSet.new.clear
+ end
+
+ it 'returns true if specific job exists' do
+ expect(described_class.exists?('Foo')).to eq(true)
+ end
+
+ it 'returns false if specific job does not exist' do
+ expect(described_class.exists?('Bar')).to eq(false)
+ 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 7a681bc6610..0def685f177 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::BitbucketImport::Importer do
"invalid",
"duplicate",
"wontfix",
- "closed" # undocumented status
+ "closed" # undocumented status
]
end
diff --git a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
index 70423823b89..1e90a2ef27f 100644
--- a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
@@ -21,12 +21,9 @@ describe Gitlab::BitbucketServerImport::Importer do
end
describe '#import_repository' do
- before do
+ it 'adds a remote' do
expect(subject).to receive(:import_pull_requests)
expect(subject).to receive(:delete_temp_branches)
- end
-
- it 'adds a remote' do
expect(project.repository).to receive(:fetch_as_mirror)
.with('http://bitbucket:test@my-bitbucket',
refmap: [:heads, :tags, '+refs/pull-requests/*/to:refs/merge-requests/*/head'],
@@ -34,6 +31,18 @@ describe Gitlab::BitbucketServerImport::Importer do
subject.execute
end
+
+ it 'raises a Gitlab::Shell exception in the fetch' do
+ expect(project.repository).to receive(:fetch_as_mirror).and_raise(Gitlab::Shell::Error)
+
+ expect { subject.execute }.to raise_error(Gitlab::Shell::Error)
+ end
+
+ it 'raises an unhandled exception in the fetch' do
+ expect(project.repository).to receive(:fetch_as_mirror).and_raise(RuntimeError)
+
+ expect { subject.execute }.to raise_error(RuntimeError)
+ end
end
describe '#import_pull_requests' do
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index be9b2588c90..483c5ea9cff 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -238,7 +238,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
end
describe '#delete_from_cache' do
- it 'deletes values from redis_cache' do
+ it 'deletes values from redis_cache' do
pipeline_status.delete_from_cache
key_exists = Gitlab::Redis::Cache.with { |redis| redis.exists(cache_key) }
diff --git a/spec/lib/gitlab/checks/push_check_spec.rb b/spec/lib/gitlab/checks/push_check_spec.rb
index 25f0d428cb9..e1bd52d6c0b 100644
--- a/spec/lib/gitlab/checks/push_check_spec.rb
+++ b/spec/lib/gitlab/checks/push_check_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::Checks::PushCheck do
context 'when the user is not allowed to push to the repo' do
it 'raises an error' do
expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
- expect(user_access).to receive(:can_push_to_branch?).with('master').and_return(false)
+ expect(project).to receive(:branch_allows_collaboration?).with(user_access.user, 'master').and_return(false)
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.')
end
diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb
index cce4efaa069..e3136fc925e 100644
--- a/spec/lib/gitlab/ci/build/step_spec.rb
+++ b/spec/lib/gitlab/ci/build/step_spec.rb
@@ -18,13 +18,6 @@ describe Gitlab::Ci::Build::Step do
end
end
- context 'when commands are specified' do
- it_behaves_like 'has correct script' do
- let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") }
- let(:script) { ['ls -la', 'date'] }
- end
- end
-
context 'when script option is specified' do
it_behaves_like 'has correct script' do
let(:job) { create(:ci_build, :no_options, options: { script: ["ls -la\necho aaa", "date"] }) }
@@ -62,7 +55,7 @@ describe Gitlab::Ci::Build::Step do
end
context 'when after_script is not empty' do
- let(:job) { create(:ci_build, options: { after_script: ['ls -la', 'date'] }) }
+ let(:job) { create(:ci_build, options: { script: ['bash'], after_script: ['ls -la', 'date'] }) }
it 'fabricates an object' do
expect(subject.name).to eq(:after_script)
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 61d78f86b51..7651f594a4c 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -153,7 +153,6 @@ describe Gitlab::Ci::Config::Entry::Global do
rspec: { name: :rspec,
script: %w[rspec ls],
before_script: %w(ls pwd),
- commands: "ls\npwd\nrspec\nls",
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
@@ -161,12 +160,10 @@ describe Gitlab::Ci::Config::Entry::Global do
variables: { 'VAR' => 'value' },
ignore: false,
after_script: ['make clean'],
- only: { refs: %w[branches tags] },
- except: {} },
+ only: { refs: %w[branches tags] } },
spinach: { name: :spinach,
before_script: [],
script: %w[spinach],
- commands: 'spinach',
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
@@ -174,8 +171,7 @@ describe Gitlab::Ci::Config::Entry::Global do
variables: {},
ignore: false,
after_script: ['make clean'],
- only: { refs: %w[branches tags] },
- except: {} }
+ only: { refs: %w[branches tags] } }
)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 8e32cede3b5..0560eb42e4d 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -255,25 +255,13 @@ describe Gitlab::Ci::Config::Entry::Job do
.to eq(name: :rspec,
before_script: %w[ls pwd],
script: %w[rspec],
- commands: "ls\npwd\nrspec",
stage: 'test',
ignore: false,
after_script: %w[cleanup],
- only: { refs: %w[branches tags] },
- except: {})
+ only: { refs: %w[branches tags] })
end
end
end
-
- describe '#commands' do
- let(:config) do
- { before_script: %w[ls pwd], script: 'rspec' }
- end
-
- it 'returns a string of commands concatenated with new line character' do
- expect(entry.commands).to eq "ls\npwd\nrspec"
- end
- end
end
describe '#manual_action?' do
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index 1a2c30d3571..271ee30df3c 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -65,18 +65,14 @@ describe Gitlab::Ci::Config::Entry::Jobs do
expect(entry.value).to eq(
rspec: { name: :rspec,
script: %w[rspec],
- commands: 'rspec',
ignore: false,
stage: 'test',
- only: { refs: %w[branches tags] },
- except: {} },
+ only: { refs: %w[branches tags] } },
spinach: { name: :spinach,
script: %w[spinach],
- commands: 'spinach',
ignore: false,
stage: 'test',
- only: { refs: %w[branches tags] },
- except: {} })
+ only: { refs: %w[branches tags] } })
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index 83001b7fdd8..1c987e13a9a 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -168,8 +168,33 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
end
+ describe '#value' do
+ context 'when default value has been provided' do
+ before do
+ entry.default = { refs: %w[branches tags] }
+ end
+
+ context 'when user overrides default values' do
+ let(:config) { { refs: %w[feature], variables: %w[$VARIABLE] } }
+
+ it 'does not include default values' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ context 'when default value has not been defined' do
+ let(:config) { { variables: %w[$VARIABLE] } }
+
+ it 'includes default values' do
+ expect(entry.value).to eq(refs: %w[branches tags],
+ variables: %w[$VARIABLE])
+ end
+ end
+ end
+ end
+
describe '.default' do
- it 'does not have a default value' do
+ it 'does not have default policy' do
expect(described_class.default).to be_nil
end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index 2e92d5204d6..1a6b3587599 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -3,13 +3,43 @@
require 'fast_spec_helper'
describe Gitlab::Ci::Config::External::File::Base do
- subject { described_class.new(location) }
+ let(:context) { described_class::Context.new(nil, 'HEAD', nil) }
+
+ let(:test_class) do
+ Class.new(described_class) do
+ def initialize(params, context = {})
+ @location = params
+
+ super
+ end
+ end
+ end
+
+ subject { test_class.new(location, context) }
before do
- allow_any_instance_of(described_class)
+ allow_any_instance_of(test_class)
.to receive(:content).and_return('key: value')
end
+ describe '#matching?' do
+ context 'when a location is present' do
+ let(:location) { 'some-location' }
+
+ it 'should return true' do
+ expect(subject).to be_matching
+ end
+ end
+
+ context 'with a location is missing' do
+ let(:location) { nil }
+
+ it 'should return false' do
+ expect(subject).not_to be_matching
+ end
+ end
+ end
+
describe '#valid?' do
context 'when location is not a YAML file' do
let(:location) { 'some/file.txt' }
@@ -39,7 +69,7 @@ describe Gitlab::Ci::Config::External::File::Base do
let(:location) { 'some/file/config.yml' }
before do
- allow_any_instance_of(described_class)
+ allow_any_instance_of(test_class)
.to receive(:content).and_return('invalid_syntax')
end
diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index 645db642e29..ff67a765da0 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -3,8 +3,37 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Local do
- let(:project) { create(:project, :repository) }
- let(:local_file) { described_class.new(location, { project: project, sha: '12345' }) }
+ set(:project) { create(:project, :repository) }
+
+ let(:context) { described_class::Context.new(project, '12345', nil) }
+ let(:params) { { local: location } }
+ let(:local_file) { described_class.new(params, context) }
+
+ describe '#matching?' do
+ context 'when a local is specified' do
+ let(:params) { { local: 'file' } }
+
+ it 'should return true' do
+ expect(local_file).to be_matching
+ end
+ end
+
+ context 'with a missing local' do
+ let(:params) { { local: nil } }
+
+ it 'should return false' do
+ expect(local_file).not_to be_matching
+ end
+ end
+
+ context 'with a missing local key' do
+ let(:params) { {} }
+
+ it 'should return false' do
+ expect(local_file).not_to be_matching
+ end
+ end
+ end
describe '#valid?' do
context 'when is a valid local path' do
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
new file mode 100644
index 00000000000..11809adcaf6
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::External::File::Project do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+
+ let(:context_user) { user }
+ let(:context) { described_class::Context.new(nil, '12345', context_user) }
+ let(:subject) { described_class.new(params, context) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#matching?' do
+ context 'when a file and project is specified' do
+ let(:params) { { file: 'file.yml', project: 'project' } }
+
+ it 'should return true' do
+ expect(subject).to be_matching
+ end
+ end
+
+ context 'with only file is specified' do
+ let(:params) { { file: 'file.yml' } }
+
+ it 'should return false' do
+ expect(subject).not_to be_matching
+ end
+ end
+
+ context 'with only project is specified' do
+ let(:params) { { project: 'project' } }
+
+ it 'should return false' do
+ expect(subject).not_to be_matching
+ end
+ end
+
+ context 'with a missing local key' do
+ let(:params) { {} }
+
+ it 'should return false' do
+ expect(subject).not_to be_matching
+ end
+ end
+ end
+
+ describe '#valid?' do
+ context 'when a valid path is used' do
+ let(:params) do
+ { project: project.full_path, file: '/file.yml' }
+ end
+
+ let(:root_ref_sha) { project.repository.root_ref_sha }
+
+ before do
+ stub_project_blob(root_ref_sha, '/file.yml') { 'image: ruby:2.1' }
+ end
+
+ it 'should return true' do
+ expect(subject).to be_valid
+ end
+
+ context 'when user does not have permission to access file' do
+ let(:context_user) { create(:user) }
+
+ it 'should return false' do
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to include("Project `#{project.full_path}` not found or access denied!")
+ end
+ end
+ end
+
+ context 'when a valid path with custom ref is used' do
+ let(:params) do
+ { project: project.full_path, ref: 'master', file: '/file.yml' }
+ end
+
+ let(:ref_sha) { project.commit('master').sha }
+
+ before do
+ stub_project_blob(ref_sha, '/file.yml') { 'image: ruby:2.1' }
+ end
+
+ it 'should return true' do
+ expect(subject).to be_valid
+ end
+ end
+
+ context 'when an empty file is used' do
+ let(:params) do
+ { project: project.full_path, file: '/file.yml' }
+ end
+
+ let(:root_ref_sha) { project.repository.root_ref_sha }
+
+ before do
+ stub_project_blob(root_ref_sha, '/file.yml') { '' }
+ end
+
+ it 'should return false' do
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!")
+ end
+ end
+
+ context 'when non-existing ref is used' do
+ let(:params) do
+ { project: project.full_path, ref: 'I-Do-Not-Exist', file: '/file.yml' }
+ end
+
+ it 'should return false' do
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to include("Project `#{project.full_path}` reference `I-Do-Not-Exist` does not exist!")
+ end
+ end
+
+ context 'when non-existing file is requested' do
+ let(:params) do
+ { project: project.full_path, file: '/invalid-file.yml' }
+ end
+
+ it 'should return false' do
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!")
+ end
+ end
+
+ context 'when file is not a yaml file' do
+ let(:params) do
+ { project: project.full_path, file: '/invalid-file' }
+ end
+
+ it 'should return false' do
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to include('Included file `/invalid-file` does not have YAML extension!')
+ end
+ end
+ end
+
+ private
+
+ def stub_project_blob(ref, path)
+ allow_any_instance_of(Repository)
+ .to receive(:blob_data_at)
+ .with(ref, path) { yield }
+ end
+end
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 eaf621e4140..3e0fda9c308 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -3,7 +3,9 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Remote do
- let(:remote_file) { described_class.new(location) }
+ let(:context) { described_class::Context.new(nil, '12345', nil) }
+ let(:params) { { remote: location } }
+ let(:remote_file) { described_class.new(params, context) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:remote_file_content) do
<<~HEREDOC
@@ -15,6 +17,32 @@ describe Gitlab::Ci::Config::External::File::Remote do
HEREDOC
end
+ describe '#matching?' do
+ context 'when a remote is specified' do
+ let(:params) { { remote: 'http://remote' } }
+
+ it 'should return true' do
+ expect(remote_file).to be_matching
+ end
+ end
+
+ context 'with a missing remote' do
+ let(:params) { { remote: nil } }
+
+ it 'should return false' do
+ expect(remote_file).not_to be_matching
+ end
+ end
+
+ context 'with a missing remote key' do
+ let(:params) { {} }
+
+ it 'should return false' do
+ expect(remote_file).not_to be_matching
+ end
+ end
+ end
+
describe "#valid?" do
context 'when is a valid remote url' do
before do
diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
new file mode 100644
index 00000000000..1fb5655309a
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::External::File::Template do
+ let(:context) { described_class::Context.new(nil, '12345') }
+ let(:template) { 'Auto-DevOps.gitlab-ci.yml' }
+ let(:params) { { template: template } }
+
+ subject { described_class.new(params, context) }
+
+ describe '#matching?' do
+ context 'when a template is specified' do
+ let(:params) { { template: 'some-template' } }
+
+ it 'should return true' do
+ expect(subject).to be_matching
+ end
+ end
+
+ context 'with a missing template' do
+ let(:params) { { template: nil } }
+
+ it 'should return false' do
+ expect(subject).not_to be_matching
+ end
+ end
+
+ context 'with a missing template key' do
+ let(:params) { {} }
+
+ it 'should return false' do
+ expect(subject).not_to be_matching
+ end
+ end
+ end
+
+ describe "#valid?" do
+ context 'when is a valid template name' do
+ let(:template) { 'Auto-DevOps.gitlab-ci.yml' }
+
+ it 'should return true' do
+ expect(subject).to be_valid
+ end
+ end
+
+ context 'with invalid template name' do
+ let(:template) { 'Template.yml' }
+
+ it 'should return false' do
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to include('Template file `Template.yml` is not a valid location!')
+ end
+ end
+
+ context 'with a non-existing template' do
+ let(:template) { 'I-Do-Not-Have-This-Template.gitlab-ci.yml' }
+
+ it 'should return false' do
+ expect(subject).not_to be_valid
+ expect(subject.error_message).to include('Included file `I-Do-Not-Have-This-Template.gitlab-ci.yml` is empty or does not exist!')
+ end
+ end
+ end
+
+ describe '#template_name' do
+ let(:template_name) { subject.send(:template_name) }
+
+ context 'when template does end with .gitlab-ci.yml' do
+ let(:template) { 'my-template.gitlab-ci.yml' }
+
+ it 'returns template name' do
+ expect(template_name).to eq('my-template')
+ end
+ end
+
+ context 'when template is nil' do
+ let(:template) { nil }
+
+ it 'returns nil' do
+ expect(template_name).to be_nil
+ end
+ end
+
+ context 'when template does not end with .gitlab-ci.yml' do
+ let(:template) { 'my-template' }
+
+ it 'returns nil' do
+ expect(template_name).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 5b236fe99f1..4cab4961b0f 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -3,84 +3,131 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Mapper do
- let(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+
+ let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
+ let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
+ let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
+
let(:file_content) do
<<~HEREDOC
image: 'ruby:2.2'
HEREDOC
end
+ before do
+ WebMock.stub_request(:get, remote_url).to_return(body: file_content)
+ end
+
describe '#process' do
- subject { described_class.new(values, project, '123456').process }
+ subject { described_class.new(values, project: project, sha: '123456', user: user).process }
- context "when 'include' keyword is defined as string" do
+ context "when single 'include' keyword is defined" do
context 'when the string is a local file' do
let(:values) do
- {
- include: '/lib/gitlab/ci/templates/non-existent-file.yml',
- image: 'ruby:2.2'
- }
+ { include: local_file,
+ image: 'ruby:2.2' }
+ end
+
+ it 'returns File instances' do
+ expect(subject).to contain_exactly(
+ an_instance_of(Gitlab::Ci::Config::External::File::Local))
end
+ end
- it 'returns an array' do
- expect(subject).to be_an(Array)
+ context 'when the key is a local file hash' do
+ let(:values) do
+ { include: { 'local' => local_file },
+ image: 'ruby:2.2' }
end
it 'returns File instances' do
- expect(subject.first)
- .to be_an_instance_of(Gitlab::Ci::Config::External::File::Local)
+ expect(subject).to contain_exactly(
+ an_instance_of(Gitlab::Ci::Config::External::File::Local))
end
end
context 'when the string is a remote file' do
- let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:values) do
- {
- include: remote_url,
- image: 'ruby:2.2'
- }
+ { include: remote_url, image: 'ruby:2.2' }
+ end
+
+ it 'returns File instances' do
+ expect(subject).to contain_exactly(
+ an_instance_of(Gitlab::Ci::Config::External::File::Remote))
+ end
+ end
+
+ context 'when the key is a remote file hash' do
+ let(:values) do
+ { include: { 'remote' => remote_url },
+ image: 'ruby:2.2' }
end
- before do
- WebMock.stub_request(:get, remote_url).to_return(body: file_content)
+ it 'returns File instances' do
+ expect(subject).to contain_exactly(
+ an_instance_of(Gitlab::Ci::Config::External::File::Remote))
end
+ end
- it 'returns an array' do
- expect(subject).to be_an(Array)
+ context 'when the key is a template file hash' do
+ let(:values) do
+ { include: { 'template' => template_file },
+ image: 'ruby:2.2' }
end
it 'returns File instances' do
- expect(subject.first)
- .to be_an_instance_of(Gitlab::Ci::Config::External::File::Remote)
+ expect(subject).to contain_exactly(
+ an_instance_of(Gitlab::Ci::Config::External::File::Template))
+ end
+ end
+
+ context 'when the key is a hash of file and remote' do
+ let(:values) do
+ { include: { 'local' => local_file, 'remote' => remote_url },
+ image: 'ruby:2.2' }
+ end
+
+ it 'returns ambigious specification error' do
+ expect { subject }.to raise_error(described_class::AmbigiousSpecificationError)
end
end
end
context "when 'include' is defined as an array" do
- let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:values) do
- {
- include:
- [
- remote_url,
- '/lib/gitlab/ci/templates/template.yml'
- ],
- image: 'ruby:2.2'
- }
+ { include: [remote_url, local_file],
+ image: 'ruby:2.2' }
end
- before do
- WebMock.stub_request(:get, remote_url).to_return(body: file_content)
+ it 'returns Files instances' do
+ expect(subject).to all(respond_to(:valid?))
+ expect(subject).to all(respond_to(:content))
end
+ end
- it 'returns an array' do
- expect(subject).to be_an(Array)
+ context "when 'include' is defined as an array of hashes" do
+ let(:values) do
+ { include: [{ remote: remote_url }, { local: local_file }],
+ image: 'ruby:2.2' }
end
it 'returns Files instances' do
expect(subject).to all(respond_to(:valid?))
expect(subject).to all(respond_to(:content))
end
+
+ context 'when it has ambigious match' do
+ let(:values) do
+ { include: [{ remote: remote_url, local: local_file }],
+ image: 'ruby:2.2' }
+ end
+
+ it 'returns ambigious specification error' do
+ expect { subject }.to raise_error(described_class::AmbigiousSpecificationError)
+ end
+ end
end
context "when 'include' is not defined" do
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index dbd28e9745c..1ac58139b25 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -3,8 +3,14 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Processor do
- let(:project) { create(:project, :repository) }
- let(:processor) { described_class.new(values, project, '12345') }
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+
+ let(:processor) { described_class.new(values, project: project, sha: '12345', user: user) }
+
+ before do
+ project.add_developer(user)
+ end
describe "#perform" do
context 'when no external files defined' do
diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb
index 97926695b6e..cd880177170 100644
--- a/spec/lib/gitlab/ci/config/normalizer_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb
@@ -62,5 +62,25 @@ describe Gitlab::Ci::Config::Normalizer do
expect(subject[:other_job][:dependencies]).not_to include(job_name)
end
end
+
+ context 'when there are dependencies which are both parallelized and not' do
+ let(:config) do
+ {
+ job_name => job_config,
+ other_job: { script: 'echo 1' },
+ final_job: { script: 'echo 1', dependencies: [job_name.to_s, "other_job"] }
+ }
+ end
+
+ it 'parallelizes dependencies' do
+ job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"]
+
+ expect(subject[:final_job][:dependencies]).to include(*job_names)
+ end
+
+ it 'includes the regular job in dependencies' do
+ expect(subject[:final_job][:dependencies]).to include('other_job')
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index ea6f1e20014..18f255c1ab7 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -1,8 +1,10 @@
require 'spec_helper'
describe Gitlab::Ci::Config do
+ set(:user) { create(:user) }
+
let(:config) do
- described_class.new(yml)
+ described_class.new(yml, project: nil, sha: nil, user: nil)
end
context 'when config is valid' do
@@ -154,7 +156,7 @@ describe Gitlab::Ci::Config do
end
let(:config) do
- described_class.new(gitlab_ci_yml, project: project, sha: '12345')
+ described_class.new(gitlab_ci_yml, project: project, sha: '12345', user: user)
end
before do
@@ -190,7 +192,7 @@ describe Gitlab::Ci::Config do
end
end
- context "when gitlab_ci.yml has invalid 'include' defined" do
+ context "when gitlab_ci.yml has invalid 'include' defined" do
let(:gitlab_ci_yml) do
<<~HEREDOC
include: invalid
@@ -205,13 +207,30 @@ describe Gitlab::Ci::Config do
end
end
+ context "when gitlab_ci.yml has ambigious 'include' defined" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ remote: http://url
+ local: /local/file.yml
+ HEREDOC
+ end
+
+ it 'raises error YamlProcessor validationError' do
+ expect { config }.to raise_error(
+ described_class::ConfigError,
+ 'Include `{"remote":"http://url","local":"/local/file.yml"}` needs to match exactly one accessor!'
+ )
+ end
+ end
+
describe 'external file version' do
context 'when external local file SHA is defined' do
it 'is using a defined value' do
expect(project.repository).to receive(:blob_data_at)
.with('eeff1122', local_location)
- described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122')
+ described_class.new(gitlab_ci_yml, project: project, sha: 'eeff1122', user: user)
end
end
@@ -219,7 +238,7 @@ describe Gitlab::Ci::Config do
it 'is using latest SHA on the default branch' do
expect(project.repository).to receive(:root_ref_sha)
- described_class.new(gitlab_ci_yml, project: project)
+ described_class.new(gitlab_ci_yml, project: project, sha: nil, user: user)
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 1b014ecfaa4..0302e4090cf 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -79,6 +79,31 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
end
+ describe 'pipeline protect' do
+ subject { step.perform! }
+
+ context 'when ref is protected' do
+ before do
+ allow(project).to receive(:protected_for?).with('master').and_return(true)
+ allow(project).to receive(:protected_for?).with('refs/heads/master').and_return(true)
+ end
+
+ it 'does not protect the pipeline' do
+ subject
+
+ expect(pipeline.protected).to eq(true)
+ end
+ end
+
+ context 'when ref is not protected' do
+ it 'does not protect the pipeline' do
+ subject
+
+ expect(pipeline.protected).to eq(false)
+ end
+ end
+ end
+
context 'when pipeline has validation errors' do
let(:pipeline) do
build(:ci_pipeline, project: project, ref: nil)
@@ -138,14 +163,14 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
->(pipeline) { pipeline.variables.create!(key: 'VAR', value: '123') }
end
- it 'raises exception' do
+ it 'wastes pipeline iid' do
expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved)
- end
- it 'wastes pipeline iid' do
- expect { step.perform! }.to raise_error
+ last_iid = InternalId.ci_pipelines
+ .where(project_id: project.id)
+ .last.last_value
- expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0
+ expect(last_iid).to be > 0
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 2cf812b26dc..fae8add6453 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -5,9 +5,7 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:attributes) do
- { name: 'rspec',
- ref: 'master',
- commands: 'rspec' }
+ { name: 'rspec', ref: 'master' }
end
subject do
@@ -18,14 +16,49 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
it 'returns hash attributes of a build' do
expect(subject.attributes).to be_a Hash
expect(subject.attributes)
- .to include(:name, :project, :ref, :commands)
+ .to include(:name, :project, :ref)
+ end
+ end
+
+ describe '#bridge?' do
+ context 'when job is a bridge' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
+ end
+
+ it { is_expected.to be_bridge }
+ end
+
+ context 'when trigger definition is empty' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { trigger: '' } }
+ end
+
+ it { is_expected.not_to be_bridge }
+ end
+
+ context 'when job is not a bridge' do
+ it { is_expected.not_to be_bridge }
end
end
describe '#to_resource' do
- it 'returns a valid build resource' do
- expect(subject.to_resource).to be_a(::Ci::Build)
- expect(subject.to_resource).to be_valid
+ context 'when job is not a bridge' do
+ it 'returns a valid build resource' do
+ expect(subject.to_resource).to be_a(::Ci::Build)
+ expect(subject.to_resource).to be_valid
+ end
+ end
+
+ context 'when job is a bridge' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
+ end
+
+ it 'returns a valid bridge resource' do
+ expect(subject.to_resource).to be_a(::Ci::Bridge)
+ expect(subject.to_resource).to be_valid
+ end
end
it 'memoizes a resource object' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index 82f741845db..493ca3cd7b5 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -62,8 +62,18 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do
expect(subject.seeds.map(&:attributes)).to all(include(ref: 'master'))
expect(subject.seeds.map(&:attributes)).to all(include(tag: false))
expect(subject.seeds.map(&:attributes)).to all(include(project: pipeline.project))
- expect(subject.seeds.map(&:attributes))
- .to all(include(trigger_request: pipeline.trigger_requests.first))
+ end
+
+ context 'when a legacy trigger exists' do
+ before do
+ create(:ci_trigger_request, pipeline: pipeline)
+ end
+
+ it 'returns build seeds including legacy trigger' do
+ expect(pipeline.legacy_trigger).not_to be_nil
+ expect(subject.seeds.map(&:attributes))
+ .to all(include(trigger_request: pipeline.legacy_trigger))
+ end
end
context 'when a ref is protected' do
diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb
index 40871f86568..0d02c371a92 100644
--- a/spec/lib/gitlab/ci/status/external/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/common_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::Ci::Status::External::Common do
end
subject do
- Gitlab::Ci::Status::Core
+ Gitlab::Ci::Status::Success
.new(external_status, user)
.extend(described_class)
end
@@ -20,6 +20,22 @@ describe Gitlab::Ci::Status::External::Common do
it 'returns description' do
expect(subject.label).to eq external_description
end
+
+ context 'when description is nil' do
+ let(:external_description) { nil }
+
+ it 'uses core status label' do
+ expect(subject.label).to eq('passed')
+ end
+ end
+
+ context 'when description is empty string' do
+ let(:external_description) { '' }
+
+ it 'uses core status label' do
+ expect(subject.label).to eq('passed')
+ end
+ end
end
describe '#has_action?' do
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 441e8214181..91139d421f5 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -3,10 +3,10 @@ require 'spec_helper'
module Gitlab
module Ci
describe YamlProcessor do
- subject { described_class.new(config) }
+ subject { described_class.new(config, user: nil) }
describe '#build_attributes' do
- subject { described_class.new(config).build_attributes(:rspec) }
+ subject { described_class.new(config, user: nil).build_attributes(:rspec) }
describe 'attributes list' do
let(:config) do
@@ -21,16 +21,12 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"]
},
allow_failure: false,
when: "on_success",
- environment: nil,
yaml_variables: []
})
end
@@ -155,13 +151,9 @@ module Gitlab
builds:
[{ stage_idx: 1,
stage: "test",
- commands: "rspec",
- tag_list: [],
name: "rspec",
allow_failure: false,
when: "on_success",
- environment: nil,
- coverage_regex: nil,
yaml_variables: [],
options: { script: ["rspec"] },
only: { refs: ["branches"] },
@@ -171,13 +163,9 @@ module Gitlab
builds:
[{ stage_idx: 2,
stage: "deploy",
- commands: "cap prod",
- tag_list: [],
name: "prod",
allow_failure: false,
when: "on_success",
- environment: nil,
- coverage_regex: nil,
yaml_variables: [],
options: { script: ["cap prod"] },
only: { refs: ["tags"] },
@@ -271,7 +259,7 @@ module Gitlab
end
it "return commands with scripts concencaced" do
- expect(subject[:commands]).to eq("global script\nscript")
+ expect(subject[:options][:before_script]).to eq(["global script"])
end
end
@@ -284,7 +272,7 @@ module Gitlab
end
it "return commands with scripts concencaced" do
- expect(subject[:commands]).to eq("local script\nscript")
+ expect(subject[:options][:before_script]).to eq(["local script"])
end
end
end
@@ -297,7 +285,7 @@ module Gitlab
end
it "return commands with scripts concencaced" do
- expect(subject[:commands]).to eq("script")
+ expect(subject[:options][:script]).to eq(["script"])
end
end
@@ -347,9 +335,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
@@ -360,7 +345,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
- environment: nil,
yaml_variables: []
})
end
@@ -382,9 +366,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
@@ -395,7 +376,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
- environment: nil,
yaml_variables: []
})
end
@@ -415,9 +395,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
@@ -426,7 +403,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
- environment: nil,
yaml_variables: []
})
end
@@ -444,9 +420,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
@@ -455,7 +428,6 @@ module Gitlab
},
allow_failure: false,
when: "on_success",
- environment: nil,
yaml_variables: []
})
end
@@ -596,7 +568,7 @@ module Gitlab
it 'correctly extends rspec job' do
expect(config_processor.builds).to be_one
- expect(subject.dig(:commands)).to eq 'test'
+ expect(subject.dig(:options, :script)).to eq %w(test)
expect(subject.dig(:options, :image, :name)).to eq 'ruby:alpine'
end
end
@@ -622,7 +594,8 @@ module Gitlab
it 'correctly extends rspec job' do
expect(config_processor.builds).to be_one
- expect(subject.dig(:commands)).to eq "bundle install\nrspec"
+ expect(subject.dig(:options, :before_script)).to eq ["bundle install"]
+ expect(subject.dig(:options, :script)).to eq %w(rspec)
expect(subject.dig(:options, :image, :name)).to eq 'image:test'
expect(subject.dig(:when)).to eq 'always'
end
@@ -769,9 +742,6 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "rspec",
- commands: "pwd\nrspec",
- coverage_regex: nil,
- tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
@@ -786,7 +756,6 @@ module Gitlab
},
when: "on_success",
allow_failure: false,
- environment: nil,
yaml_variables: []
})
end
@@ -895,7 +864,7 @@ module Gitlab
end
context 'without matching job' do
- let(:close_review) { nil }
+ let(:close_review) { nil }
it 'raises error' do
expect { builds }.to raise_error('review job: on_stop job close_review is not defined')
@@ -983,15 +952,11 @@ module Gitlab
stage: "test",
stage_idx: 1,
name: "normal_job",
- commands: "test",
- coverage_regex: nil,
- tag_list: [],
options: {
script: ["test"]
},
when: "on_success",
allow_failure: false,
- environment: nil,
yaml_variables: []
})
end
@@ -1031,30 +996,22 @@ module Gitlab
stage: "build",
stage_idx: 0,
name: "job1",
- commands: "execute-script-for-job",
- coverage_regex: nil,
- tag_list: [],
options: {
script: ["execute-script-for-job"]
},
when: "on_success",
allow_failure: false,
- environment: nil,
yaml_variables: []
})
expect(subject.second).to eq({
stage: "build",
stage_idx: 0,
name: "job2",
- commands: "execute-script-for-job",
- coverage_regex: nil,
- tag_list: [],
options: {
script: ["execute-script-for-job"]
},
when: "on_success",
allow_failure: false,
- environment: nil,
yaml_variables: []
})
end
diff --git a/spec/lib/gitlab/config/entry/configurable_spec.rb b/spec/lib/gitlab/config/entry/configurable_spec.rb
index 85a7cf1d241..37e38e49c0d 100644
--- a/spec/lib/gitlab/config/entry/configurable_spec.rb
+++ b/spec/lib/gitlab/config/entry/configurable_spec.rb
@@ -7,6 +7,10 @@ describe Gitlab::Config::Entry::Configurable do
end
end
+ before do
+ allow(entry).to receive(:default)
+ end
+
describe 'validations' do
context 'when entry is a hash' do
let(:instance) { entry.new(key: 'value') }
@@ -26,9 +30,11 @@ describe Gitlab::Config::Entry::Configurable do
end
describe 'configured entries' do
+ let(:entry_class) { double('entry_class', default: nil) }
+
before do
- entry.class_eval do
- entry :object, Object, description: 'test object'
+ entry.class_exec(entry_class) do |entry_class|
+ entry :object, entry_class, description: 'test object'
end
end
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
index f22c2c90334..8a470e12d04 100644
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
@@ -301,8 +301,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -403,8 +402,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -505,8 +503,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -604,8 +601,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -729,8 +725,7 @@ describe Gitlab::Diff::LinesUnfolder do
expected_diff_lines.each_with_index do |expected_line, i|
line = new_diff_lines[i]
- expect([line.old_pos, line.new_pos, line.text])
- .to eq([expected_line[0], expected_line[1], expected_line[2]])
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
end
end
@@ -746,5 +741,105 @@ describe Gitlab::Diff::LinesUnfolder do
end
end
end
+
+ context 'position requires bottom expansion and no new match line' do
+ let(:position) do
+ Gitlab::Diff::Position.new(base_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
+ start_sha: "1c59dfa64afbea8c721bb09a06a9d326c952ea19",
+ head_sha: "1487062132228de836236c522fe52fed4980a46c",
+ old_path: "build-aux/flatpak/org.gnome.Nautilus.json",
+ new_path: "build-aux/flatpak/org.gnome.Nautilus.json",
+ position_type: "text",
+ old_line: 95,
+ new_line: 87)
+ end
+
+ context 'blob lines' do
+ let(:expected_blob_lines) do
+ [[94, 94, " \"url\": \"https://gitlab.gnome.org/GNOME/nautilus.git\""],
+ [95, 95, " }"],
+ [96, 96, " ]"],
+ [97, 97, " }"],
+ [98, 98, " ]"]]
+ end
+
+ it 'returns the extracted blob lines correctly' do
+ extracted_lines = subject.blob_lines
+
+ expect(extracted_lines.size).to eq(5)
+
+ extracted_lines.each_with_index do |line, i|
+ expect([line.old_line, line.new_line, line.text]).to eq(expected_blob_lines[i])
+ end
+ end
+ end
+
+ context 'diff lines' do
+ let(:expected_diff_lines) do
+ [[7, 7, "@@ -7,9 +7,6 @@"],
+ [7, 7, " \"tags\": [\"devel\", \"development\", \"nightly\"],"],
+ [8, 8, " \"desktop-file-name-prefix\": \"(Development) \","],
+ [9, 9, " \"finish-args\": ["],
+ [10, 10, "- \"--share=ipc\", \"--socket=x11\","],
+ [11, 10, "- \"--socket=wayland\","],
+ [12, 10, "- \"--talk-name=org.gnome.OnlineAccounts\","],
+ [13, 10, " \"--talk-name=org.freedesktop.Tracker1\","],
+ [14, 11, " \"--filesystem=home\","],
+ [15, 12, " \"--talk-name=org.gtk.vfs\", \"--talk-name=org.gtk.vfs.*\","],
+ [62, 59, "@@ -62,7 +59,7 @@"],
+ [62, 59, " },"],
+ [63, 60, " {"],
+ [64, 61, " \"name\": \"gnome-desktop\","],
+ [65, 62, "- \"config-opts\": [\"--disable-debug-tools\", \"--disable-udev\"],"],
+ [66, 62, "+ \"config-opts\": [\"--disable-debug-tools\", \"--disable-\"],"],
+ [66, 63, " \"sources\": ["],
+ [67, 64, " {"],
+ [68, 65, " \"type\": \"git\","],
+ [83, 80, "@@ -83,11 +80,6 @@"],
+ [83, 80, " \"buildsystem\": \"meson\","],
+ [84, 81, " \"builddir\": true,"],
+ [85, 82, " \"name\": \"nautilus\","],
+ [86, 83, "- \"config-opts\": ["],
+ [87, 83, "- \"-Denable-desktop=false\","],
+ [88, 83, "- \"-Denable-selinux=false\","],
+ [89, 83, "- \"--libdir=/app/lib\""],
+ [90, 83, "- ],"],
+ [91, 83, " \"sources\": ["],
+ [92, 84, " {"],
+ [93, 85, " \"type\": \"git\","],
+ # No new match line
+
+ # Injected blob lines
+ [94, 86, " \"url\": \"https://gitlab.gnome.org/GNOME/nautilus.git\""],
+ [95, 87, " }"],
+ [96, 88, " ]"],
+ [97, 89, " }"],
+ [98, 90, " ]"]]
+ # end
+ end
+
+ it 'return merge of blob lines with diff lines correctly' do
+ new_diff_lines = subject.unfolded_diff_lines
+
+ expected_diff_lines.each_with_index do |expected_line, i|
+ line = new_diff_lines[i]
+
+ expect([line.old_pos, line.new_pos, line.text]).to eq(expected_line)
+ end
+ end
+
+ it 'merged lines have correct line codes' do
+ new_diff_lines = subject.unfolded_diff_lines
+
+ new_diff_lines.each_with_index do |line, i|
+ old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+
+ unless line.type == 'match'
+ expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index a2eed07ca55..866550753a8 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -1515,8 +1515,8 @@ describe Gitlab::Diff::PositionTracer do
{ new_path: file_name, new_line: 4, change: true },
{ new_path: file_name, old_line: 3, change: true },
{ old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
- { new_path: file_name, old_line: 5, change: true },
- { new_path: file_name, new_line: 7 }
+ { new_path: file_name, old_line: 5, change: true },
+ { new_path: file_name, new_line: 7 }
]
expect_new_positions(old_position_attrs, new_position_attrs)
@@ -1588,7 +1588,7 @@ describe Gitlab::Diff::PositionTracer do
{ new_path: file_name, new_line: 4, change: true },
{ old_path: file_name, old_line: 3, change: true },
{ old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
- { old_path: file_name, old_line: 5, change: true },
+ { old_path: file_name, old_line: 5, change: true },
{ new_path: file_name, new_line: 7 }
]
@@ -1638,13 +1638,13 @@ describe Gitlab::Diff::PositionTracer do
new_position_attrs = [
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
{ old_path: file_name, old_line: 2 },
- { old_path: file_name, old_line: 2, change: true },
+ { old_path: file_name, old_line: 2, change: true },
{ old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 },
- { old_path: file_name, old_line: 4, change: true },
+ { old_path: file_name, old_line: 4, change: true },
{ old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 },
- { new_path: file_name, new_line: 5, change: true },
- { old_path: file_name, old_line: 6, change: true },
- { new_path: file_name, new_line: 6 }
+ { new_path: file_name, new_line: 5, change: true },
+ { old_path: file_name, old_line: 6, change: true },
+ { new_path: file_name, new_line: 6 }
]
expect_new_positions(old_position_attrs, new_position_attrs)
@@ -1880,7 +1880,7 @@ describe Gitlab::Diff::PositionTracer do
new_position_attrs = [
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
- { old_path: file_name, old_line: 2, change: true },
+ { old_path: file_name, old_line: 2, change: true },
{ new_path: file_name, new_line: 2 },
{ old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 },
{ new_path: file_name, new_line: 4 },
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index 1d75e8cb5da..48139c2f9dc 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
stub_config_setting(host: 'localhost')
end
- let(:email_raw) { fixture_file('emails/valid_new_issue.eml') }
+ let(:email_raw) { email_fixture('emails/valid_new_issue.eml') }
let(:namespace) { create(:namespace, path: 'gitlabhq') }
let!(:project) { create(:project, :public, namespace: namespace, path: 'gitlabhq') }
@@ -23,21 +23,58 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
)
end
+ context "when email key" do
+ let(:mail) { Mail::Message.new(email_raw) }
+
+ it "matches the new format" do
+ handler = described_class.new(mail, "gitlabhq-gitlabhq-#{project.project_id}-#{user.incoming_email_token}-issue")
+
+ expect(handler.instance_variable_get(:@project_id)).to eq project.project_id
+ expect(handler.instance_variable_get(:@project_slug)).to eq project.full_path_slug
+ expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "matches the legacy format" do
+ handler = described_class.new(mail, "h5bp/html5-boilerplate+#{user.incoming_email_token}")
+
+ expect(handler.instance_variable_get(:@project_path)).to eq 'h5bp/html5-boilerplate'
+ expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "doesn't match either format" do
+ handler = described_class.new(mail, "h5bp-html5-boilerplate+something+invalid")
+
+ expect(handler.can_handle?).to be_falsey
+ end
+ end
+
context "when everything is fine" do
- it "creates a new issue" do
- setup_attachment
+ shared_examples "a new issue" do
+ it "creates a new issue" do
+ setup_attachment
- expect { receiver.execute }.to change { project.issues.count }.by(1)
- issue = project.issues.last
+ expect { receiver.execute }.to change { project.issues.count }.by(1)
+ issue = project.issues.last
+
+ expect(issue.author).to eq(user)
+ expect(issue.title).to eq('New Issue by email')
+ expect(issue.description).to include('reply by email')
+ expect(issue.description).to include(markdown)
+ end
+ end
+
+ it_behaves_like "a new issue"
- expect(issue.author).to eq(user)
- expect(issue.title).to eq('New Issue by email')
- expect(issue.description).to include('reply by email')
- expect(issue.description).to include(markdown)
+ context "creates a new issue with legacy email address" do
+ let(:email_raw) { fixture_file('emails/valid_new_issue_legacy.eml') }
+
+ it_behaves_like "a new issue"
end
context "when the reply is blank" do
- let(:email_raw) { fixture_file("emails/valid_new_issue_empty.eml") }
+ let(:email_raw) { email_fixture("emails/valid_new_issue_empty.eml") }
it "creates a new issue" do
expect { receiver.execute }.to change { project.issues.count }.by(1)
@@ -50,7 +87,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
end
context "when there are quotes in email" do
- let(:email_raw) { fixture_file("emails/valid_new_issue_with_quote.eml") }
+ let(:email_raw) { email_fixture("emails/valid_new_issue_with_quote.eml") }
it "creates a new issue" do
expect { receiver.execute }.to change { project.issues.count }.by(1)
@@ -76,7 +113,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
end
context "when we can't find the incoming_email_token" do
- let(:email_raw) { fixture_file("emails/wrong_incoming_email_token.eml") }
+ let(:email_raw) { email_fixture("emails/wrong_issue_incoming_email_token.eml") }
it "raises an UserNotFoundError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
@@ -91,4 +128,8 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
end
end
end
+
+ def email_fixture(path)
+ fixture_file(path).gsub('project_id', project.project_id.to_s)
+ end
end
diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
index f276f1a8ddf..2fa86b2b46f 100644
--- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
TestEnv.clean_test_path
end
- let(:email_raw) { fixture_file('emails/valid_new_merge_request.eml') }
+ let(:email_raw) { email_fixture('emails/valid_new_merge_request.eml') }
let(:namespace) { create(:namespace, path: 'gitlabhq') }
let!(:project) { create(:project, :public, :repository, namespace: namespace, path: 'gitlabhq') }
@@ -27,6 +27,33 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
)
end
+ context "when email key" do
+ let(:mail) { Mail::Message.new(email_raw) }
+
+ it "matches the new format" do
+ handler = described_class.new(mail, "gitlabhq-gitlabhq-#{project.project_id}-#{user.incoming_email_token}-merge-request")
+
+ expect(handler.instance_variable_get(:@project_id)).to eq project.project_id
+ expect(handler.instance_variable_get(:@project_slug)).to eq project.full_path_slug
+ expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "matches the legacy format" do
+ handler = described_class.new(mail, "h5bp/html5-boilerplate+merge-request+#{user.incoming_email_token}")
+
+ expect(handler.instance_variable_get(:@project_path)).to eq 'h5bp/html5-boilerplate'
+ expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "doesn't match either format" do
+ handler = described_class.new(mail, "h5bp-html5-boilerplate+merge-request")
+
+ expect(handler.can_handle?).to be_falsey
+ end
+ end
+
context "as a non-developer" do
before do
project.add_guest(user)
@@ -43,15 +70,25 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context "when everything is fine" do
- it "creates a new merge request" do
- expect { receiver.execute }.to change { project.merge_requests.count }.by(1)
- merge_request = project.merge_requests.last
-
- expect(merge_request.author).to eq(user)
- expect(merge_request.source_branch).to eq('feature')
- expect(merge_request.title).to eq('Feature added')
- expect(merge_request.description).to eq('Merge request description')
- expect(merge_request.target_branch).to eq(project.default_branch)
+ shared_examples "a new merge request" do
+ it "creates a new merge request" do
+ expect { receiver.execute }.to change { project.merge_requests.count }.by(1)
+ merge_request = project.merge_requests.last
+
+ expect(merge_request.author).to eq(user)
+ expect(merge_request.source_branch).to eq('feature')
+ expect(merge_request.title).to eq('Feature added')
+ expect(merge_request.description).to eq('Merge request description')
+ expect(merge_request.target_branch).to eq(project.default_branch)
+ end
+ end
+
+ it_behaves_like "a new merge request"
+
+ context "creates a new merge request with legacy email address" do
+ let(:email_raw) { fixture_file('emails/valid_new_merge_request_legacy.eml') }
+
+ it_behaves_like "a new merge request"
end
end
@@ -67,7 +104,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context "when we can't find the incoming_email_token" do
- let(:email_raw) { fixture_file("emails/wrong_incoming_email_token.eml") }
+ let(:email_raw) { email_fixture("emails/wrong_merge_request_incoming_email_token.eml") }
it "raises an UserNotFoundError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
@@ -75,7 +112,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context "when the subject is blank" do
- let(:email_raw) { fixture_file("emails/valid_new_merge_request_no_subject.eml") }
+ let(:email_raw) { email_fixture("emails/valid_new_merge_request_no_subject.eml") }
it "raises an InvalidMergeRequestError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidMergeRequestError)
@@ -83,7 +120,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context "when the message body is blank" do
- let(:email_raw) { fixture_file("emails/valid_new_merge_request_no_description.eml") }
+ let(:email_raw) { email_fixture("emails/valid_new_merge_request_no_description.eml") }
it "creates a new merge request with description set from the last commit" do
expect { receiver.execute }.to change { project.merge_requests.count }.by(1)
@@ -95,7 +132,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context 'when the email contains patch attachments' do
- let(:email_raw) { fixture_file("emails/valid_merge_request_with_patch.eml") }
+ let(:email_raw) { email_fixture("emails/valid_merge_request_with_patch.eml") }
it 'creates the source branch and applies the patches' do
receiver.execute
@@ -120,7 +157,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context 'when the patch could not be applied' do
- let(:email_raw) { fixture_file("emails/merge_request_with_conflicting_patch.eml") }
+ let(:email_raw) { email_fixture("emails/merge_request_with_conflicting_patch.eml") }
it 'raises an error' do
expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidAttachment)
@@ -128,7 +165,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
context 'when specifying the target branch using quick actions' do
- let(:email_raw) { fixture_file('emails/merge_request_with_patch_and_target_branch.eml') }
+ let(:email_raw) { email_fixture('emails/merge_request_with_patch_and_target_branch.eml') }
it 'creates the merge request with the correct target branch' do
receiver.execute
@@ -150,7 +187,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
end
describe '#patch_attachments' do
- let(:email_raw) { fixture_file('emails/merge_request_multiple_patches.eml') }
+ let(:email_raw) { email_fixture('emails/merge_request_multiple_patches.eml') }
let(:mail) { Mail::Message.new(email_raw) }
subject(:handler) { described_class.new(mail, mail_key) }
@@ -163,4 +200,8 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do
expect(attachments).to eq(expected_filenames)
end
end
+
+ def email_fixture(path)
+ fixture_file(path).gsub('project_id', project.project_id.to_s)
+ end
end
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
index b8660b133ec..dcddd00df59 100644
--- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -10,13 +10,35 @@ describe Gitlab::Email::Handler::UnsubscribeHandler do
stub_config_setting(host: 'localhost')
end
- let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}+unsubscribe") }
- let(:project) { create(:project, :public) }
- let(:user) { create(:user) }
- let(:noteable) { create(:issue, project: project) }
+ let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}") }
+ let(:project) { create(:project, :public) }
+ let(:user) { create(:user) }
+ let(:noteable) { create(:issue, project: project) }
let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) }
+ context "when email key" do
+ let(:mail) { Mail::Message.new(email_raw) }
+
+ it "matches the new format" do
+ handler = described_class.new(mail, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}")
+
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "matches the legacy format" do
+ handler = described_class.new(mail, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY}")
+
+ expect(handler.can_handle?).to be_truthy
+ end
+
+ it "doesn't match either format" do
+ handler = described_class.new(mail, "+#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}")
+
+ expect(handler.can_handle?).to be_falsey
+ end
+ end
+
context 'when notification concerns a commit' do
let(:commit) { create(:commit, project: project) }
let!(:sent_notification) { SentNotification.record(commit, user.id, mail_key) }
@@ -40,6 +62,14 @@ describe Gitlab::Email::Handler::UnsubscribeHandler do
it 'unsubscribes user from notable' do
expect { receiver.execute }.to change { noteable.subscribed?(user) }.from(true).to(false)
end
+
+ context 'when using old style unsubscribe link' do
+ let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY}") }
+
+ it 'unsubscribes user from notable' do
+ expect { receiver.execute }.to change { noteable.subscribed?(user) }.from(true).to(false)
+ end
+ end
end
context 'when the noteable could not be found' do
diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index c651765dc0f..d2920b08956 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -19,7 +19,8 @@ describe Gitlab::Email::Handler do
describe 'regexps are set properly' do
let(:addresses) do
- %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token)
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path-to-project-123-user_email_token-merge-request path-to-project-123-user_email_token-issue) +
+ %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token)
end
it 'picks each handler at least once' do
diff --git a/spec/lib/gitlab/git/bundle_file_spec.rb b/spec/lib/gitlab/git/bundle_file_spec.rb
new file mode 100644
index 00000000000..ff7c981dadd
--- /dev/null
+++ b/spec/lib/gitlab/git/bundle_file_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::Git::BundleFile do
+ describe '.check!' do
+ let(:valid_bundle) { Tempfile.new }
+ let(:valid_bundle_path) { valid_bundle.path }
+ let(:invalid_bundle_path) { Rails.root.join('spec/fixtures/malicious.bundle') }
+
+ after do
+ valid_bundle.close!
+ end
+
+ it 'returns nil for a valid bundle' do
+ valid_bundle.write("# v2 git bundle\nfoo bar baz\n")
+ valid_bundle.close
+
+ expect(described_class.check!(valid_bundle_path)).to be_nil
+ end
+
+ it 'raises an exception for an invalid bundle' do
+ expect do
+ described_class.check!(invalid_bundle_path)
+ end.to raise_error(described_class::InvalidBundleError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb
index 7cc6f52f8ee..771c71a16a9 100644
--- a/spec/lib/gitlab/git/compare_spec.rb
+++ b/spec/lib/gitlab/git/compare_spec.rb
@@ -67,7 +67,7 @@ describe Gitlab::Git::Compare, :seed_helper do
end
end
- describe '#same' do
+ describe '#same' do
subject do
compare.same
end
diff --git a/spec/lib/gitlab/git/merge_base_spec.rb b/spec/lib/gitlab/git/merge_base_spec.rb
index 8d16d451730..dbb4e3d0b3e 100644
--- a/spec/lib/gitlab/git/merge_base_spec.rb
+++ b/spec/lib/gitlab/git/merge_base_spec.rb
@@ -62,7 +62,7 @@ describe Gitlab::Git::MergeBase do
end
describe '#commit' do
- context 'for existing refs with a merge base', :existing_refs do
+ context 'for existing refs with a merge base', :existing_refs do
it 'finds the commit for the merge base' do
expect(merge_base.commit).to be_a(Commit)
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 852ee9c96af..a19e3e84f83 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1753,22 +1753,23 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#create_from_bundle' do
- let(:bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+ let(:valid_bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+ let(:malicious_bundle_path) { Rails.root.join('spec/fixtures/malicious.bundle') }
let(:project) { create(:project) }
let(:imported_repo) { project.repository.raw }
before do
- expect(repository.bundle_to_disk(bundle_path)).to be_truthy
+ expect(repository.bundle_to_disk(valid_bundle_path)).to be_truthy
end
after do
- FileUtils.rm_rf(bundle_path)
+ FileUtils.rm_rf(valid_bundle_path)
end
it 'creates a repo from a bundle file' do
expect(imported_repo).not_to exist
- result = imported_repo.create_from_bundle(bundle_path)
+ result = imported_repo.create_from_bundle(valid_bundle_path)
expect(result).to be_truthy
expect(imported_repo).to exist
@@ -1776,11 +1777,17 @@ describe Gitlab::Git::Repository, :seed_helper do
end
it 'creates a symlink to the global hooks dir' do
- imported_repo.create_from_bundle(bundle_path)
+ 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.config.gitlab_shell.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)
+ end.to raise_error(::Gitlab::Git::BundleFile::InvalidBundleError)
+ end
end
describe '#checksum' do
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 8d8eb50ad76..3e34dd592f2 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::GitAccess do
let(:authentication_abilities) { %i[read_project download_code push_code] }
let(:redirected_path) { nil }
let(:auth_result_type) { nil }
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
let(:push_access_check) { access.check('git-receive-pack', changes) }
let(:pull_access_check) { access.check('git-upload-pack', changes) }
@@ -437,7 +437,7 @@ describe Gitlab::GitAccess do
let(:project) { nil }
context 'when changes is _any' do
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
context 'when authentication abilities include push code' do
let(:authentication_abilities) { [:push_code] }
@@ -483,7 +483,7 @@ describe Gitlab::GitAccess do
end
context 'when project exists' do
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
let!(:project) { create(:project) }
it 'does not create a new project' do
@@ -497,7 +497,7 @@ describe Gitlab::GitAccess do
let(:project_path) { "nonexistent" }
let(:project) { nil }
let(:namespace_path) { user.namespace.path }
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
it 'does not create a new project' do
expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count }
@@ -507,7 +507,7 @@ describe Gitlab::GitAccess do
context 'when pull' do
let(:cmd) { 'git-upload-pack' }
- let(:changes) { '_any' }
+ let(:changes) { Gitlab::GitAccess::ANY }
context 'when project does not exist' do
let(:project_path) { "new-project" }
@@ -736,7 +736,8 @@ describe Gitlab::GitAccess do
end
let(:changes) do
- { push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
+ { any: Gitlab::GitAccess::ANY,
+ push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
push_master: '6f6d7e7ed 570e7b2ab refs/heads/master',
push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature',
push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\
@@ -798,6 +799,7 @@ describe Gitlab::GitAccess do
permissions_matrix = {
admin: {
+ any: true,
push_new_branch: true,
push_master: true,
push_protected_branch: true,
@@ -809,6 +811,7 @@ describe Gitlab::GitAccess do
},
maintainer: {
+ any: true,
push_new_branch: true,
push_master: true,
push_protected_branch: true,
@@ -820,6 +823,7 @@ describe Gitlab::GitAccess do
},
developer: {
+ any: true,
push_new_branch: true,
push_master: true,
push_protected_branch: false,
@@ -831,6 +835,7 @@ describe Gitlab::GitAccess do
},
reporter: {
+ any: false,
push_new_branch: false,
push_master: false,
push_protected_branch: false,
@@ -842,6 +847,7 @@ describe Gitlab::GitAccess do
},
guest: {
+ any: false,
push_new_branch: false,
push_master: false,
push_protected_branch: false,
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 9c6c9fe13bf..6ba65b56618 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -38,7 +38,7 @@ describe Gitlab::GitAccessWiki do
end
describe '#access_check_download!' do
- subject { access.check('git-upload-pack', '_any') }
+ subject { access.check('git-upload-pack', Gitlab::GitAccess::ANY) }
before do
project.add_developer(user)
diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb
index c6f09ca2112..1ff2334bacf 100644
--- a/spec/lib/gitlab/gon_helper_spec.rb
+++ b/spec/lib/gitlab/gon_helper_spec.rb
@@ -29,4 +29,13 @@ describe Gitlab::GonHelper do
helper.push_frontend_feature_flag(:my_feature_flag, 10)
end
end
+
+ describe '#default_avatar_url' do
+ it 'returns an absolute URL' do
+ url = helper.default_avatar_url
+
+ expect(url).to match(/^http/)
+ expect(url).to match(/no_avatar.*png$/)
+ end
+ end
end
diff --git a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
index 96615ae80de..9bcc1e78a78 100644
--- a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::Graphql::Connections::KeysetConnection do
let(:nodes) { Project.all.order(id: :asc) }
- let(:arguments) { {} }
+ let(:arguments) { {} }
subject(:connection) do
described_class.new(nodes, arguments, max_page_size: 3)
end
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
index 7eac2cacb90..01d43ed00a2 100644
--- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb
+++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
@@ -19,15 +19,6 @@ describe Gitlab::HashedStorage::Migrator do
end
end
- it 'sets projects as read only' do
- allow(ProjectMigrateHashedStorageWorker).to receive(:perform_async).twice
- subject.bulk_migrate(ids.min, ids.max)
-
- projects.each do |project|
- expect(project.reload.repository_read_only?).to be_truthy
- end
- end
-
it 'rescues and log exceptions' do
allow_any_instance_of(Project).to receive(:migrate_to_hashed_storage!).and_raise(StandardError)
expect { subject.bulk_migrate(ids.min, ids.max) }.not_to raise_error
@@ -40,6 +31,16 @@ describe Gitlab::HashedStorage::Migrator do
subject.bulk_migrate(ids.min, ids.max)
end
+
+ it 'has migrated projects set as writable' do
+ perform_enqueued_jobs do
+ subject.bulk_migrate(ids.min, ids.max)
+ end
+
+ projects.each do |project|
+ expect(project.reload.repository_read_only?).to be_falsey
+ end
+ end
end
describe '#migrate' do
@@ -57,19 +58,20 @@ describe Gitlab::HashedStorage::Migrator do
expect { subject.migrate(project) }.not_to raise_error
end
- it 'sets project as read only' do
- allow(ProjectMigrateHashedStorageWorker).to receive(:perform_async)
- subject.migrate(project)
+ it 'migrate project' do
+ perform_enqueued_jobs do
+ subject.migrate(project)
+ end
- expect(project.reload.repository_read_only?).to be_truthy
+ expect(project.reload.hashed_storage?(:attachments)).to be_truthy
end
- it 'migrate project' do
+ it 'has migrated project set as writable' do
perform_enqueued_jobs do
subject.migrate(project)
end
- expect(project.reload.hashed_storage?(:attachments)).to be_truthy
+ expect(project.reload.repository_read_only?).to be_falsey
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index c8c74883640..6897ac8a3a8 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -66,6 +66,9 @@ snippets:
releases:
- author
- project
+- links
+links:
+- release
project_members:
- created_by
- user
@@ -111,6 +114,7 @@ ci_pipelines:
- stages
- statuses
- builds
+- processables
- trigger_requests
- variables
- auto_canceled_by
@@ -134,6 +138,7 @@ stages:
- pipeline
- statuses
- builds
+- bridges
statuses:
- project
- pipeline
@@ -311,6 +316,7 @@ project:
- repository_languages
- pool_repository
- kubernetes_namespaces
+- error_tracking_setting
award_emoji:
- awardable
- user
@@ -342,3 +348,5 @@ resource_label_events:
- merge_request
- epic
- label
+error_tracking_setting:
+- project
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 11f98d782b1..898e4d07760 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -29,7 +29,7 @@ describe Gitlab::ImportExport::Importer do
expect(shared.errors).to be_empty
end
- it 'extracts the archive' do
+ it 'extracts the archive' do
expect(Gitlab::ImportExport::FileImporter).to receive(:import).and_call_original
importer.execute
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index 1ef024d3078..f93ff074770 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Reader do
+describe Gitlab::ImportExport::Reader do
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
let(:test_config) { 'spec/support/import_export/import_export.yml' }
let(:project_tree_hash) do
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 24b1f2d995b..fe2087e8fc3 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -120,6 +120,13 @@ Release:
- project_id
- created_at
- updated_at
+Releases::Link:
+- id
+- release_id
+- url
+- name
+- created_at
+- updated_at
ProjectMember:
- id
- access_level
@@ -593,3 +600,8 @@ ResourceLabelEvent:
- label_id
- user_id
- created_at
+ErrorTracking::ProjectErrorTrackingSetting:
+- id
+- api_url
+- enabled
+- project_id
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index 4c0c3fcbcc7..2db62ab983a 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -61,7 +61,7 @@ describe Gitlab::IncomingEmail do
end
it 'returns the address with interpolated reply key and unsubscribe suffix' do
- expect(described_class.unsubscribe_address('key')).to eq('replies+key+unsubscribe@example.com')
+ expect(described_class.unsubscribe_address('key')).to eq("replies+key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}@example.com")
end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 2dd3a570a1d..27c802f34ec 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -2,8 +2,8 @@ require 'rails_helper'
describe Gitlab::Kubernetes::Helm::Pod do
describe '#generate' do
- let(:app) { create(:clusters_applications_prometheus) }
- let(:command) { app.install_command }
+ let(:app) { create(:clusters_applications_prometheus) }
+ let(:command) { app.install_command }
let(:namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
let(:service_account_name) { nil }
@@ -30,7 +30,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'should generate the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
- expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.11.0-kube-1.11.0')
+ expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.2-kube-1.11.0')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index 8fc85301304..02364e92149 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -24,6 +24,32 @@ describe Gitlab::Kubernetes::KubeClient do
end
end
+ shared_examples 'redirection not allowed' do |method_name|
+ before do
+ redirect_url = 'https://not-under-our-control.example.com/api/v1/pods'
+
+ stub_request(:get, %r{\A#{api_url}/})
+ .to_return(status: 302, headers: { location: redirect_url })
+
+ stub_request(:get, redirect_url)
+ .to_return(status: 200, body: '{}')
+ end
+
+ it 'does not follow redirects' do
+ method_call = -> do
+ case method_name
+ when /\A(get_|delete_)/
+ client.public_send(method_name)
+ when /\A(create_|update_)/
+ client.public_send(method_name, {})
+ else
+ raise "Unknown method name #{method_name}"
+ end
+ end
+ expect { method_call.call }.to raise_error(Kubeclient::HttpError)
+ end
+ end
+
describe '#core_client' do
subject { client.core_client }
@@ -103,6 +129,8 @@ describe Gitlab::Kubernetes::KubeClient do
:update_service_account
].each do |method|
describe "##{method}" do
+ include_examples 'redirection not allowed', method
+
it 'delegates to the core client' do
expect(client).to delegate_method(method).to(:core_client)
end
@@ -123,6 +151,8 @@ describe Gitlab::Kubernetes::KubeClient do
:update_cluster_role_binding
].each do |method|
describe "##{method}" do
+ include_examples 'redirection not allowed', method
+
it 'delegates to the rbac client' do
expect(client).to delegate_method(method).to(:rbac_client)
end
@@ -139,6 +169,8 @@ describe Gitlab::Kubernetes::KubeClient do
let(:extensions_client) { client.extensions_client }
describe '#get_deployments' do
+ include_examples 'redirection not allowed', 'get_deployments'
+
it 'delegates to the extensions client' do
expect(client).to delegate_method(:get_deployments).to(:extensions_client)
end
diff --git a/spec/lib/gitlab/loop_helpers_spec.rb b/spec/lib/gitlab/loop_helpers_spec.rb
new file mode 100644
index 00000000000..e17a0342d64
--- /dev/null
+++ b/spec/lib/gitlab/loop_helpers_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::LoopHelpers do
+ let(:class_instance) { (Class.new { include ::Gitlab::LoopHelpers }).new }
+
+ describe '#loop_until' do
+ subject do
+ class_instance.loop_until(**params) { true }
+ end
+
+ context 'when limit is not given' do
+ let(:params) { { limit: nil } }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when timeout is specified' do
+ let(:params) { { timeout: 1.second } }
+
+ it "returns false after it's expired" do
+ is_expected.to be_falsy
+ end
+
+ it 'executes the block at least once' do
+ expect { |b| class_instance.loop_until(**params, &b) }
+ .to yield_control.at_least(1)
+ end
+ end
+
+ context 'when iteration limit is specified' do
+ let(:params) { { limit: 1 } }
+
+ it "returns false after it's expired" do
+ is_expected.to be_falsy
+ end
+
+ it 'executes the block once' do
+ expect { |b| class_instance.loop_until(**params, &b) }
+ .to yield_control.once
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index daf454665b0..3f6ada6832a 100644
--- a/spec/lib/gitlab/middleware/multipart_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -10,7 +10,9 @@ describe Gitlab::Middleware::Multipart do
shared_examples_for 'multipart upload files' do
it 'opens top-level files' do
Tempfile.open('top-level') do |tempfile|
- env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path, 'file.remote_id' => remote_id }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
+ rewritten = { 'file' => tempfile.path }
+ in_params = { 'file.name' => original_filename, 'file.path' => tempfile.path, 'file.remote_id' => remote_id }
+ env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(file))
@@ -21,7 +23,8 @@ describe Gitlab::Middleware::Multipart do
it 'opens files one level deep' do
Tempfile.open('one-level') do |tempfile|
in_params = { 'user' => { 'avatar' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } }
- env = post_env({ 'user[avatar]' => tempfile.path }, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
+ rewritten = { 'user[avatar]' => tempfile.path }
+ env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(user avatar))
@@ -32,7 +35,8 @@ describe Gitlab::Middleware::Multipart do
it 'opens files two levels deep' do
Tempfile.open('two-levels') do |tempfile|
in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } } }
- env = post_env({ 'project[milestone][themesong]' => tempfile.path }, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
+ rewritten = { 'project[milestone][themesong]' => tempfile.path }
+ env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(project milestone themesong))
@@ -42,7 +46,7 @@ describe Gitlab::Middleware::Multipart do
def expect_uploaded_file(tempfile, path, remote: false)
expect(app).to receive(:call) do |env|
- file = Rack::Request.new(env).params.dig(*path)
+ file = get_params(env).dig(*path)
expect(file).to be_a(::UploadedFile)
expect(file.path).to eq(tempfile.path)
expect(file.original_filename).to eq(original_filename)
@@ -87,7 +91,7 @@ describe Gitlab::Middleware::Multipart do
env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect(app).to receive(:call) do |env|
- expect(Rack::Request.new(env).params['file']).to be_a(::UploadedFile)
+ expect(get_params(env)['file']).to be_a(::UploadedFile)
end
middleware.call(env)
@@ -115,13 +119,21 @@ describe Gitlab::Middleware::Multipart do
allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir'))
expect(app).to receive(:call) do |env|
- expect(Rack::Request.new(env).params['file']).to be_a(::UploadedFile)
+ expect(get_params(env)['file']).to be_a(::UploadedFile)
end
middleware.call(env)
end
end
+ # Rails 5 doesn't combine the GET/POST parameters in
+ # ActionDispatch::HTTP::Parameters if action_dispatch.request.parameters is set:
+ # https://github.com/rails/rails/blob/aea6423f013ca48f7704c70deadf2cd6ac7d70a1/actionpack/lib/action_dispatch/http/parameters.rb#L41
+ def get_params(env)
+ req = ActionDispatch::Request.new(env)
+ req.GET.merge(req.POST)
+ end
+
def post_env(rewritten_fields, params, secret, issuer)
token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256')
Rack::MockRequest.env_for(
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index bdb1f34d2f6..24d49a049b6 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -101,16 +101,36 @@ describe Gitlab::Middleware::ReadOnly do
expect(subject).not_to disallow_request
end
- it 'expects requests to sidekiq admin to be allowed' do
- response = request.post('/admin/sidekiq')
+ context 'sidekiq admin requests' do
+ where(:mounted_at) do
+ [
+ '',
+ '/',
+ '/gitlab',
+ '/gitlab/',
+ '/gitlab/gitlab',
+ '/gitlab/gitlab/'
+ ]
+ end
- expect(response).not_to be_redirect
- expect(subject).not_to disallow_request
+ with_them do
+ before do
+ stub_config_setting(relative_url_root: mounted_at)
+ end
- response = request.get('/admin/sidekiq')
+ it 'allows requests' do
+ path = File.join(mounted_at, 'admin/sidekiq')
+ response = request.post(path)
- expect(response).not_to be_redirect
- expect(subject).not_to disallow_request
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+
+ response = request.get(path)
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+ end
+ end
end
where(:description, :path) do
diff --git a/spec/lib/gitlab/release_blog_post_spec.rb b/spec/lib/gitlab/release_blog_post_spec.rb
new file mode 100644
index 00000000000..2c987df3767
--- /dev/null
+++ b/spec/lib/gitlab/release_blog_post_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+describe Gitlab::ReleaseBlogPost do
+ describe '.blog_post_url' do
+ let(:releases_xml) do
+ <<~EOS
+ <?xml version='1.0' encoding='utf-8' ?>
+ <feed xmlns='http://www.w3.org/2005/Atom'>
+ <entry>
+ <release>11.2</release>
+ <id>https://about.gitlab.com/2018/08/22/gitlab-11-2-released/</id>
+ </entry>
+ <entry>
+ <release>11.1</release>
+ <id>https://about.gitlab.com/2018/07/22/gitlab-11-1-released/</id>
+ </entry>
+ <entry>
+ <release>11.0</release>
+ <id>https://about.gitlab.com/2018/06/22/gitlab-11-0-released/</id>
+ </entry>
+ <entry>
+ <release>10.8</release>
+ <id>https://about.gitlab.com/2018/05/22/gitlab-10-8-released/</id>
+ </entry>
+ </feed>
+ EOS
+ end
+
+ subject { described_class.send(:new).blog_post_url }
+
+ before do
+ stub_request(:get, 'https://about.gitlab.com/releases.xml')
+ .to_return(status: 200, headers: { 'content-type' => ['text/xml'] }, body: releases_xml)
+ end
+
+ context 'matches GitLab version to blog post url' do
+ it 'returns the correct url for major pre release' do
+ stub_const('Gitlab::VERSION', '11.0.0-pre')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/05/22/gitlab-10-8-released/')
+ end
+
+ it 'returns the correct url for major release candidate' do
+ stub_const('Gitlab::VERSION', '11.0.0-rc3')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/05/22/gitlab-10-8-released/')
+ end
+
+ it 'returns the correct url for major release' do
+ stub_const('Gitlab::VERSION', '11.0.0')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/06/22/gitlab-11-0-released/')
+ end
+
+ it 'returns the correct url for minor pre release' do
+ stub_const('Gitlab::VERSION', '11.2.0-pre')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/07/22/gitlab-11-1-released/')
+ end
+
+ it 'returns the correct url for minor release candidate' do
+ stub_const('Gitlab::VERSION', '11.2.0-rc3')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/07/22/gitlab-11-1-released/')
+ end
+
+ it 'returns the correct url for minor release' do
+ stub_const('Gitlab::VERSION', '11.2.0')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/')
+ end
+
+ it 'returns the correct url for patch pre release' do
+ stub_const('Gitlab::VERSION', '11.2.1-pre')
+ expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/')
+ end
+
+ it 'returns the correct url for patch release candidate' do
+ stub_const('Gitlab::VERSION', '11.2.1-rc3')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/')
+ end
+
+ it 'returns the correct url for patch release' do
+ stub_const('Gitlab::VERSION', '11.2.1')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/')
+ end
+
+ it 'returns nil when no blog post is matched' do
+ stub_const('Gitlab::VERSION', '9.0.0')
+
+ expect(subject).to be(nil)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
index 1b9a8b4ab0d..741ee12633f 100644
--- a/spec/lib/gitlab/repository_cache_spec.rb
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -4,14 +4,14 @@ describe Gitlab::RepositoryCache do
let(:backend) { double('backend').as_null_object }
let(:project) { create(:project) }
let(:repository) { project.repository }
- let(:namespace) { "project:#{project.id}" }
+ let(:namespace) { "#{repository.full_path}:#{project.id}" }
let(:cache) { described_class.new(repository, backend: backend) }
describe '#cache_key' do
subject { cache.cache_key(:foo) }
it 'includes the namespace' do
- expect(subject).to eq "#{namespace}:foo"
+ expect(subject).to eq "foo:#{namespace}"
end
context 'with a given namespace' do
@@ -22,7 +22,7 @@ describe Gitlab::RepositoryCache do
end
it 'includes the full namespace' do
- expect(subject).to eq "#{namespace}:#{extra_namespace}:foo"
+ expect(subject).to eq "foo:#{namespace}:#{extra_namespace}"
end
end
end
@@ -30,21 +30,21 @@ describe Gitlab::RepositoryCache do
describe '#expire' do
it 'expires the given key from the cache' do
cache.expire(:foo)
- expect(backend).to have_received(:delete).with("#{namespace}:foo")
+ expect(backend).to have_received(:delete).with("foo:#{namespace}")
end
end
describe '#fetch' do
it 'fetches the given key from the cache' do
cache.fetch(:bar)
- expect(backend).to have_received(:fetch).with("#{namespace}:bar")
+ expect(backend).to have_received(:fetch).with("bar:#{namespace}")
end
it 'accepts a block' do
p = -> {}
cache.fetch(:baz, &p)
- expect(backend).to have_received(:fetch).with("#{namespace}:baz", &p)
+ expect(backend).to have_received(:fetch).with("baz:#{namespace}", &p)
end
end
@@ -67,7 +67,7 @@ describe Gitlab::RepositoryCache do
end
it 'caches the value' do
- expect(backend).to receive(:write).with("#{namespace}:#{key}", true)
+ expect(backend).to receive(:write).with("#{key}:#{namespace}", true)
cache.fetch_without_caching_false(key) { true }
end
@@ -83,7 +83,7 @@ describe Gitlab::RepositoryCache do
end
it 'does not cache the value' do
- expect(backend).not_to receive(:write).with("#{namespace}:#{key}", true)
+ expect(backend).not_to receive(:write).with("#{key}:#{namespace}", true)
cache.fetch_without_caching_false(key, &p)
end
@@ -92,7 +92,7 @@ describe Gitlab::RepositoryCache do
context 'when the cached value is truthy' do
before do
- backend.write("#{namespace}:#{key}", true)
+ backend.write("#{key}:#{namespace}", true)
end
it 'returns the cached value' do
@@ -116,7 +116,7 @@ describe Gitlab::RepositoryCache do
context 'when the cached value is falsey' do
before do
- backend.write("#{namespace}:#{key}", false)
+ backend.write("#{key}:#{namespace}", false)
end
it 'returns the result of the block' do
@@ -126,7 +126,7 @@ describe Gitlab::RepositoryCache do
end
it 'writes the truthy value to the cache' do
- expect(backend).to receive(:write).with("#{namespace}:#{key}", 'block result')
+ expect(backend).to receive(:write).with("#{key}:#{namespace}", 'block result')
cache.fetch_without_caching_false(key) { 'block result' }
end
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index 8a28ad0e597..fd443cc1f71 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::RequestContext do
let(:ip) { '192.168.1.11' }
before do
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
+ allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return(ip)
described_class.new(app).call(env)
end
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index f773f370ee2..a9d15f1d522 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -82,15 +82,36 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
end.to raise_error(ArgumentError)
end
end
+
+ context 'when the job args are bigger than the maximum allowed' do
+ it 'keeps args from the front until they exceed the limit' do
+ Timecop.freeze(timestamp) do
+ job['args'] = [
+ 1,
+ 2,
+ 'a' * (described_class::MAXIMUM_JOB_ARGUMENTS_LENGTH / 2),
+ 'b' * (described_class::MAXIMUM_JOB_ARGUMENTS_LENGTH / 2),
+ 3
+ ]
+
+ expected_args = job['args'].take(3) + ['...']
+
+ expect(logger).to receive(:info).with(start_payload.merge('args' => expected_args)).ordered
+ expect(logger).to receive(:info).with(end_payload.merge('args' => expected_args)).ordered
+ expect(subject).to receive(:log_job_start).and_call_original
+ expect(subject).to receive(:log_job_done).and_call_original
+
+ subject.call(job, 'test_queue') { }
+ end
+ end
+ end
end
context 'with SIDEKIQ_LOG_ARGUMENTS disabled' do
- it 'logs start and end of job' do
+ it 'logs start and end of job without args' do
Timecop.freeze(timestamp) do
- start_payload.delete('args')
-
- expect(logger).to receive(:info).with(start_payload).ordered
- expect(logger).to receive(:info).with(end_payload).ordered
+ expect(logger).to receive(:info).with(start_payload.except('args')).ordered
+ expect(logger).to receive(:info).with(end_payload.except('args')).ordered
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
diff --git a/spec/lib/gitlab/slash_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
index 724c76ade6e..59de11766d8 100644
--- a/spec/lib/gitlab/slash_commands/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
@@ -44,7 +44,7 @@ describe Gitlab::SlashCommands::IssueNew do
end
context 'issue cannot be created' do
- let!(:issue) { create(:issue, project: project, title: 'bird is the word') }
+ let!(:issue) { create(:issue, project: project, title: 'bird is the word') }
let(:regex_match) { described_class.match("issue create #{'a' * 512}}") }
it 'displays the errors' do
diff --git a/spec/lib/gitlab/tracing/factory_spec.rb b/spec/lib/gitlab/tracing/factory_spec.rb
new file mode 100644
index 00000000000..945490f0988
--- /dev/null
+++ b/spec/lib/gitlab/tracing/factory_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::Factory do
+ describe '.create_tracer' do
+ let(:service_name) { 'rspec' }
+
+ context "when tracing is not configured" do
+ it 'ignores null connection strings' do
+ expect(described_class.create_tracer(service_name, nil)).to be_nil
+ end
+
+ it 'ignores empty connection strings' do
+ expect(described_class.create_tracer(service_name, '')).to be_nil
+ end
+
+ it 'ignores unknown implementations' do
+ expect(described_class.create_tracer(service_name, 'opentracing://invalid_driver')).to be_nil
+ end
+
+ it 'ignores invalid connection strings' do
+ expect(described_class.create_tracer(service_name, 'open?tracing')).to be_nil
+ end
+ end
+
+ context "when tracing is configured with jaeger" do
+ let(:mock_tracer) { double('tracer') }
+
+ it 'processes default connections' do
+ expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, {}).and_return(mock_tracer)
+
+ expect(described_class.create_tracer(service_name, 'opentracing://jaeger')).to be(mock_tracer)
+ end
+
+ it 'processes connections with parameters' do
+ expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, { a: '1', b: '2', c: '3' }).and_return(mock_tracer)
+
+ expect(described_class.create_tracer(service_name, 'opentracing://jaeger?a=1&b=2&c=3')).to be(mock_tracer)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/grpc_interceptor_spec.rb b/spec/lib/gitlab/tracing/grpc_interceptor_spec.rb
new file mode 100644
index 00000000000..7f5aecb7baa
--- /dev/null
+++ b/spec/lib/gitlab/tracing/grpc_interceptor_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::GRPCInterceptor do
+ subject { described_class.instance }
+
+ shared_examples_for "a grpc interceptor method" do
+ let(:custom_error) { Class.new(StandardError) }
+
+ it 'yields' do
+ expect { |b| method.call(kwargs, &b) }.to yield_control
+ end
+
+ it 'propagates exceptions' do
+ expect { method.call(kwargs) { raise custom_error } }.to raise_error(custom_error)
+ end
+ end
+
+ describe '#request_response' do
+ let(:method) { subject.method(:request_response) }
+ let(:kwargs) { { request: {}, call: {}, method: 'grc_method', metadata: {} } }
+
+ it_behaves_like 'a grpc interceptor method'
+ end
+
+ describe '#client_streamer' do
+ let(:method) { subject.method(:client_streamer) }
+ let(:kwargs) { { requests: [], call: {}, method: 'grc_method', metadata: {} } }
+
+ it_behaves_like 'a grpc interceptor method'
+ end
+
+ describe '#server_streamer' do
+ let(:method) { subject.method(:server_streamer) }
+ let(:kwargs) { { request: {}, call: {}, method: 'grc_method', metadata: {} } }
+
+ it_behaves_like 'a grpc interceptor method'
+ end
+
+ describe '#bidi_streamer' do
+ let(:method) { subject.method(:bidi_streamer) }
+ let(:kwargs) { { requests: [], call: {}, method: 'grc_method', metadata: {} } }
+
+ it_behaves_like 'a grpc interceptor method'
+ end
+end
diff --git a/spec/lib/gitlab/tracing/jaeger_factory_spec.rb b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
new file mode 100644
index 00000000000..3d6a007cfd9
--- /dev/null
+++ b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::JaegerFactory do
+ describe '.create_tracer' do
+ let(:service_name) { 'rspec' }
+
+ shared_examples_for 'a jaeger tracer' do
+ it 'responds to active_span methods' do
+ expect(tracer).to respond_to(:active_span)
+ end
+
+ it 'yields control' do
+ expect { |b| tracer.start_active_span('operation_name', &b) }.to yield_control
+ end
+ end
+
+ context 'processes default connections' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, {}) }
+ end
+ end
+
+ context 'handles debug options' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { debug: "1" }) }
+ end
+ end
+
+ context 'handles const sampler' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { sampler: "const", sampler_param: "1" }) }
+ end
+ end
+
+ context 'handles probabilistic sampler' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { sampler: "probabilistic", sampler_param: "0.5" }) }
+ end
+ end
+
+ context 'handles http_endpoint configurations' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { http_endpoint: "http://localhost:1234" }) }
+ end
+ end
+
+ context 'handles udp_endpoint configurations' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { udp_endpoint: "localhost:4321" }) }
+ end
+ end
+
+ context 'ignores invalid parameters' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { invalid: "true" }) }
+ end
+ end
+
+ context 'accepts the debug parameter when strict_parser is set' do
+ it_behaves_like 'a jaeger tracer' do
+ let(:tracer) { described_class.create_tracer(service_name, { debug: "1", strict_parsing: "1" }) }
+ end
+ end
+
+ it 'rejects invalid parameters when strict_parser is set' do
+ expect { described_class.create_tracer(service_name, { invalid: "true", strict_parsing: "1" }) }.to raise_error(StandardError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/rack_middleware_spec.rb b/spec/lib/gitlab/tracing/rack_middleware_spec.rb
new file mode 100644
index 00000000000..13d4d8a89f7
--- /dev/null
+++ b/spec/lib/gitlab/tracing/rack_middleware_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Tracing::RackMiddleware do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#call' do
+ context 'for normal middleware flow' do
+ let(:fake_app) { -> (env) { fake_app_response } }
+ subject { described_class.new(fake_app) }
+ let(:request) { }
+
+ context 'for 200 responses' do
+ let(:fake_app_response) { [200, { 'Content-Type': 'text/plain' }, ['OK']] }
+
+ it 'delegates correctly' do
+ expect(subject.call(Rack::MockRequest.env_for("/"))).to eq(fake_app_response)
+ end
+ end
+
+ context 'for 500 responses' do
+ let(:fake_app_response) { [500, { 'Content-Type': 'text/plain' }, ['Error']] }
+
+ it 'delegates correctly' do
+ expect(subject.call(Rack::MockRequest.env_for("/"))).to eq(fake_app_response)
+ end
+ end
+ end
+
+ context 'when an application is raising an exception' do
+ let(:custom_error) { Class.new(StandardError) }
+ let(:fake_app) { ->(env) { raise custom_error } }
+
+ subject { described_class.new(fake_app) }
+
+ it 'delegates propagates exceptions correctly' do
+ expect { subject.call(Rack::MockRequest.env_for("/")) }.to raise_error(custom_error)
+ end
+ end
+ end
+
+ describe '.build_sanitized_url_from_env' do
+ def env_for_url(url)
+ env = Rack::MockRequest.env_for(input_url)
+ env['action_dispatch.parameter_filter'] = [/token/]
+
+ env
+ end
+
+ where(:input_url, :output_url) do
+ '/gitlab-org/gitlab-ce' | 'http://example.org/gitlab-org/gitlab-ce'
+ '/gitlab-org/gitlab-ce?safe=1' | 'http://example.org/gitlab-org/gitlab-ce?safe=1'
+ '/gitlab-org/gitlab-ce?private_token=secret' | 'http://example.org/gitlab-org/gitlab-ce?private_token=%5BFILTERED%5D'
+ '/gitlab-org/gitlab-ce?mixed=1&private_token=secret' | 'http://example.org/gitlab-org/gitlab-ce?mixed=1&private_token=%5BFILTERED%5D'
+ end
+
+ with_them do
+ it { expect(described_class.build_sanitized_url_from_env(env_for_url(input_url))).to eq(output_url) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb b/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb
new file mode 100644
index 00000000000..5eb5c044f84
--- /dev/null
+++ b/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::Tracing::Rails::ActiveRecordSubscriber do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '.instrument' do
+ it 'is unsubscribable' do
+ subscription = described_class.instrument
+
+ expect(subscription).not_to be_nil
+ expect { ActiveSupport::Notifications.unsubscribe(subscription) }.not_to raise_error
+ end
+ end
+
+ describe '#notify' do
+ subject { described_class.new }
+ let(:start) { Time.now }
+ let(:finish) { Time.now }
+
+ where(:name, :operation_name, :exception, :connection_id, :cached, :cached_response, :sql) do
+ nil | "active_record:sqlquery" | nil | nil | nil | false | nil
+ "" | "active_record:sqlquery" | nil | nil | nil | false | nil
+ "User Load" | "active_record:User Load" | nil | nil | nil | false | nil
+ "Repo Load" | "active_record:Repo Load" | StandardError.new | nil | nil | false | nil
+ nil | "active_record:sqlquery" | nil | 123 | nil | false | nil
+ nil | "active_record:sqlquery" | nil | nil | false | false | nil
+ nil | "active_record:sqlquery" | nil | nil | true | true | nil
+ nil | "active_record:sqlquery" | nil | nil | true | true | "SELECT * FROM users"
+ end
+
+ with_them do
+ def payload
+ {
+ name: name,
+ exception: exception,
+ connection_id: connection_id,
+ cached: cached,
+ sql: sql
+ }
+ end
+
+ def expected_tags
+ {
+ "component" => "ActiveRecord",
+ "span.kind" => "client",
+ "db.type" => "sql",
+ "db.connection_id" => connection_id,
+ "db.cached" => cached_response,
+ "db.statement" => sql
+ }
+ end
+
+ it 'should notify the tracer when the hash contains null values' do
+ expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception)
+
+ subject.notify(start, finish, payload)
+ end
+
+ it 'should notify the tracer when the payload is missing values' do
+ expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception)
+
+ subject.notify(start, finish, payload.compact)
+ end
+
+ it 'should not throw exceptions when with the default tracer' do
+ expect { subject.notify(start, finish, payload) }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb b/spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb
new file mode 100644
index 00000000000..3755860b5ba
--- /dev/null
+++ b/spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::Sidekiq::ClientMiddleware do
+ describe '#call' do
+ let(:worker_class) { 'test_worker_class' }
+ let(:job) do
+ {
+ 'class' => "jobclass",
+ 'queue' => "jobqueue",
+ 'retry' => 0,
+ 'args' => %w{1 2 3}
+ }
+ end
+ let(:queue) { 'test_queue' }
+ let(:redis_pool) { double("redis_pool") }
+ let(:custom_error) { Class.new(StandardError) }
+ let(:span) { OpenTracing.start_span('test', ignore_active_scope: true) }
+
+ subject { described_class.new }
+
+ it 'yields' do
+ expect(subject).to receive(:in_tracing_span).with(
+ operation_name: "sidekiq:jobclass",
+ tags: {
+ "component" => "sidekiq",
+ "span.kind" => "client",
+ "sidekiq.queue" => "jobqueue",
+ "sidekiq.jid" => nil,
+ "sidekiq.retry" => "0",
+ "sidekiq.args" => "1, 2, 3"
+ }
+ ).and_yield(span)
+
+ expect { |b| subject.call(worker_class, job, queue, redis_pool, &b) }.to yield_control
+ end
+
+ it 'propagates exceptions' do
+ expect { subject.call(worker_class, job, queue, redis_pool) { raise custom_error } }.to raise_error(custom_error)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb b/spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb
new file mode 100644
index 00000000000..c3087de785a
--- /dev/null
+++ b/spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::Sidekiq::ServerMiddleware do
+ describe '#call' do
+ let(:worker_class) { 'test_worker_class' }
+ let(:job) do
+ {
+ 'class' => "jobclass",
+ 'queue' => "jobqueue",
+ 'retry' => 0,
+ 'args' => %w{1 2 3}
+ }
+ end
+ let(:queue) { 'test_queue' }
+ let(:custom_error) { Class.new(StandardError) }
+ let(:span) { OpenTracing.start_span('test', ignore_active_scope: true) }
+ subject { described_class.new }
+
+ it 'yields' do
+ expect(subject).to receive(:in_tracing_span).with(
+ hash_including(
+ operation_name: "sidekiq:jobclass",
+ tags: {
+ "component" => "sidekiq",
+ "span.kind" => "server",
+ "sidekiq.queue" => "jobqueue",
+ "sidekiq.jid" => nil,
+ "sidekiq.retry" => "0",
+ "sidekiq.args" => "1, 2, 3"
+ }
+ )
+ ).and_yield(span)
+
+ expect { |b| subject.call(worker_class, job, queue, &b) }.to yield_control
+ end
+
+ it 'propagates exceptions' do
+ expect { subject.call(worker_class, job, queue) { raise custom_error } }.to raise_error(custom_error)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index 7ffcef2baef..e22f898dc4c 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -180,7 +180,7 @@ describe Gitlab::TreeSummary do
with_them do
before do
- create_file('dummy', path: 'other') if num_entries.zero?
+ create_file('dummy', path: 'other') if num_entries.zero?
1.upto(num_entries) { |n| create_file(n, path: path) }
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index f5a4b7e2ebf..8f5029b3565 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -197,4 +197,20 @@ describe Gitlab::Utils do
end
end
end
+
+ describe '.deep_indifferent_access' do
+ let(:hash) do
+ { "variables" => [{ "key" => "VAR1", "value" => "VALUE2" }] }
+ end
+
+ subject { described_class.deep_indifferent_access(hash) }
+
+ it 'allows to access hash keys with symbols' do
+ expect(subject[:variables]).to be_a(Array)
+ end
+
+ it 'allows to access array keys with symbols' do
+ expect(subject[:variables].first[:key]).to eq('VAR1')
+ end
+ end
end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index d63f448883b..6ac3d115bc6 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -8,6 +8,7 @@ describe Gitlab do
expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__)))
end
end
+
describe '.revision' do
let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h -n 1] }
@@ -69,6 +70,82 @@ describe Gitlab do
end
end
+ describe '.final_release?' do
+ subject { described_class.final_release? }
+
+ context 'returns the corrent boolean value' do
+ it 'is false for a pre release' do
+ stub_const('Gitlab::VERSION', '11.0.0-pre')
+
+ expect(subject).to be false
+ end
+
+ it 'is false for a release candidate' do
+ stub_const('Gitlab::VERSION', '11.0.0-rc2')
+
+ expect(subject).to be false
+ end
+
+ it 'is true for a final release' do
+ stub_const('Gitlab::VERSION', '11.0.2')
+
+ expect(subject).to be true
+ end
+ end
+ end
+
+ describe '.minor_release' do
+ subject { described_class.minor_release }
+
+ it 'returns the minor release of the full GitLab version' do
+ stub_const('Gitlab::VERSION', '11.0.1-rc3')
+
+ expect(subject).to eql '11.0'
+ end
+ end
+
+ describe '.previous_release' do
+ subject { described_class.previous_release }
+
+ context 'it should return the previous release' do
+ it 'returns the previous major version when GitLab major version is not final' do
+ stub_const('Gitlab::VERSION', '11.0.1-pre')
+
+ expect(subject).to eql '10'
+ end
+
+ it 'returns the current minor version when the GitLab patch version is RC and > 0' do
+ stub_const('Gitlab::VERSION', '11.2.1-rc3')
+
+ expect(subject).to eql '11.2'
+ end
+
+ it 'returns the previous minor version when the GitLab patch version is RC and 0' do
+ stub_const('Gitlab::VERSION', '11.2.0-rc3')
+
+ expect(subject).to eql '11.1'
+ end
+ end
+ end
+
+ describe '.new_major_release?' do
+ subject { described_class.new_major_release? }
+
+ context 'returns the corrent boolean value' do
+ it 'is true when the minor version is 0 and the patch is a pre release' do
+ stub_const('Gitlab::VERSION', '11.0.1-pre')
+
+ expect(subject).to be true
+ end
+
+ it 'is false when the minor version is above 0' do
+ stub_const('Gitlab::VERSION', '11.2.1-rc3')
+
+ expect(subject).to be false
+ end
+ end
+ end
+
describe '.com?' do
it 'is true when on GitLab.com' do
stub_config_setting(url: 'https://gitlab.com')
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index c2e2db27362..c1eaf0bb0bf 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -25,6 +25,8 @@ describe OmniAuth::Strategies::Jwt do
subject.options[:secret] = secret
subject.options[:algorithm] = algorithm
+ # We use Rack::Request instead of ActionDispatch::Request because
+ # Rack::Test::Methods enables testing of this module.
expect_next_instance_of(Rack::Request) do |rack_request|
expect(rack_request).to receive(:params).and_return('jwt' => payload)
end
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
new file mode 100644
index 00000000000..b36be0fd9c1
--- /dev/null
+++ b/spec/lib/sentry/client_spec.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Sentry::Client do
+ let(:issue_status) { 'unresolved' }
+ let(:limit) { 20 }
+ let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
+ let(:token) { 'test-token' }
+
+ let(:sample_response) do
+ Gitlab::Utils.deep_indifferent_access(
+ JSON.parse(File.read(Rails.root.join('spec/fixtures/sentry/issues_sample_response.json')))
+ )
+ end
+
+ subject(:client) { described_class.new(sentry_url, token) }
+
+ describe '#list_issues' do
+ subject { client.list_issues(issue_status: issue_status, limit: limit) }
+
+ before do
+ stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: sample_response)
+ end
+
+ it 'returns objects of type ErrorTracking::Error' do
+ expect(subject.length).to eq(1)
+ expect(subject[0]).to be_a(Gitlab::ErrorTracking::Error)
+ end
+
+ context 'error object created from sentry response' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:error_object, :sentry_response) do
+ :id | :id
+ :first_seen | :firstSeen
+ :last_seen | :lastSeen
+ :title | :title
+ :type | :type
+ :user_count | :userCount
+ :count | :count
+ :message | [:metadata, :value]
+ :culprit | :culprit
+ :short_id | :shortId
+ :status | :status
+ :frequency | [:stats, '24h']
+ :project_id | [:project, :id]
+ :project_name | [:project, :name]
+ :project_slug | [:project, :slug]
+ end
+
+ with_them do
+ it { expect(subject[0].public_send(error_object)).to eq(sample_response[0].dig(*sentry_response)) }
+ end
+
+ context 'external_url' do
+ it 'is constructed correctly' do
+ expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11')
+ end
+ end
+ end
+
+ context 'redirects' do
+ let(:redirect_to) { 'https://redirected.example.com' }
+ let(:other_url) { 'https://other.example.org' }
+
+ let!(:redirected_req_stub) { stub_sentry_request(other_url) }
+
+ let!(:redirect_req_stub) do
+ stub_sentry_request(
+ sentry_url + '/issues/?limit=20&query=is:unresolved',
+ status: 302,
+ headers: { location: redirect_to }
+ )
+ end
+
+ it 'does not follow redirects' do
+ expect { subject }.to raise_exception(Sentry::Client::Error, 'Sentry response error: 302')
+ expect(redirect_req_stub).to have_been_requested
+ expect(redirected_req_stub).not_to have_been_requested
+ end
+ end
+
+ # Sentry API returns 404 if there are extra slashes in the URL!
+ context 'extra slashes in URL' do
+ let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects//sentry-org/sentry-project/' }
+ let(:client) { described_class.new(sentry_url, token) }
+
+ let!(:valid_req_stub) do
+ stub_sentry_request(
+ 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \
+ 'issues/?limit=20&query=is:unresolved'
+ )
+ end
+
+ it 'removes extra slashes in api url' do
+ expect(Gitlab::HTTP).to receive(:get).with(
+ URI('https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/issues/'),
+ anything
+ ).and_call_original
+
+ client.list_issues(issue_status: issue_status, limit: limit)
+
+ expect(valid_req_stub).to have_been_requested
+ end
+ end
+ end
+
+ private
+
+ def stub_sentry_request(url, body: {}, status: 200, headers: {})
+ WebMock.stub_request(:get, url)
+ .to_return(
+ status: status,
+ headers: { 'Content-Type' => 'application/json' }.merge(headers),
+ body: body.to_json
+ )
+ end
+end
diff --git a/spec/lib/serializers/json_spec.rb b/spec/lib/serializers/json_spec.rb
new file mode 100644
index 00000000000..5d59d66e8b8
--- /dev/null
+++ b/spec/lib/serializers/json_spec.rb
@@ -0,0 +1,102 @@
+require 'fast_spec_helper'
+
+describe Serializers::JSON do
+ describe '.dump' do
+ let(:obj) { { key: "value" } }
+
+ subject { described_class.dump(obj) }
+
+ context 'when MySQL is used' do
+ before do
+ allow(Gitlab::Database).to receive(:adapter_name) { 'mysql2' }
+ end
+
+ it 'encodes as string' do
+ is_expected.to eq('{"key":"value"}')
+ end
+ end
+
+ context 'when PostgreSQL is used' do
+ before do
+ allow(Gitlab::Database).to receive(:adapter_name) { 'postgresql' }
+ end
+
+ it 'returns a hash' do
+ is_expected.to eq(obj)
+ end
+ end
+ end
+
+ describe '.load' do
+ let(:data_string) { '{"key":"value","variables":[{"key":"VAR1","value":"VALUE1"}]}' }
+ let(:data_hash) { JSON.parse(data_string) }
+
+ shared_examples 'having consistent accessor' do
+ it 'allows to access with symbols' do
+ expect(subject[:key]).to eq('value')
+ expect(subject[:variables].first[:key]).to eq('VAR1')
+ end
+
+ it 'allows to access with strings' do
+ expect(subject["key"]).to eq('value')
+ expect(subject["variables"].first["key"]).to eq('VAR1')
+ end
+ end
+
+ context 'when MySQL is used' do
+ before do
+ allow(Gitlab::Database).to receive(:adapter_name) { 'mysql2' }
+ end
+
+ context 'when loading a string' do
+ subject { described_class.load(data_string) }
+
+ it 'decodes a string' do
+ is_expected.to be_a(Hash)
+ end
+
+ it_behaves_like 'having consistent accessor'
+ end
+
+ context 'when loading a different type' do
+ subject { described_class.load({ key: 'hash' }) }
+
+ it 'raises an exception' do
+ expect { subject }.to raise_error(TypeError)
+ end
+ end
+
+ context 'when loading a nil' do
+ subject { described_class.load(nil) }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ context 'when PostgreSQL is used' do
+ before do
+ allow(Gitlab::Database).to receive(:adapter_name) { 'postgresql' }
+ end
+
+ context 'when loading a hash' do
+ subject { described_class.load(data_hash) }
+
+ it 'decodes a string' do
+ is_expected.to be_a(Hash)
+ end
+
+ it_behaves_like 'having consistent accessor'
+ end
+
+ context 'when loading a nil' do
+ subject { described_class.load(nil) }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/mailers/emails/issues_spec.rb b/spec/mailers/emails/issues_spec.rb
new file mode 100644
index 00000000000..09253cf8003
--- /dev/null
+++ b/spec/mailers/emails/issues_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'email_spec'
+
+describe Emails::Issues do
+ include EmailSpec::Matchers
+
+ describe "#import_issues_csv_email" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ subject { Notify.import_issues_csv_email(user.id, project.id, @results) }
+
+ it "shows number of successful issues imported" do
+ @results = { success: 165, error_lines: [], parse_error: false }
+
+ expect(subject).to have_body_text "165 issues imported"
+ end
+
+ it "shows error when file is invalid" do
+ @results = { success: 0, error_lines: [], parse_error: true }
+
+ expect(subject).to have_body_text "Error parsing CSV"
+ end
+
+ it "shows line numbers with errors" do
+ @results = { success: 0, error_lines: [23, 34, 58], parse_error: false }
+
+ expect(subject).to have_body_text "23, 34, 58"
+ end
+ end
+end
diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb
index fe428ea657d..c74fd66ad22 100644
--- a/spec/mailers/emails/pages_domains_spec.rb
+++ b/spec/mailers/emails/pages_domains_spec.rb
@@ -6,7 +6,7 @@ describe Emails::PagesDomains do
include_context 'gitlab email notification'
set(:project) { create(:project) }
- set(:domain) { create(:pages_domain, project: project) }
+ set(:domain) { create(:pages_domain, project: project) }
set(:user) { project.owner }
shared_examples 'a pages domain email' do
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index f2d99872401..1f5b4a8f908 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -887,7 +887,7 @@ describe Notify do
allow(Note).to receive(:find).with(note.id).and_return(note)
end
- shared_examples 'an email for a note on a diff discussion' do |model|
+ shared_examples 'an email for a note on a diff discussion' do |model|
let(:note) { create(model, author: note_author) }
context 'when note is not on text' do
diff --git a/spec/migrations/README.md b/spec/migrations/README.md
index 49760fa62b8..5df44dbc355 100644
--- a/spec/migrations/README.md
+++ b/spec/migrations/README.md
@@ -22,39 +22,33 @@ migrate the database **down** to the previous migration version.
With this approach you can test a migration against a database schema that this
migration has been written for.
-Use `migrate!` helper to run the migration that is under test.
-
The `after` hook will migrate the database **up** and reinstitutes the latest
schema version, so that the process does not affect subsequent specs and
ensures proper isolation.
-## Testing a class that is not an ActiveRecord::Migration
-
-In order to test a class that is not a migration itself, you will need to
-manually provide a required schema version. Please add a `schema` tag to a
-context that you want to switch the database schema within.
-
-Example: `describe SomeClass, :migration, schema: 20170608152748`.
-
## Available helpers
Use `table` helper to create a temporary `ActiveRecord::Base` derived model
for a table.
-Use `migrate!` helper to run the migration that is under test. It will not only
+See `spec/support/helpers/migrations_helpers.rb` for all the available helpers.
+
+## Testing a class that is an ActiveRecord::Migration
+
+In order to test a class that is an `ActiveRecord::Migration`, you will need to
+manually `require` the migration file because it is not autoloaded with Rails.
+
+Use `migrate!` helper to run the migration that is under test. It will not only
run migration, but will also bump the schema version in the `schema_migrations`
table. It is necessary because in the `after` hook we trigger the rest of
the migrations, and we need to know where to start.
-See `spec/support/migrations_helpers.rb` for all the available helpers.
+### Example
-## An example
+This spec tests the [`db/post_migrate/20170526185842_migrate_pipeline_stages.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.6.5/db/post_migrate/20170526185842_migrate_pipeline_stages.rb) migration. You can find the complete spec on [`spec/migrations/migrate_pipeline_stages_spec.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.6.5/spec/migrations/migrate_pipeline_stages_spec.rb).
```ruby
require 'spec_helper'
-
-# Load a migration class.
-
require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb')
describe MigratePipelineStages, :migration do
@@ -86,6 +80,56 @@ describe MigratePipelineStages, :migration do
end
```
+## Testing a class that is not an ActiveRecord::Migration
+
+To test a class that is not an `ActiveRecord::Migration` (a background migration),
+you will need to manually provide a required schema version. Please add a
+schema tag to a context that you want to switch the database schema within.
+
+Example: `describe SomeClass, :migration, schema: 20170608152748`.
+
+### Example
+
+This spec tests the [`lib/gitlab/background_migration/archive_legacy_traces.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.6.5/lib/gitlab/background_migration/archive_legacy_traces.rb)
+background migration. You can find the complete spec on
+[`spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.6.5/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb)
+
+```ruby
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 20180529152628 do
+ include TraceHelpers
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:builds) { table(:ci_builds) }
+ let(:job_artifacts) { table(:ci_job_artifacts) }
+
+ before do
+ namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
+ projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123)
+ @build = builds.create!(id: 1, project_id: 123, status: 'success', type: 'Ci::Build')
+ end
+
+ context 'when trace file exsits at the right place' do
+ before do
+ create_legacy_trace(@build, 'trace in file')
+ end
+
+ it 'correctly archive legacy traces' do
+ expect(job_artifacts.count).to eq(0)
+ expect(File.exist?(legacy_trace_path(@build))).to be_truthy
+
+ described_class.new.perform(1, 1)
+
+ expect(job_artifacts.count).to eq(1)
+ expect(File.exist?(legacy_trace_path(@build))).to be_falsy
+ expect(File.read(archived_trace_path(job_artifacts.first))).to eq('trace in file')
+ end
+ end
+end
+```
+
## Best practices
1. Note that this type of tests do not run within the transaction, we use
diff --git a/spec/migrations/add_foreign_keys_to_todos_spec.rb b/spec/migrations/add_foreign_keys_to_todos_spec.rb
index bf2fa5c0f56..efd87173b9c 100644
--- a/spec/migrations/add_foreign_keys_to_todos_spec.rb
+++ b/spec/migrations/add_foreign_keys_to_todos_spec.rb
@@ -3,9 +3,11 @@ require Rails.root.join('db', 'migrate', '20180201110056_add_foreign_keys_to_tod
describe AddForeignKeysToTodos, :migration do
let(:todos) { table(:todos) }
+ let(:users) { table(:users) }
+ let(:projects) { table(:projects) }
- let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:project) { projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1) }
+ let(:user) { users.create!(email: 'email@email.com', name: 'foo', username: 'foo', projects_limit: 0) }
context 'add foreign key on user_id' do
let!(:todo_with_user) { create_todo(user_id: user.id) }
diff --git a/spec/migrations/cleanup_legacy_artifact_migration_spec.rb b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
new file mode 100644
index 00000000000..dc269d32e5a
--- /dev/null
+++ b/spec/migrations/cleanup_legacy_artifact_migration_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190104182041_cleanup_legacy_artifact_migration.rb')
+
+describe CleanupLegacyArtifactMigration, :migration, :sidekiq, :redis do
+ let(:migration) { spy('migration') }
+
+ context 'when still legacy artifacts exist' do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:jobs) { table(:ci_builds) }
+ let(:job_artifacts) { table(:ci_job_artifacts) }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id) }
+ let(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a') }
+ let(:archive_file_type) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::ARCHIVE_FILE_TYPE }
+ let(:metadata_file_type) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::METADATA_FILE_TYPE }
+ let(:local_store) { ::ObjectStorage::Store::LOCAL }
+ let(:remote_store) { ::ObjectStorage::Store::REMOTE }
+ let(:legacy_location) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::LEGACY_PATH_FILE_LOCATION }
+
+ before do
+ jobs.create!(id: 1, commit_id: pipeline.id, project_id: project.id, status: :success, artifacts_file: 'archive.zip')
+ jobs.create!(id: 2, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_metadata: 'metadata.gz')
+ jobs.create!(id: 3, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_file: 'archive.zip', artifacts_metadata: 'metadata.gz')
+ jobs.create!(id: 4, commit_id: pipeline.id, project_id: project.id, status: :running)
+ jobs.create!(id: 5, commit_id: pipeline.id, project_id: project.id, status: :success, artifacts_file: 'archive.zip', artifacts_file_store: remote_store, artifacts_metadata: 'metadata.gz')
+ jobs.create!(id: 6, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_file: 'archive.zip', artifacts_metadata: 'metadata.gz')
+ end
+
+ it 'steals sidekiq jobs from MigrateLegacyArtifacts background migration' do
+ expect(Gitlab::BackgroundMigration).to receive(:steal).with('MigrateLegacyArtifacts')
+
+ migrate!
+ end
+
+ it 'migrates legacy artifacts to ci_job_artifacts table' do
+ migrate!
+
+ expect(job_artifacts.order(:job_id, :file_type).pluck('project_id, job_id, file_type, file_store, size, expire_at, file, file_sha256, file_location'))
+ .to eq([[project.id, 1, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 3, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 3, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location],
+ [project.id, 5, archive_file_type, remote_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 5, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location],
+ [project.id, 6, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 6, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location]])
+ end
+ end
+end
diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
index b5980cb9ddb..651341906c2 100644
--- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespaceless_pending_delete_projects.rb')
describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222043024 do
+ let(:projects) { table(:projects) }
+
before do
# Stub after_save callbacks that will fail when Project has no namespace
allow_any_instance_of(Project).to receive(:ensure_storage_path_exists).and_return(nil)
@@ -10,9 +12,9 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
describe '#up' do
it 'only cleans up pending delete projects' do
- create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- project = build(:project, pending_delete: true, namespace_id: nil) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true)
+ project = Project.new(pending_delete: true, namespace_id: nil)
project.save(validate: false)
expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
@@ -21,8 +23,8 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
end
it 'does nothing when no pending delete projects without namespace found' do
- create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true)
expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
diff --git a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb
index 8c55daf0d37..e2ce69a7bb1 100644
--- a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb
+++ b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb
@@ -90,12 +90,19 @@ describe DeleteInconsistentInternalIdRecords, :migration do
context 'for ci_pipelines' do
let(:scope) { :ci_pipeline }
+
+ let(:create_models) do
+ create_list(:ci_empty_pipeline, 3, project: project1)
+ create_list(:ci_empty_pipeline, 3, project: project2)
+ create_list(:ci_empty_pipeline, 3, project: project3)
+ end
+
it_behaves_like 'deleting inconsistent internal_id records'
end
context 'for milestones (by group)' do
# milestones (by group) is a little different than most of the other models
- let(:groups) { table(:namespaces) }
+ let(:groups) { table(:namespaces) }
let(:group1) { groups.create(name: 'Group 1', type: 'Group', path: 'group_1') }
let(:group2) { groups.create(name: 'Group 2', type: 'Group', path: 'group_2') }
let(:group3) { groups.create(name: 'Group 2', type: 'Group', path: 'group_3') }
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
index baf16c2ce53..80ae209e9d1 100644
--- a/spec/migrations/rename_more_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_more_reserved_project_names_spec.rb
@@ -37,9 +37,8 @@ describe RenameMoreReservedProjectNames, :delete do
.to receive(:execute)
.and_raise(Projects::AfterRenameService::RenameFailedError)
- allow(Projects::AfterRenameService)
- .to receive(:new)
- .with(project)
+ expect(migration)
+ .to receive(:after_rename_service)
.and_return(service)
end
diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb
index 7818aa0d560..93e5c032287 100644
--- a/spec/migrations/rename_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_reserved_project_names_spec.rb
@@ -41,9 +41,8 @@ describe RenameReservedProjectNames, :migration, schema: :latest do
.to receive(:execute)
.and_raise(Projects::AfterRenameService::RenameFailedError)
- allow(Projects::AfterRenameService)
- .to receive(:new)
- .with(project)
+ expect(migration)
+ .to receive(:after_rename_service)
.and_return(service)
end
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index ec2e7d672f0..cc76a2019ec 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -36,6 +36,13 @@ describe Appearance do
expect(subject.send("#{logo_type}_path")).to be_nil
end
+ it 'returns the path when the upload has been orphaned' do
+ appearance.send(logo_type).upload.destroy
+ appearance.reload
+
+ expect(appearance.send("#{logo_type}_path")).to eq(expected_path)
+ end
+
it 'returns a local path using the system route' do
expect(appearance.send("#{logo_type}_path")).to eq(expected_path)
end
diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
index 01c555a7a90..16bf947b493 100644
--- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
+++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
@@ -4,7 +4,9 @@ describe BlobViewer::GitlabCiYml do
include FakeBlobHelpers
include RepoHelpers
- let(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+
let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
let(:blob) { fake_blob(path: '.gitlab-ci.yml', data: data) }
let(:sha) { sample_commit.id }
@@ -14,12 +16,12 @@ describe BlobViewer::GitlabCiYml do
it 'calls prepare! on the viewer' do
expect(subject).to receive(:prepare!)
- subject.validation_message(project, sha)
+ subject.validation_message(project: project, sha: sha, user: user)
end
context 'when the configuration is valid' do
it 'returns nil' do
- expect(subject.validation_message(project, sha)).to be_nil
+ expect(subject.validation_message(project: project, sha: sha, user: user)).to be_nil
end
end
@@ -27,7 +29,7 @@ describe BlobViewer::GitlabCiYml do
let(:data) { 'oof' }
it 'returns the error message' do
- expect(subject.validation_message(project, sha)).to eq('Invalid configuration format')
+ expect(subject.validation_message(project: project, sha: sha, user: user)).to eq('Invalid configuration format')
end
end
end
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
index 519968b9e48..016a5899eef 100644
--- a/spec/models/ci/build_metadata_spec.rb
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -13,12 +13,12 @@ describe Ci::BuildMetadata do
end
let(:build) { create(:ci_build, pipeline: pipeline) }
- let(:build_metadata) { build.metadata }
+ let(:metadata) { build.metadata }
it_behaves_like 'having unique enum values'
describe '#update_timeout_state' do
- subject { build_metadata }
+ subject { metadata }
context 'when runner is not assigned to the job' do
it "doesn't change timeout value" do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 7baf4d93804..8ba33ff9c04 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1457,8 +1457,24 @@ describe Ci::Build do
context 'with retries max config option' do
subject { create(:ci_build, options: { retry: { max: 1 } }) }
- it 'returns the number of configured max retries' do
- expect(subject.retries_max).to eq 1
+ context 'when build_metadata_config is set' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: true)
+ end
+
+ it 'returns the number of configured max retries' do
+ expect(subject.retries_max).to eq 1
+ end
+ end
+
+ context 'when build_metadata_config is not set' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: false)
+ end
+
+ it 'returns the number of configured max retries' do
+ expect(subject.retries_max).to eq 1
+ end
end
end
@@ -1679,14 +1695,49 @@ describe Ci::Build do
let(:options) do
{
image: "ruby:2.1",
- services: [
- "postgres"
- ]
+ services: ["postgres"],
+ script: ["ls -a"]
}
end
it 'contains options' do
- expect(build.options).to eq(options)
+ expect(build.options).to eq(options.stringify_keys)
+ end
+
+ it 'allows to access with keys' do
+ expect(build.options[:image]).to eq('ruby:2.1')
+ end
+
+ it 'allows to access with strings' do
+ expect(build.options['image']).to eq('ruby:2.1')
+ end
+
+ context 'when ci_build_metadata_config is set' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: true)
+ end
+
+ it 'persist data in build metadata' do
+ expect(build.metadata.read_attribute(:config_options)).to eq(options.stringify_keys)
+ end
+
+ it 'does not persist data in build' do
+ expect(build.read_attribute(:options)).to be_nil
+ end
+ end
+
+ context 'when ci_build_metadata_config is disabled' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: false)
+ end
+
+ it 'persist data in build' do
+ expect(build.read_attribute(:options)).to eq(options.symbolize_keys)
+ end
+
+ it 'does not persist data in build metadata' do
+ expect(build.metadata.read_attribute(:config_options)).to be_nil
+ end
end
end
@@ -2030,56 +2081,6 @@ describe Ci::Build do
end
end
- describe '#when' do
- subject { build.when }
-
- context 'when `when` is undefined' do
- before do
- build.when = nil
- end
-
- context 'use from gitlab-ci.yml' do
- let(:project) { create(:project, :repository) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
-
- before do
- stub_ci_pipeline_yaml_file(config)
- end
-
- context 'when config is not found' do
- let(:config) { nil }
-
- it { is_expected.to eq('on_success') }
- end
-
- context 'when config does not have a questioned job' do
- let(:config) do
- YAML.dump({
- test_other: {
- script: 'Hello World'
- }
- })
- end
-
- it { is_expected.to eq('on_success') }
- end
-
- context 'when config has `when`' do
- let(:config) do
- YAML.dump({
- test: {
- script: 'Hello World',
- when: 'always'
- }
- })
- end
-
- it { is_expected.to eq('always') }
- end
- end
- end
- end
-
describe '#variables' do
let(:container_registry_enabled) { false }
@@ -2132,6 +2133,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true },
+ { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true },
{ key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true },
{ key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
{ key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true },
@@ -2148,62 +2150,6 @@ describe Ci::Build do
it { is_expected.to include(*predefined_variables) }
- context 'when yaml variables are undefined' do
- let(:pipeline) do
- create(:ci_pipeline, project: project,
- sha: project.commit.id,
- ref: project.default_branch)
- end
-
- before do
- build.yaml_variables = nil
- end
-
- context 'use from gitlab-ci.yml' do
- before do
- stub_ci_pipeline_yaml_file(config)
- end
-
- context 'when config is not found' do
- let(:config) { nil }
-
- it { is_expected.to include(*predefined_variables) }
- end
-
- context 'when config does not have a questioned job' do
- let(:config) do
- YAML.dump({
- test_other: {
- script: 'Hello World'
- }
- })
- end
-
- it { is_expected.to include(*predefined_variables) }
- end
-
- context 'when config has variables' do
- let(:config) do
- YAML.dump({
- test: {
- script: 'Hello World',
- variables: {
- KEY: 'value'
- }
- }
- })
- end
-
- let(:variables) do
- [{ key: 'KEY', value: 'value', public: true }]
- end
-
- it { is_expected.to include(*predefined_variables) }
- it { is_expected.to include(*variables) }
- end
- end
- end
-
describe 'variables ordering' do
context 'when variables hierarchy is stubbed' do
let(:build_pre_var) { { key: 'build', value: 'value', public: true } }
@@ -2534,7 +2480,7 @@ describe Ci::Build do
context 'when container registry is enabled' do
let(:container_registry_enabled) { true }
let(:ci_registry) do
- { key: 'CI_REGISTRY', value: 'registry.example.com', public: true }
+ { key: 'CI_REGISTRY', value: 'registry.example.com', public: true }
end
let(:ci_registry_image) do
{ key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true }
@@ -2792,29 +2738,53 @@ describe Ci::Build do
end
describe '#yaml_variables' do
- before do
- build.update_attribute(:yaml_variables, variables)
+ let(:build) { create(:ci_build, pipeline: pipeline, yaml_variables: variables) }
+
+ let(:variables) do
+ [
+ { 'key' => :VARIABLE, 'value' => 'my value' },
+ { 'key' => 'VARIABLE2', 'value' => 'my value 2' }
+ ]
end
- context 'when serialized valu is a symbolized hash' do
- let(:variables) do
- [{ key: :VARIABLE, value: 'my value 1' }]
+ shared_examples 'having consistent representation' do
+ it 'allows to access using symbols' do
+ expect(build.reload.yaml_variables.first[:key]).to eq('VARIABLE')
+ expect(build.reload.yaml_variables.first[:value]).to eq('my value')
+ expect(build.reload.yaml_variables.second[:key]).to eq('VARIABLE2')
+ expect(build.reload.yaml_variables.second[:value]).to eq('my value 2')
end
+ end
+
+ context 'when ci_build_metadata_config is set' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: true)
+ end
+
+ it_behaves_like 'having consistent representation'
- it 'keeps symbolizes keys and stringifies variables names' do
- expect(build.yaml_variables)
- .to eq [{ key: 'VARIABLE', value: 'my value 1' }]
+ it 'persist data in build metadata' do
+ expect(build.metadata.read_attribute(:config_variables)).not_to be_nil
+ end
+
+ it 'does not persist data in build' do
+ expect(build.read_attribute(:yaml_variables)).to be_nil
end
end
- context 'when serialized value is a hash with string keys' do
- let(:variables) do
- [{ 'key' => :VARIABLE, 'value' => 'my value 2' }]
+ context 'when ci_build_metadata_config is disabled' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: false)
+ end
+
+ it_behaves_like 'having consistent representation'
+
+ it 'persist data in build' do
+ expect(build.read_attribute(:yaml_variables)).not_to be_nil
end
- it 'symblizes variables hash' do
- expect(build.yaml_variables)
- .to eq [{ key: 'VARIABLE', value: 'my value 2' }]
+ it 'does not persist data in build metadata' do
+ expect(build.metadata.read_attribute(:config_variables)).to be_nil
end
end
end
@@ -2986,7 +2956,7 @@ describe Ci::Build do
end
context 'when build is configured to be retried' do
- subject { create(:ci_build, :running, options: { retry: { max: 3 } }, project: project, user: user) }
+ subject { create(:ci_build, :running, options: { script: ["ls -al"], retry: 3 }, project: project, user: user) }
it 'retries build and assigns the same user to it' do
expect(described_class).to receive(:retry)
@@ -3058,6 +3028,24 @@ describe Ci::Build do
subject.drop!
end
end
+
+ context 'when associated deployment failed to update its status' do
+ let(:build) { create(:ci_build, :running, pipeline: pipeline) }
+ let!(:deployment) { create(:deployment, deployable: build) }
+
+ before do
+ allow_any_instance_of(Deployment)
+ .to receive(:drop!).and_raise('Unexpected error')
+ end
+
+ it 'can drop the build' do
+ expect(Gitlab::Sentry).to receive(:track_exception)
+
+ expect { build.drop! }.not_to raise_error
+
+ expect(build).to be_failed
+ end
+ end
end
describe '.matches_tag_ids' do
@@ -3475,6 +3463,23 @@ describe Ci::Build do
end
end
+ describe 'degenerate!' do
+ let(:build) { create(:ci_build) }
+
+ subject { build.degenerate! }
+
+ before do
+ build.ensure_metadata
+ end
+
+ it 'drops metadata' do
+ subject
+
+ expect(build.reload).to be_degenerated
+ expect(build.metadata).to be_nil
+ end
+ end
+
describe '#archived?' do
context 'when build is degenerated' do
subject { create(:ci_build, :degenerated) }
@@ -3502,4 +3507,97 @@ describe Ci::Build do
end
end
end
+
+ describe '#read_metadata_attribute' do
+ let(:build) { create(:ci_build, :degenerated) }
+ let(:build_options) { { "key" => "build" } }
+ let(:metadata_options) { { "key" => "metadata" } }
+ let(:default_options) { { "key" => "default" } }
+
+ subject { build.send(:read_metadata_attribute, :options, :config_options, default_options) }
+
+ context 'when build and metadata options is set' do
+ before do
+ build.write_attribute(:options, build_options)
+ build.ensure_metadata.write_attribute(:config_options, metadata_options)
+ end
+
+ it 'prefers build options' do
+ is_expected.to eq(build_options)
+ end
+ end
+
+ context 'when only metadata options is set' do
+ before do
+ build.write_attribute(:options, nil)
+ build.ensure_metadata.write_attribute(:config_options, metadata_options)
+ end
+
+ it 'returns metadata options' do
+ is_expected.to eq(metadata_options)
+ end
+ end
+
+ context 'when none is set' do
+ it 'returns default value' do
+ is_expected.to eq(default_options)
+ end
+ end
+ end
+
+ describe '#write_metadata_attribute' do
+ let(:build) { create(:ci_build, :degenerated) }
+ let(:options) { { "key" => "new options" } }
+ let(:existing_options) { { "key" => "existing options" } }
+
+ subject { build.send(:write_metadata_attribute, :options, :config_options, options) }
+
+ context 'when ci_build_metadata_config is set' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: true)
+ end
+
+ context 'when data in build is already set' do
+ before do
+ build.write_attribute(:options, existing_options)
+ end
+
+ it 'does set metadata options' do
+ subject
+
+ expect(build.metadata.read_attribute(:config_options)).to eq(options)
+ end
+
+ it 'does reset build options' do
+ subject
+
+ expect(build.read_attribute(:options)).to be_nil
+ end
+ end
+ end
+
+ context 'when ci_build_metadata_config is disabled' do
+ before do
+ stub_feature_flags(ci_build_metadata_config: false)
+ end
+
+ context 'when data in build metadata is already set' do
+ before do
+ build.ensure_metadata.write_attribute(:config_options, existing_options)
+ end
+
+ it 'does set metadata options' do
+ subject
+
+ expect(build.read_attribute(:options)).to eq(options)
+ end
+
+ it 'does reset build options' do
+ subject
+
+ expect(build.metadata.read_attribute(:config_options)).to be_nil
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 17f33785fda..72a0df96a80 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -39,6 +39,29 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '.processables' do
+ before do
+ create(:ci_build, name: 'build', pipeline: pipeline)
+ create(:ci_bridge, name: 'bridge', pipeline: pipeline)
+ create(:commit_status, name: 'commit status', pipeline: pipeline)
+ create(:generic_commit_status, name: 'generic status', pipeline: pipeline)
+ end
+
+ it 'has an association with processable CI/CD entities' do
+ pipeline.processables.pluck('name').yield_self do |processables|
+ expect(processables).to match_array %w[build bridge]
+ end
+ end
+
+ it 'makes it possible to append a new processable' do
+ pipeline.processables << build(:ci_bridge)
+
+ pipeline.save!
+
+ expect(pipeline.processables.reload.count).to eq 3
+ end
+ end
+
describe '.sort_by_merge_request_pipelines' do
subject { described_class.sort_by_merge_request_pipelines }
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index ad79f8d4ce0..eb2daed7f32 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -817,4 +817,13 @@ describe Ci::Runner do
expect(runners).to eq([runner2, runner1])
end
end
+
+ describe '#uncached_contacted_at' do
+ let(:contacted_at_stored) { 1.hour.ago.change(usec: 0) }
+ let(:runner) { create(:ci_runner, contacted_at: contacted_at_stored) }
+
+ subject { runner.uncached_contacted_at }
+
+ it { is_expected.to eq(contacted_at_stored) }
+ end
end
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index e825f3e2392..8e14abe098d 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -29,7 +29,7 @@ describe Clusters::Applications::CertManager do
expect(subject.name).to eq('certmanager')
expect(subject.chart).to eq('stable/cert-manager')
expect(subject.version).to eq('v0.5.2')
- expect(subject).not_to be_rbac
+ expect(subject).to be_rbac
expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file))
expect(subject.postinstall).to eq(['/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml'])
end
@@ -45,12 +45,12 @@ describe Clusters::Applications::CertManager do
end
end
- context 'on a rbac enabled cluster' do
+ context 'on a non rbac enabled cluster' do
before do
- cert_manager.cluster.platform_kubernetes.rbac!
+ cert_manager.cluster.platform_kubernetes.abac!
end
- it { is_expected.to be_rbac }
+ it { is_expected.not_to be_rbac }
end
context 'application failed to install previously' do
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index 2c37cd20ecc..64f6d9c8bb4 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -49,16 +49,16 @@ describe Clusters::Applications::Helm do
end
describe 'rbac' do
- context 'non rbac cluster' do
- it { expect(subject).not_to be_rbac }
+ context 'rbac cluster' do
+ it { expect(subject).to be_rbac }
end
- context 'rbac cluster' do
+ context 'non rbac cluster' do
before do
- helm.cluster.platform_kubernetes.rbac!
+ helm.cluster.platform_kubernetes.abac!
end
- it { expect(subject).to be_rbac }
+ it { expect(subject).not_to be_rbac }
end
end
end
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index cd28f1fe9c6..52c347229c6 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -35,7 +35,7 @@ describe Clusters::Applications::Ingress do
let(:application) { create(:clusters_applications_ingress, :scheduled, version: '0.22.0') }
it 'updates the application version' do
- expect(application.reload.version).to eq('0.23.0')
+ expect(application.reload.version).to eq('1.1.2')
end
end
end
@@ -90,24 +90,24 @@ describe Clusters::Applications::Ingress do
it 'should be initialized with ingress arguments' do
expect(subject.name).to eq('ingress')
expect(subject.chart).to eq('stable/nginx-ingress')
- expect(subject.version).to eq('0.23.0')
- expect(subject).not_to be_rbac
+ expect(subject.version).to eq('1.1.2')
+ expect(subject).to be_rbac
expect(subject.files).to eq(ingress.files)
end
- context 'on a rbac enabled cluster' do
+ context 'on a non rbac enabled cluster' do
before do
- ingress.cluster.platform_kubernetes.rbac!
+ ingress.cluster.platform_kubernetes.abac!
end
- it { is_expected.to be_rbac }
+ it { is_expected.not_to be_rbac }
end
context 'application failed to install previously' do
let(:ingress) { create(:clusters_applications_ingress, :errored, version: 'nginx') }
it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.23.0')
+ expect(subject.version).to eq('1.1.2')
end
end
end
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index a40edbf267b..391e5425384 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -52,17 +52,17 @@ describe Clusters::Applications::Jupyter do
expect(subject.name).to eq('jupyter')
expect(subject.chart).to eq('jupyter/jupyterhub')
expect(subject.version).to eq('v0.6')
- expect(subject).not_to be_rbac
+ expect(subject).to be_rbac
expect(subject.repository).to eq('https://jupyterhub.github.io/helm-chart/')
expect(subject.files).to eq(jupyter.files)
end
- context 'on a rbac enabled cluster' do
+ context 'on a non rbac enabled cluster' do
before do
- jupyter.cluster.platform_kubernetes.rbac!
+ jupyter.cluster.platform_kubernetes.abac!
end
- it { is_expected.to be_rbac }
+ it { is_expected.not_to be_rbac }
end
context 'application failed to install previously' do
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 809880f5969..35818be8deb 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -15,6 +15,13 @@ describe Clusters::Applications::Knative do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
end
+ describe 'when rbac is not enabled' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled) }
+ let(:knative_no_rbac) { create(:clusters_applications_knative, cluster: cluster) }
+
+ it { expect(knative_no_rbac).to be_not_installable }
+ end
+
describe '.installed' do
subject { described_class.installed }
@@ -108,6 +115,23 @@ describe Clusters::Applications::Knative do
expect(subject.version).to eq('0.2.2')
expect(subject.files).to eq(knative.files)
end
+
+ it 'should not install metrics for prometheus' do
+ expect(subject.postinstall).to be_nil
+ end
+
+ context 'with prometheus installed' do
+ let(:prometheus) { create(:clusters_applications_prometheus, :installed) }
+ let(:knative) { create(:clusters_applications_knative, cluster: prometheus.cluster) }
+
+ subject { knative.install_command }
+
+ it 'should install metrics' do
+ expect(subject.postinstall).not_to be_nil
+ expect(subject.postinstall.length).to be(1)
+ expect(subject.postinstall[0]).to eql("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}")
+ end
+ end
end
describe '#files' do
@@ -125,6 +149,35 @@ describe Clusters::Applications::Knative 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 'should be 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 }
@@ -142,6 +195,7 @@ describe Clusters::Applications::Knative do
before do
stub_kubeclient_discover(service.api_url)
stub_kubeclient_knative_services
+ stub_kubeclient_service_pods
end
it 'should have an unintialized cache' do
@@ -150,7 +204,11 @@ describe Clusters::Applications::Knative do
context 'when using synchronous reactive cache' do
before do
- stub_reactive_cache(knative, services: kube_response(kube_knative_services_body))
+ 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
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 893ed3e3f64..e50ba67c493 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -90,6 +90,24 @@ describe Clusters::Applications::Prometheus do
expect(application).not_to be_ready
end
+
+ it 'returns true when updating' do
+ application = build(:clusters_applications_prometheus, :updating, cluster: cluster)
+
+ expect(application).to be_ready
+ end
+
+ it 'returns true when updated' do
+ application = build(:clusters_applications_prometheus, :updated, cluster: cluster)
+
+ expect(application).to be_ready
+ end
+
+ it 'returns true when errored' do
+ application = build(:clusters_applications_prometheus, :update_errored, cluster: cluster)
+
+ expect(application).to be_ready
+ end
end
describe '#prometheus_client' do
@@ -161,16 +179,16 @@ describe Clusters::Applications::Prometheus do
expect(subject.name).to eq('prometheus')
expect(subject.chart).to eq('stable/prometheus')
expect(subject.version).to eq('6.7.3')
- expect(subject).not_to be_rbac
+ expect(subject).to be_rbac
expect(subject.files).to eq(prometheus.files)
end
- context 'on a rbac enabled cluster' do
+ context 'on a non rbac enabled cluster' do
before do
- prometheus.cluster.platform_kubernetes.rbac!
+ prometheus.cluster.platform_kubernetes.abac!
end
- it { is_expected.to be_rbac }
+ it { is_expected.not_to be_rbac }
end
context 'application failed to install previously' do
@@ -180,6 +198,61 @@ describe Clusters::Applications::Prometheus do
expect(subject.version).to eq('6.7.3')
end
end
+
+ it 'should not install knative metrics' do
+ expect(subject.postinstall).to be_nil
+ end
+
+ context 'with knative installed' do
+ let(:knative) { create(:clusters_applications_knative, :installed ) }
+ let(:prometheus) { create(:clusters_applications_prometheus, cluster: knative.cluster) }
+
+ subject { prometheus.install_command }
+
+ it 'should install knative metrics' do
+ expect(subject.postinstall).to include("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}")
+ end
+ end
+ end
+
+ describe '#upgrade_command' do
+ let(:prometheus) { build(:clusters_applications_prometheus) }
+ let(:values) { prometheus.values }
+
+ it 'returns an instance of Gitlab::Kubernetes::Helm::GetCommand' do
+ expect(prometheus.upgrade_command(values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::UpgradeCommand)
+ end
+
+ it 'should be initialized with 3 arguments' do
+ command = prometheus.upgrade_command(values)
+
+ expect(command.name).to eq('prometheus')
+ expect(command.chart).to eq('stable/prometheus')
+ expect(command.version).to eq('6.7.3')
+ expect(command.files).to eq(prometheus.files)
+ end
+ end
+
+ describe '#update_in_progress?' do
+ context 'when app is updating' do
+ it 'returns true' do
+ cluster = create(:cluster)
+ prometheus_app = build(:clusters_applications_prometheus, :updating, cluster: cluster)
+
+ expect(prometheus_app.update_in_progress?).to be true
+ end
+ end
+ end
+
+ describe '#update_errored?' do
+ context 'when app errored' do
+ it 'returns true' do
+ cluster = create(:cluster)
+ prometheus_app = build(:clusters_applications_prometheus, :update_errored, cluster: cluster)
+
+ expect(prometheus_app.update_errored?).to be true
+ end
+ end
end
describe '#files' do
@@ -196,4 +269,43 @@ describe Clusters::Applications::Prometheus do
expect(values).to include('serverFiles')
end
end
+
+ describe '#files_with_replaced_values' do
+ let(:application) { build(:clusters_applications_prometheus) }
+ let(:files) { application.files }
+
+ subject { application.files_with_replaced_values({ hello: :world }) }
+
+ it 'does not modify #files' do
+ expect(subject[:'values.yaml']).not_to eq(files)
+ expect(files[:'values.yaml']).to eq(application.values)
+ end
+
+ it 'returns values.yaml with replaced values' do
+ expect(subject[:'values.yaml']).to eq({ hello: :world })
+ end
+
+ it 'should include cert files' do
+ expect(subject[:'ca.pem']).to be_present
+ expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert)
+
+ expect(subject[:'cert.pem']).to be_present
+ expect(subject[:'key.pem']).to be_present
+
+ cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem'])
+ expect(cert.not_after).to be < 60.minutes.from_now
+ end
+
+ context 'when the helm application does not have a ca_cert' do
+ before do
+ application.cluster.application_helm.ca_cert = nil
+ end
+
+ it 'should not include cert files' do
+ expect(subject[:'ca.pem']).not_to be_present
+ expect(subject[:'cert.pem']).not_to be_present
+ expect(subject[:'key.pem']).not_to be_present
+ end
+ end
+ end
end
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index cae23daac8c..8ad41e997c2 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do
let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') }
it 'updates the application version' do
- expect(application.reload.version).to eq('0.1.43')
+ expect(application.reload.version).to eq('0.1.45')
end
end
end
@@ -46,25 +46,25 @@ describe Clusters::Applications::Runner do
it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
- expect(subject.version).to eq('0.1.43')
- expect(subject).not_to be_rbac
+ expect(subject.version).to eq('0.1.45')
+ expect(subject).to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
end
- context 'on a rbac enabled cluster' do
+ context 'on a non rbac enabled cluster' do
before do
- gitlab_runner.cluster.platform_kubernetes.rbac!
+ gitlab_runner.cluster.platform_kubernetes.abac!
end
- it { is_expected.to be_rbac }
+ it { is_expected.not_to be_rbac }
end
context 'application failed to install previously' do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.1.43')
+ expect(subject.version).to eq('0.1.45')
end
end
end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 840f74c9890..0161db740ee 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -29,6 +29,7 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:available?).to(:application_helm).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
+ it { is_expected.to delegate_method(:available?).to(:application_knative).with_prefix }
it { is_expected.to respond_to :project }
@@ -112,7 +113,7 @@ describe Clusters::Cluster do
end
end
- describe 'validation' do
+ describe 'validations' do
subject { cluster.valid? }
context 'when validates name' do
@@ -251,6 +252,31 @@ describe Clusters::Cluster do
end
end
end
+
+ describe 'domain validation' do
+ let(:cluster) { build(:cluster) }
+
+ subject { cluster }
+
+ context 'when cluster has domain' do
+ let(:cluster) { build(:cluster, :with_domain) }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when cluster has an invalid domain' do
+ let(:cluster) { build(:cluster, domain: 'not-valid-domain') }
+
+ it 'should add an error on domain' do
+ expect(subject).not_to be_valid
+ expect(subject.errors[:domain].first).to eq('is not a fully qualified domain name')
+ end
+ end
+
+ context 'when cluster does not have a domain' do
+ it { is_expected.to be_valid }
+ end
+ end
end
describe '.ancestor_clusters_for_clusterable' do
diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb
index 56c98d016c9..235e2ee4e69 100644
--- a/spec/models/clusters/kubernetes_namespace_spec.rb
+++ b/spec/models/clusters/kubernetes_namespace_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
let(:cluster_project) { create(:cluster_project) }
let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') }
- subject { kubernetes_namespace }
+ subject { kubernetes_namespace }
context 'when cluster is using the namespace' do
before do
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index e6b076adc76..6c8a223092e 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -154,19 +154,11 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
describe '#rbac?' do
- subject { kubernetes.rbac? }
-
let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }
- context 'when authorization type is rbac' do
- let(:kubernetes) { build(:cluster_platform_kubernetes, :rbac_enabled, :configured) }
-
- it { is_expected.to be_truthy }
- end
+ subject { kubernetes.rbac? }
- context 'when authorization type is nil' do
- it { is_expected.to be_falsey }
- end
+ it { is_expected.to be_truthy }
end
describe '#actual_namespace' do
diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb
index d134608b538..5012e6f15c6 100644
--- a/spec/models/clusters/providers/gcp_spec.rb
+++ b/spec/models/clusters/providers/gcp_spec.rb
@@ -79,17 +79,7 @@ describe Clusters::Providers::Gcp do
subject { gcp }
- it 'should default to true' do
- is_expected.to be_legacy_abac
- end
-
- context 'legacy_abac is set to false' do
- let(:gcp) { build(:cluster_provider_gcp, legacy_abac: false) }
-
- it 'is false' do
- is_expected.not_to be_legacy_abac
- end
- end
+ it { is_expected.not_to be_legacy_abac }
end
describe '#state_machine' do
diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb
index 066fe7d154e..005005b236b 100644
--- a/spec/models/commit_collection_spec.rb
+++ b/spec/models/commit_collection_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe CommitCollection do
let(:project) { create(:project, :repository) }
- let(:commit) { project.commit }
+ let(:commit) { project.commit("c1c67abbaf91f624347bb3ae96eabe3a1b742478") }
describe '#each' do
it 'yields every commit' do
@@ -12,6 +12,29 @@ describe CommitCollection do
end
end
+ describe '.committers' do
+ it 'returns a relation of users when users are found' do
+ user = create(:user, email: commit.committer_email.upcase)
+ collection = described_class.new(project, [commit])
+
+ expect(collection.committers).to contain_exactly(user)
+ end
+
+ it 'returns empty array when committers cannot be found' do
+ collection = described_class.new(project, [commit])
+
+ expect(collection.committers).to be_empty
+ end
+
+ it 'excludes authors of merge commits' do
+ commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98")
+ create(:user, email: commit.committer_email.upcase)
+ collection = described_class.new(project, [commit])
+
+ expect(collection.committers).to be_empty
+ end
+ end
+
describe '#with_pipeline_status' do
it 'sets the pipeline status for every commit so no additional queries are necessary' do
create(
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index 4b16e6e3902..64bf04071e8 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -7,7 +7,7 @@ describe DiscussionOnDiff do
let(:truncated_lines) { subject.truncated_diff_lines }
context "when diff is greater than allowed number of truncated diff lines " do
- it "returns fewer lines" do
+ it "returns fewer lines" do
expect(subject.diff_lines.count).to be > DiffDiscussion::NUMBER_OF_TRUNCATED_DIFF_LINES
expect(truncated_lines.count).to be <= DiffDiscussion::NUMBER_OF_TRUNCATED_DIFF_LINES
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index a4bf3e2350a..5753c646106 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -566,7 +566,7 @@ describe Issuable do
end
let(:merged_mr) { create(:merge_request, :merged, author: contributor, target_project: project, source_project: project) }
- let(:open_mr) { create(:merge_request, author: first_time_contributor, target_project: project, source_project: project) }
+ let(:open_mr) { create(:merge_request, author: first_time_contributor, target_project: project, source_project: project) }
let(:merged_mr_other_project) { create(:merge_request, :merged, author: first_time_contributor, target_project: other_project, source_project: other_project) }
context "for merge requests" do
diff --git a/spec/models/concerns/manual_inverse_association_spec.rb b/spec/models/concerns/manual_inverse_association_spec.rb
index aad40883854..ff4a04ea573 100644
--- a/spec/models/concerns/manual_inverse_association_spec.rb
+++ b/spec/models/concerns/manual_inverse_association_spec.rb
@@ -32,10 +32,10 @@ describe ManualInverseAssociation do
.not_to exceed_query_limit(0)
end
- it 'passes arguments to the default association method, to allow reloading' do
+ it 'allows reloading the relation' do
query_count = ActiveRecord::QueryRecorder.new do
instance.manual_association
- instance.manual_association(true)
+ instance.reload_manual_association
end.count
expect(query_count).to eq(2)
diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
new file mode 100644
index 00000000000..2f8ab21d4b2
--- /dev/null
+++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ErrorTracking::ProjectErrorTrackingSetting do
+ include ReactiveCachingHelpers
+
+ set(:project) { create(:project) }
+
+ subject { create(:project_error_tracking_setting, project: project) }
+
+ describe 'Associations' do
+ it { is_expected.to belong_to(:project) }
+ end
+
+ describe 'Validations' do
+ context 'when api_url is over 255 chars' do
+ it 'fails validation' do
+ subject.api_url = 'https://' + 'a' * 250
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:api_url]).to include('is too long (maximum is 255 characters)')
+ end
+ end
+
+ context 'With unsafe url' do
+ it 'fails validation' do
+ subject.api_url = "https://replaceme.com/'><script>alert(document.cookie)</script>"
+
+ expect(subject).not_to be_valid
+ end
+ end
+
+ context 'URL path' do
+ it 'fails validation with wrong path' do
+ subject.api_url = 'http://gitlab.com/project1/something'
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.messages[:api_url]).to include('path needs to start with /api/0/projects')
+ end
+
+ it 'passes validation with correct path' do
+ subject.api_url = 'http://gitlab.com/api/0/projects/project1/something'
+
+ expect(subject).to be_valid
+ end
+ end
+ end
+
+ describe '#sentry_external_url' do
+ let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
+
+ before do
+ subject.api_url = sentry_url
+ end
+
+ it 'returns the correct url' do
+ expect(subject.class).to receive(:extract_sentry_external_url).with(sentry_url).and_call_original
+
+ result = subject.sentry_external_url
+
+ expect(result).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project')
+ end
+ end
+
+ describe '#sentry_client' do
+ it 'returns sentry client' do
+ expect(subject.sentry_client).to be_a(Sentry::Client)
+ end
+ end
+
+ describe '#list_sentry_issues' do
+ let(:issues) { [:list, :of, :issues] }
+
+ let(:opts) do
+ { issue_status: 'unresolved', limit: 10 }
+ end
+
+ let(:result) do
+ subject.list_sentry_issues(**opts)
+ end
+
+ context 'when cached' do
+ let(:sentry_client) { spy(:sentry_client) }
+
+ before do
+ stub_reactive_cache(subject, issues, opts)
+ synchronous_reactive_cache(subject)
+
+ expect(subject).to receive(:sentry_client).and_return(sentry_client)
+ end
+
+ it 'returns cached issues' do
+ expect(sentry_client).to receive(:list_issues).with(opts)
+ .and_return(issues)
+
+ expect(result).to eq(issues: issues)
+ end
+ end
+
+ context 'when not cached' do
+ it 'returns nil' do
+ expect(subject).not_to receive(:sentry_client)
+
+ expect(result).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index a64720f1876..ce4f8ee4705 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -399,10 +399,7 @@ describe Event do
expect(event.visible_to_user?(nil)).to be_falsy
expect(event.visible_to_user?(non_member)).to be_falsy
expect(event.visible_to_user?(author)).to be_truthy
-
- # It is very unexpected that a private personal snippet is not visible
- # to an instance administrator. This should be fixed in the future.
- expect(event.visible_to_user?(admin)).to be_falsy
+ expect(event.visible_to_user?(admin)).to be_truthy
end
end
end
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index c8748daf46b..83ba22caa03 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -30,7 +30,7 @@ describe ExternalIssue do
end
context 'if issue id is a number' do
- let(:issue) { described_class.new('1234', project) }
+ let(:issue) { described_class.new('1234', project) }
it 'returns the issue ID prefixed by #' do
expect(issue.reference_link_text).to eq '#1234'
end
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index 62699df5611..f93904065c7 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -91,6 +91,12 @@ describe GlobalMilestone do
it 'sorts collection by due date' do
expect(global_milestones.map(&:due_date)).to eq [milestone1_due_date, milestone1_due_date, milestone1_due_date, nil, nil, nil]
end
+
+ it 'filters milestones by search_title when params[:search_title] is present' do
+ global_milestones = described_class.build_collection(projects, { search_title: 'v1.2' })
+
+ expect(global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'Milestone v1.2', 'Milestone v1.2'])
+ end
end
context 'when adding new milestones' do
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index 33e6f1de3d1..58a1d2e4ea2 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -199,7 +199,7 @@ describe GpgKey do
gpg_key.revoke
- expect(gpg_key.subkeys(true)).to be_blank
+ expect(gpg_key.subkeys.reload).to be_blank
end
it 'invalidates all signatures associated to the subkeys' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index e63881242f6..9dc32a815d8 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -722,6 +722,42 @@ describe Group do
end
end
+ describe '#highest_group_member', :nested_groups do
+ let(:nested_group) { create(:group, parent: group) }
+ let(:nested_group_2) { create(:group, parent: nested_group) }
+ let(:user) { create(:user) }
+
+ subject(:highest_group_member) { nested_group_2.highest_group_member(user) }
+
+ context 'when the user is not a member of any group in the hierarchy' do
+ it 'returns nil' do
+ expect(highest_group_member).to be_nil
+ end
+ end
+
+ context 'when the user is only a member of one group in the hierarchy' do
+ before do
+ nested_group.add_developer(user)
+ end
+
+ it 'returns that group member' do
+ expect(highest_group_member.access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ context 'when the user is a member of several groups in the hierarchy' do
+ before do
+ group.add_owner(user)
+ nested_group.add_developer(user)
+ nested_group_2.add_maintainer(user)
+ end
+
+ it 'returns the group member with the highest access level' do
+ expect(highest_group_member.access_level).to eq(Gitlab::Access::OWNER)
+ end
+ end
+ end
+
describe '#has_parent?' do
context 'when the group has a parent' do
it 'should be truthy' do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 6f900a60213..5d18e085a6f 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -721,39 +721,28 @@ describe Issue do
end
end
- describe '#check_for_spam' do
- let(:project) { create :project, visibility_level: visibility_level }
- let(:issue) { create :issue, project: project }
+ describe '#check_for_spam?' do
+ using RSpec::Parameterized::TableSyntax
- subject do
- issue.assign_attributes(description: description)
- issue.check_for_spam?
+ where(:visibility_level, :confidential, :new_attributes, :check_for_spam?) do
+ Gitlab::VisibilityLevel::PUBLIC | false | { description: 'woo' } | true
+ Gitlab::VisibilityLevel::PUBLIC | false | { title: 'woo' } | true
+ Gitlab::VisibilityLevel::PUBLIC | true | { confidential: false } | true
+ Gitlab::VisibilityLevel::PUBLIC | true | { description: 'woo' } | false
+ Gitlab::VisibilityLevel::PUBLIC | false | { title: 'woo', confidential: true } | false
+ Gitlab::VisibilityLevel::PUBLIC | false | { description: 'original description' } | false
+ Gitlab::VisibilityLevel::INTERNAL | false | { description: 'woo' } | false
+ Gitlab::VisibilityLevel::PRIVATE | false | { description: 'woo' } | false
end
- context 'when project is public and spammable attributes changed' do
- let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
- let(:description) { 'woo' }
+ with_them do
+ it 'checks for spam on issues that can be seen anonymously' do
+ project = create(:project, visibility_level: visibility_level)
+ issue = create(:issue, project: project, confidential: confidential, description: 'original description')
- it 'returns true' do
- is_expected.to be_truthy
- end
- end
-
- context 'when project is private' do
- let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
- let(:description) { issue.description }
-
- it 'returns false' do
- is_expected.to be_falsey
- end
- end
-
- context 'when spammable attributes have not changed' do
- let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
- let(:description) { issue.description }
+ issue.assign_attributes(new_attributes)
- it 'returns false' do
- is_expected.to be_falsey
+ expect(issue.check_for_spam?).to eq(check_for_spam?)
end
end
end
diff --git a/spec/models/label_note_spec.rb b/spec/models/label_note_spec.rb
index f69874d94aa..dd2c702a7a9 100644
--- a/spec/models/label_note_spec.rb
+++ b/spec/models/label_note_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
describe LabelNote do
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
set(:label) { create(:label, project: project) }
set(:label2) { create(:label, project: project) }
let(:resource_parent) { project }
diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb
index e74f342d3eb..41ca1578b94 100644
--- a/spec/models/lfs_file_lock_spec.rb
+++ b/spec/models/lfs_file_lock_spec.rb
@@ -13,7 +13,7 @@ describe LfsFileLock do
describe '#can_be_unlocked_by?' do
let(:developer) { create(:user) }
- let(:maintainer) { create(:user) }
+ let(:maintainer) { create(:user) }
before do
project = lfs_file_lock.project
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 99d3ab41b97..36bfff2c339 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -39,7 +39,7 @@ describe ProjectMember do
describe "#destroy" do
let(:owner) { create(:project_member, access_level: ProjectMember::MAINTAINER) }
let(:project) { owner.project }
- let(:maintainer) { create(:project_member, project: project) }
+ let(:maintainer) { create(:project_member, project: project) }
it "creates an expired event when left due to expiry" do
expired = create(:project_member, project: project, expires_at: Time.now - 6.days)
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
index 8c01a7ac18f..10487190a44 100644
--- a/spec/models/merge_request_diff_commit_spec.rb
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -16,7 +16,7 @@ describe MergeRequestDiffCommit do
end
describe '.create_bulk' do
- let(:sha_attribute) { Gitlab::Database::ShaAttribute.new }
+ let(:sha_attribute) { Gitlab::Database::ShaAttribute.new }
let(:merge_request_diff_id) { merge_request.merge_request_diff.id }
let(:commits) do
[
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 4cc3a6a3644..b62f973ad1e 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -991,6 +991,34 @@ describe MergeRequest do
end
end
+ describe '#committers' do
+ it 'returns all the committers of every commit in the merge request' do
+ users = subject.commits.map(&:committer_email).uniq.map do |email|
+ create(:user, email: email)
+ end
+
+ expect(subject.committers).to match_array(users)
+ end
+
+ it 'returns an empty array if no committer is associated with a user' do
+ expect(subject.committers).to be_empty
+ end
+ end
+
+ describe '#authors' do
+ it 'returns a list with all the committers in the merge request and author' do
+ users = subject.commits.map(&:committer_email).uniq.map do |email|
+ create(:user, email: email)
+ end
+
+ expect(subject.authors).to match_array([subject.author, *users])
+ end
+
+ it 'returns only the author if no committer is associated with a user' do
+ expect(subject.authors).to contain_exactly(subject.author)
+ end
+ end
+
describe '#hook_attrs' do
it 'delegates to Gitlab::HookData::MergeRequestBuilder#build' do
builder = double
@@ -1004,7 +1032,7 @@ describe MergeRequest do
end
describe '#diverged_commits_count' do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository) }
let(:forked_project) { fork_project(project, nil, repository: true) }
context 'when the target branch does not exist anymore' do
@@ -1372,6 +1400,51 @@ describe MergeRequest do
end
end
+ describe '#update_head_pipeline' do
+ subject { merge_request.update_head_pipeline }
+
+ let(:merge_request) { create(:merge_request) }
+
+ context 'when there is a pipeline with the diff head sha' do
+ let!(:pipeline) do
+ create(:ci_empty_pipeline,
+ project: merge_request.project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch)
+ end
+
+ it 'updates the head pipeline' do
+ expect { subject }
+ .to change { merge_request.reload.head_pipeline }
+ .from(nil).to(pipeline)
+ end
+
+ context 'when merge request has already had head pipeline' do
+ before do
+ merge_request.update!(head_pipeline: pipeline)
+ end
+
+ context 'when failed to find an actual head pipeline' do
+ before do
+ allow(merge_request).to receive(:find_actual_head_pipeline) { }
+ end
+
+ it 'does not update the current head pipeline' do
+ expect { subject }
+ .not_to change { merge_request.reload.head_pipeline }
+ end
+ end
+ end
+ end
+
+ context 'when there are no pipelines with the diff head sha' do
+ it 'does not update the head pipeline' do
+ expect { subject }
+ .not_to change { merge_request.reload.head_pipeline }
+ end
+ end
+ end
+
describe '#has_test_reports?' do
subject { merge_request.has_test_reports? }
@@ -2225,9 +2298,9 @@ describe MergeRequest do
end
describe "#source_project_missing?" do
- let(:project) { create(:project) }
+ let(:project) { create(:project) }
let(:forked_project) { fork_project(project) }
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
context "when the fork exists" do
@@ -2297,9 +2370,9 @@ describe MergeRequest do
end
describe "#closed_without_fork?" do
- let(:project) { create(:project) }
+ let(:project) { create(:project) }
let(:forked_project) { fork_project(project) }
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
context "when the merge request is closed" do
@@ -2421,7 +2494,7 @@ describe MergeRequest do
expect(merge_request.mergeable_with_quick_action?(user, last_diff_sha: mr_sha)).to be_falsey
end
- context 'closed MR' do
+ context 'closed MR' do
before do
merge_request.update_attribute(:state, :closed)
end
@@ -2431,7 +2504,7 @@ describe MergeRequest do
end
end
- context 'MR with WIP' do
+ context 'MR with WIP' do
before do
merge_request.update_attribute(:title, 'WIP: some MR')
end
@@ -2441,19 +2514,19 @@ describe MergeRequest do
end
end
- context 'sha differs from the MR diff_head_sha' do
+ context 'sha differs from the MR diff_head_sha' do
it 'is not mergeable' do
expect(merge_request.mergeable_with_quick_action?(developer, last_diff_sha: 'some other sha')).to be_falsey
end
end
- context 'sha is not provided' do
+ context 'sha is not provided' do
it 'is not mergeable' do
expect(merge_request.mergeable_with_quick_action?(developer)).to be_falsey
end
end
- context 'with pipeline ok' do
+ context 'with pipeline ok' do
before do
create_pipeline(:success)
end
@@ -2463,7 +2536,7 @@ describe MergeRequest do
end
end
- context 'with failing pipeline' do
+ context 'with failing pipeline' do
before do
create_pipeline(:failed)
end
@@ -2473,7 +2546,7 @@ describe MergeRequest do
end
end
- context 'with running pipeline' do
+ context 'with running pipeline' do
before do
create_pipeline(:running)
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index b3d31e65c85..af7e3d3a6c9 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -240,7 +240,111 @@ describe Milestone do
end
end
- describe '.upcoming_ids_by_projects' do
+ describe '#search_title' do
+ let(:milestone) { create(:milestone, title: 'foo', description: 'bar') }
+
+ it 'returns milestones with a matching title' do
+ expect(described_class.search_title(milestone.title)) .to eq([milestone])
+ end
+
+ it 'returns milestones with a partially matching title' do
+ expect(described_class.search_title(milestone.title[0..2])).to eq([milestone])
+ end
+
+ it 'returns milestones with a matching title regardless of the casing' do
+ expect(described_class.search_title(milestone.title.upcase))
+ .to eq([milestone])
+ end
+
+ it 'searches only on the title and ignores milestones with a matching description' do
+ create(:milestone, title: 'bar', description: 'foo')
+
+ expect(described_class.search_title(milestone.title)) .to eq([milestone])
+ end
+ end
+
+ describe '#for_projects_and_groups' do
+ let(:project) { create(:project) }
+ let(:project_other) { create(:project) }
+ let(:group) { create(:group) }
+ let(:group_other) { create(:group) }
+
+ before do
+ create(:milestone, project: project)
+ create(:milestone, project: project_other)
+ create(:milestone, group: group)
+ create(:milestone, group: group_other)
+ end
+
+ subject { described_class.for_projects_and_groups(projects, groups) }
+
+ shared_examples 'filters by projects and groups' do
+ it 'returns milestones filtered by project' do
+ milestones = described_class.for_projects_and_groups(projects, [])
+
+ expect(milestones.count).to eq(1)
+ expect(milestones.first.project_id).to eq(project.id)
+ end
+
+ it 'returns milestones filtered by group' do
+ milestones = described_class.for_projects_and_groups([], groups)
+
+ expect(milestones.count).to eq(1)
+ expect(milestones.first.group_id).to eq(group.id)
+ end
+
+ it 'returns milestones filtered by both project and group' do
+ milestones = described_class.for_projects_and_groups(projects, groups)
+
+ expect(milestones.count).to eq(2)
+ expect(milestones).to contain_exactly(project.milestones.first, group.milestones.first)
+ end
+ end
+
+ context 'ids as params' do
+ let(:projects) { [project.id] }
+ let(:groups) { [group.id] }
+
+ it_behaves_like 'filters by projects and groups'
+ end
+
+ context 'relations as params' do
+ let(:projects) { Project.where(id: project.id).select(:id) }
+ let(:groups) { Group.where(id: group.id).select(:id) }
+
+ it_behaves_like 'filters by projects and groups'
+ end
+
+ context 'objects as params' do
+ let(:projects) { [project] }
+ let(:groups) { [group] }
+
+ it_behaves_like 'filters by projects and groups'
+ end
+
+ it 'returns no records if projects and groups are nil' do
+ milestones = described_class.for_projects_and_groups(nil, nil)
+
+ expect(milestones).to be_empty
+ end
+ end
+
+ describe '.upcoming_ids' do
+ let(:group_1) { create(:group) }
+ let(:group_2) { create(:group) }
+ let(:group_3) { create(:group) }
+ let(:groups) { [group_1, group_2, group_3] }
+
+ let!(:past_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.now - 1.day) }
+ let!(:current_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.now + 1.day) }
+ let!(:future_milestone_group_1) { create(:milestone, group: group_1, due_date: Time.now + 2.days) }
+
+ let!(:past_milestone_group_2) { create(:milestone, group: group_2, due_date: Time.now - 1.day) }
+ let!(:closed_milestone_group_2) { create(:milestone, :closed, group: group_2, due_date: Time.now + 1.day) }
+ let!(:current_milestone_group_2) { create(:milestone, group: group_2, due_date: Time.now + 2.days) }
+
+ let!(:past_milestone_group_3) { create(:milestone, group: group_3, due_date: Time.now - 1.day) }
+
let(:project_1) { create(:project) }
let(:project_2) { create(:project) }
let(:project_3) { create(:project) }
@@ -256,16 +360,20 @@ describe Milestone do
let!(:past_milestone_project_3) { create(:milestone, project: project_3, due_date: Time.now - 1.day) }
- # The call to `#try` is because this returns a relation with a Postgres DB,
- # and an array of IDs with a MySQL DB.
- let(:milestone_ids) { described_class.upcoming_ids_by_projects(projects).map { |id| id.try(:id) || id } }
+ let(:milestone_ids) { described_class.upcoming_ids(projects, groups).map(&:id) }
- it 'returns the next upcoming open milestone ID for each project' do
- expect(milestone_ids).to contain_exactly(current_milestone_project_1.id, current_milestone_project_2.id)
+ 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
- context 'when the projects have no open upcoming milestones' do
+ context 'when the projects and groups have no open upcoming milestones' do
let(:projects) { [project_3] }
+ let(:groups) { [group_3] }
it 'returns no results' do
expect(milestone_ids).to be_empty
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index bcdfe3cf1eb..385b8a7959f 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -890,4 +890,19 @@ describe Note do
end
end
end
+
+ describe '#parent' do
+ it 'returns project for project notes' do
+ project = create(:project)
+ note = create(:note_on_issue, project: project)
+
+ expect(note.parent).to eq(project)
+ end
+
+ it 'returns nil for personal snippet note' do
+ note = create(:note_on_personal_snippet)
+
+ expect(note.parent).to be_nil
+ end
+ end
end
diff --git a/spec/models/project_import_data_spec.rb b/spec/models/project_import_data_spec.rb
index e9910c0a5d1..fe47811f074 100644
--- a/spec/models/project_import_data_spec.rb
+++ b/spec/models/project_import_data_spec.rb
@@ -39,4 +39,15 @@ describe ProjectImportData do
expect(row.credentials).to eq({ 'number' => 10, 'foo' => 'bar' })
end
end
+
+ describe '#clear_credentials' do
+ it 'clears out the Hash' do
+ row = described_class.new
+
+ row.merge_credentials('number' => 10)
+ row.clear_credentials
+
+ expect(row.credentials).to eq({})
+ end
+ end
end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index d8972aff407..26597d9b83c 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -117,7 +117,7 @@ describe DroneCiService, :use_clean_rails_memory_store_caching do
describe "execute" do
include_context :drone_ci_service
- let(:user) { create(:user, username: 'username') }
+ let(:user) { create(:user, username: 'username') }
let(:push_sample_data) do
Gitlab::DataBuilder::Push.build_sample(project, user)
end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 43a0ed99296..64b4efca43a 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -205,7 +205,7 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do
end
def stub_request(status: 200, body: nil, build_status: 'success')
- teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,number:123'
+ teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123'
auth = %w(mic password)
body ||= %Q({"build":{"status":"#{build_status}","id":"666"}})
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 4b6592020c1..437f0066450 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -62,6 +62,7 @@ describe Project do
it { is_expected.to have_one(:last_event).class_name('Event') }
it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
+ it { is_expected.to have_one(:error_tracking_setting).class_name('ErrorTracking::ProjectErrorTrackingSetting') }
it { is_expected.to have_many(:commit_statuses) }
it { is_expected.to have_many(:ci_pipelines) }
it { is_expected.to have_many(:builds) }
@@ -208,9 +209,14 @@ describe Project do
it 'does not allow new projects beyond user limits' do
project2 = build(:project)
- allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
+
+ allow(project2)
+ .to receive(:creator)
+ .and_return(
+ double(can_create_project?: false, projects_limit: 0).as_null_object
+ )
+
expect(project2).not_to be_valid
- expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
end
describe 'wiki path conflict' do
@@ -610,16 +616,20 @@ describe Project do
end
it 'returns the address to create a new issue' do
- address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab"
+ address = "p+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-issue@gl.ab"
expect(project.new_issuable_address(user, 'issue')).to eq(address)
end
it 'returns the address to create a new merge request' do
- address = "p+#{project.full_path}+merge-request+#{user.incoming_email_token}@gl.ab"
+ address = "p+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-merge-request@gl.ab"
expect(project.new_issuable_address(user, 'merge_request')).to eq(address)
end
+
+ it 'returns nil with invalid address type' do
+ expect(project.new_issuable_address(user, 'invalid_param')).to be_nil
+ end
end
context 'incoming email disabled' do
@@ -1100,13 +1110,13 @@ describe Project do
describe '#pipeline_for' do
let(:project) { create(:project, :repository) }
- let!(:pipeline) { create_pipeline }
+ let!(:pipeline) { create_pipeline(project) }
shared_examples 'giving the correct pipeline' do
it { is_expected.to eq(pipeline) }
context 'return latest' do
- let!(:pipeline2) { create_pipeline }
+ let!(:pipeline2) { create_pipeline(project) }
it { is_expected.to eq(pipeline2) }
end
@@ -1123,13 +1133,6 @@ describe Project do
it_behaves_like 'giving the correct pipeline'
end
-
- def create_pipeline
- create(:ci_pipeline,
- project: project,
- ref: 'master',
- sha: project.commit('master').sha)
- end
end
describe '#builds_enabled' do
@@ -1415,6 +1418,24 @@ describe Project do
end
end
+ describe '#visibility_level' do
+ let(:project) { build(:project) }
+
+ subject { project.visibility_level }
+
+ context 'by default' do
+ it { is_expected.to eq(Gitlab::VisibilityLevel::PRIVATE) }
+ end
+
+ context 'when set to INTERNAL in application settings' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it { is_expected.to eq(Gitlab::VisibilityLevel::INTERNAL) }
+ end
+ end
+
describe '#visibility_level_allowed?' do
let(:project) { create(:project, :internal) }
@@ -1917,38 +1938,21 @@ describe Project do
end
end
- describe '#latest_successful_builds_for and #latest_successful_build_for' do
- def create_pipeline(status = 'success')
- create(:ci_pipeline, project: project,
- sha: project.commit.sha,
- ref: project.default_branch,
- status: status)
- end
-
- def create_build(new_pipeline = pipeline, name = 'test')
- create(:ci_build, :success, :artifacts,
- pipeline: new_pipeline,
- status: new_pipeline.status,
- name: name)
- end
-
+ describe '#latest_successful_build_for' do
let(:project) { create(:project, :repository) }
- let(:pipeline) { create_pipeline }
+ let(:pipeline) { create_pipeline(project) }
context 'with many builds' do
it 'gives the latest builds from latest pipeline' do
- pipeline1 = create_pipeline
- pipeline2 = create_pipeline
+ pipeline1 = create_pipeline(project)
+ pipeline2 = create_pipeline(project)
create_build(pipeline1, 'test')
create_build(pipeline1, 'test2')
build1_p2 = create_build(pipeline2, 'test')
- build2_p2 = create_build(pipeline2, 'test2')
+ create_build(pipeline2, 'test2')
- latest_builds = project.latest_successful_builds_for
- single_build = project.latest_successful_build_for(build1_p2.name)
-
- expect(latest_builds).to contain_exactly(build2_p2, build1_p2)
- expect(single_build).to eq(build1_p2)
+ expect(project.latest_successful_build_for(build1_p2.name))
+ .to eq(build1_p2)
end
end
@@ -1957,51 +1961,90 @@ describe Project do
context 'standalone pipeline' do
it 'returns builds for ref for default_branch' do
- builds = project.latest_successful_builds_for
- single_build = project.latest_successful_build_for(build.name)
+ expect(project.latest_successful_build_for(build.name))
+ .to eq(build)
+ end
- expect(builds).to contain_exactly(build)
- expect(single_build).to eq(build)
+ it 'returns empty relation if the build cannot be found' do
+ expect(project.latest_successful_build_for('TAIL'))
+ .to be_nil
end
+ end
+
+ context 'with some pending pipeline' do
+ before do
+ create_build(create_pipeline(project, 'pending'))
+ end
+
+ it 'gives the latest build from latest pipeline' do
+ expect(project.latest_successful_build_for(build.name))
+ .to eq(build)
+ end
+ end
+ end
+
+ context 'with pending pipeline' do
+ it 'returns empty relation' do
+ pipeline.update(status: 'pending')
+ pending_build = create_build(pipeline)
+
+ expect(project.latest_successful_build_for(pending_build.name)).to be_nil
+ end
+ end
+ end
+
+ describe '#latest_successful_build_for!' do
+ let(:project) { create(:project, :repository) }
+ let(:pipeline) { create_pipeline(project) }
- it 'returns empty relation if the build cannot be found for #latest_successful_builds_for' do
- builds = project.latest_successful_builds_for('TAIL')
+ context 'with many builds' do
+ it 'gives the latest builds from latest pipeline' do
+ pipeline1 = create_pipeline(project)
+ pipeline2 = create_pipeline(project)
+ create_build(pipeline1, 'test')
+ create_build(pipeline1, 'test2')
+ build1_p2 = create_build(pipeline2, 'test')
+ create_build(pipeline2, 'test2')
- expect(builds).to be_kind_of(ActiveRecord::Relation)
- expect(builds).to be_empty
+ expect(project.latest_successful_build_for(build1_p2.name))
+ .to eq(build1_p2)
+ end
+ end
+
+ context 'with succeeded pipeline' do
+ let!(:build) { create_build }
+
+ context 'standalone pipeline' do
+ it 'returns builds for ref for default_branch' do
+ expect(project.latest_successful_build_for!(build.name))
+ .to eq(build)
end
- it 'returns exception if the build cannot be found for #latest_successful_build_for' do
- expect { project.latest_successful_build_for(build.name, 'TAIL') }.to raise_error(ActiveRecord::RecordNotFound)
+ it 'returns exception if the build cannot be found' do
+ expect { project.latest_successful_build_for!(build.name, 'TAIL') }
+ .to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'with some pending pipeline' do
before do
- create_build(create_pipeline('pending'))
+ create_build(create_pipeline(project, 'pending'))
end
it 'gives the latest build from latest pipeline' do
- latest_builds = project.latest_successful_builds_for
- last_single_build = project.latest_successful_build_for(build.name)
-
- expect(latest_builds).to contain_exactly(build)
- expect(last_single_build).to eq(build)
+ expect(project.latest_successful_build_for!(build.name))
+ .to eq(build)
end
end
end
context 'with pending pipeline' do
- before do
- pipeline.update(status: 'pending')
- create_build(pipeline)
- end
-
it 'returns empty relation' do
- builds = project.latest_successful_builds_for
+ pipeline.update(status: 'pending')
+ pending_build = create_build(pipeline)
- expect(builds).to be_kind_of(ActiveRecord::Relation)
- expect(builds).to be_empty
+ expect { project.latest_successful_build_for!(pending_build.name) }
+ .to raise_error(ActiveRecord::RecordNotFound)
end
end
end
@@ -2406,6 +2449,20 @@ describe Project do
end
end
+ describe '#set_repository_read_only!' do
+ let(:project) { create(:project) }
+
+ it 'returns true when there is no existing git transfer in progress' do
+ expect(project.set_repository_read_only!).to be_truthy
+ end
+
+ it 'returns false when there is an existing git transfer in progress' do
+ allow(project).to receive(:git_transfer_in_progress?) { true }
+
+ expect(project.set_repository_read_only!).to be_falsey
+ end
+ end
+
describe '#pushes_since_gc' do
let(:project) { create(:project) }
@@ -2959,6 +3016,24 @@ describe Project do
end
end
+ describe '#update' do
+ let(:project) { create(:project) }
+
+ it 'validates the visibility' do
+ expect(project).to receive(:visibility_level_allowed_as_fork).and_call_original
+ expect(project).to receive(:visibility_level_allowed_by_group).and_call_original
+
+ project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it 'does not validate the visibility' do
+ expect(project).not_to receive(:visibility_level_allowed_as_fork).and_call_original
+ expect(project).not_to receive(:visibility_level_allowed_by_group).and_call_original
+
+ project.update(updated_at: Time.now)
+ end
+ end
+
describe '#last_repository_updated_at' do
it 'sets to created_at upon creation' do
project = create(:project, created_at: 2.hours.ago)
@@ -3017,7 +3092,7 @@ describe Project do
context 'when the project is in a subgroup' do
let(:group) { create(:group, :nested) }
- it { is_expected.to be(false) }
+ it { is_expected.to be(true) }
end
end
@@ -3121,6 +3196,33 @@ describe Project do
end
end
+ describe '#git_transfer_in_progress?' do
+ let(:project) { build(:project) }
+
+ subject { project.git_transfer_in_progress? }
+
+ it 'returns false when repo_reference_count and wiki_reference_count are 0' do
+ allow(project).to receive(:repo_reference_count) { 0 }
+ allow(project).to receive(:wiki_reference_count) { 0 }
+
+ expect(subject).to be_falsey
+ end
+
+ it 'returns true when repo_reference_count is > 0' do
+ allow(project).to receive(:repo_reference_count) { 2 }
+ allow(project).to receive(:wiki_reference_count) { 0 }
+
+ expect(subject).to be_truthy
+ end
+
+ it 'returns true when wiki_reference_count is > 0' do
+ allow(project).to receive(:repo_reference_count) { 0 }
+ allow(project).to receive(:wiki_reference_count) { 2 }
+
+ expect(subject).to be_truthy
+ end
+ end
+
context 'legacy storage' do
let(:project) { create(:project, :repository, :legacy_storage) }
let(:gitlab_shell) { Gitlab::Shell.new }
@@ -3181,8 +3283,11 @@ describe Project do
expect(project.migrate_to_hashed_storage!).to be_truthy
end
- it 'flags as read-only' do
- expect { project.migrate_to_hashed_storage! }.to change { project.repository_read_only }.to(true)
+ it 'does not validate project visibility' do
+ expect(project).not_to receive(:visibility_level_allowed_as_fork)
+ expect(project).not_to receive(:visibility_level_allowed_by_group)
+
+ project.migrate_to_hashed_storage!
end
it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the project repo is in use' do
@@ -3496,7 +3601,31 @@ describe Project do
end
end
- context '#auto_devops_variables' do
+ describe '#api_variables' do
+ set(:project) { create(:project) }
+
+ it 'exposes API v4 URL' do
+ expect(project.api_variables.first[:key]).to eq 'CI_API_V4_URL'
+ expect(project.api_variables.first[:value]).to include '/api/v4'
+ end
+
+ it 'contains a URL variable for every supported API version' do
+ # Ensure future API versions have proper variables defined. We're not doing this for v3.
+ supported_versions = API::API.versions - ['v3']
+ supported_versions = supported_versions.select do |version|
+ API::API.routes.select { |route| route.version == version }.many?
+ end
+
+ required_variables = supported_versions.map do |version|
+ "CI_API_#{version.upcase}_URL"
+ end
+
+ expect(project.api_variables.map { |variable| variable[:key] })
+ .to contain_exactly(*required_variables)
+ end
+ end
+
+ describe '#auto_devops_variables' do
set(:project) { create(:project) }
subject { project.auto_devops_variables }
@@ -3797,7 +3926,7 @@ describe Project do
describe '#badges' do
let(:project_group) { create(:group) }
- let(:project) { create(:project, path: 'avatar', namespace: project_group) }
+ let(:project) { create(:project, path: 'avatar', namespace: project_group) }
before do
create_list(:project_badge, 2, project: project)
@@ -3831,6 +3960,16 @@ describe Project do
let(:user) { create(:user) }
let(:target_project) { create(:project, :repository) }
let(:project) { fork_project(target_project, nil, repository: true) }
+ let!(:local_merge_request) do
+ create(
+ :merge_request,
+ target_project: project,
+ target_branch: 'target-branch',
+ source_project: project,
+ source_branch: 'awesome-feature-1',
+ allow_collaboration: true
+ )
+ end
let!(:merge_request) do
create(
:merge_request,
@@ -3875,14 +4014,23 @@ describe Project do
end
end
- describe '#branch_allows_collaboration_push?' do
- it 'allows access if the user can merge the merge request' do
- expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
+ describe '#any_branch_allows_collaboration?' do
+ it 'allows access when there are merge requests open allowing collaboration' do
+ expect(project.any_branch_allows_collaboration?(user))
.to be_truthy
end
- it 'allows access when there are merge requests open but no branch name is given' do
- expect(project.branch_allows_collaboration?(user, nil))
+ it 'does not allow access when there are no merge requests open allowing collaboration' do
+ merge_request.close!
+
+ expect(project.any_branch_allows_collaboration?(user))
+ .to be_falsey
+ end
+ end
+
+ describe '#branch_allows_collaboration?' do
+ it 'allows access if the user can merge the merge request' do
+ expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
.to be_truthy
end
@@ -3913,13 +4061,6 @@ describe Project do
.to be_falsy
end
- it 'caches the result' do
- control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') }
-
- expect { 3.times { project.branch_allows_collaboration?(user, 'awesome-feature-1') } }
- .not_to exceed_query_limit(control)
- end
-
context 'when the requeststore is active', :request_store do
it 'only queries per project across instances' do
control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') }
@@ -4284,7 +4425,101 @@ describe Project do
end
end
+ describe '#leave_pool_repository' do
+ let(:pool) { create(:pool_repository) }
+ let(:project) { create(:project, :repository, pool_repository: pool) }
+
+ it 'removes the membership' do
+ project.leave_pool_repository
+
+ expect(pool.member_projects.reload).not_to include(project)
+ end
+ end
+
+ describe '#check_personal_projects_limit' do
+ context 'when creating a project for a group' do
+ it 'does nothing' do
+ creator = build(:user)
+ project = build(:project, namespace: build(:group), creator: creator)
+
+ allow(creator)
+ .to receive(:can_create_project?)
+ .and_return(false)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors).to be_empty
+ end
+ end
+
+ context 'when the user is not allowed to create a personal project' do
+ let(:user) { build(:user) }
+ let(:project) { build(:project, creator: user) }
+
+ before do
+ allow(user)
+ .to receive(:can_create_project?)
+ .and_return(false)
+ end
+
+ context 'when the project limit is zero' do
+ it 'adds a validation error' do
+ allow(user)
+ .to receive(:projects_limit)
+ .and_return(0)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors[:limit_reached].first)
+ .to match(/Personal project creation is not allowed/)
+ end
+ end
+
+ context 'when the project limit is greater than zero' do
+ it 'adds a validation error' do
+ allow(user)
+ .to receive(:projects_limit)
+ .and_return(5)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors[:limit_reached].first)
+ .to match(/Your project limit is 5 projects/)
+ end
+ end
+ end
+
+ context 'when the user is allowed to create personal projects' do
+ it 'does nothing' do
+ user = build(:user)
+ project = build(:project, creator: user)
+
+ allow(user)
+ .to receive(:can_create_project?)
+ .and_return(true)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors).to be_empty
+ end
+ end
+ end
+
def rugged_config
rugged_repo(project.repository).config
end
+
+ def create_pipeline(project, status = 'success')
+ create(:ci_pipeline, project: project,
+ sha: project.commit.sha,
+ ref: project.default_branch,
+ status: status)
+ end
+
+ def create_build(new_pipeline = pipeline, name = 'test')
+ create(:ci_build, :success, :artifacts,
+ pipeline: new_pipeline,
+ status: new_pipeline.status,
+ name: name)
+ end
end
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 92ba2d82f58..157c96c1f65 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -10,10 +10,35 @@ RSpec.describe Release do
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:author).class_name('User') }
+ it { is_expected.to have_many(:links).class_name('Releases::Link') }
end
describe 'validation' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:description) }
end
+
+ describe '#assets_count' do
+ subject { release.assets_count }
+
+ it 'returns the number of sources' do
+ is_expected.to eq(Releases::Source::FORMATS.count)
+ end
+
+ context 'when a links exists' do
+ let!(:link) { create(:release_link, release: release) }
+
+ it 'counts the link as an asset' do
+ is_expected.to eq(1 + Releases::Source::FORMATS.count)
+ end
+ end
+ end
+
+ describe '#sources' do
+ subject { release.sources }
+
+ it 'returns sources' do
+ is_expected.to all(be_a(Releases::Source))
+ end
+ end
end
diff --git a/spec/models/releases/link_spec.rb b/spec/models/releases/link_spec.rb
new file mode 100644
index 00000000000..06ed1438688
--- /dev/null
+++ b/spec/models/releases/link_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Releases::Link do
+ let(:release) { create(:release, project: project) }
+ let(:project) { create(:project) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:release) }
+ end
+
+ describe 'validation' do
+ it { is_expected.to validate_presence_of(:url) }
+ it { is_expected.to validate_presence_of(:name) }
+
+ context 'when url is invalid' do
+ let(:link) { build(:release_link, url: 'hoge') }
+
+ it 'will be invalid' do
+ expect(link).to be_invalid
+ end
+ end
+
+ context 'when duplicate name is added to a release' do
+ let!(:link) { create(:release_link, name: 'alpha', release: release) }
+
+ it 'raises an error' do
+ expect do
+ create(:release_link, name: 'alpha', release: release)
+ end.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+
+ context 'when duplicate url is added to a release' do
+ let!(:link) { create(:release_link, url: 'http://gitlab.com', release: release) }
+
+ it 'raises an error' do
+ expect do
+ create(:release_link, url: 'http://gitlab.com', release: release)
+ end.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+
+ describe '.sorted' do
+ subject { described_class.sorted }
+
+ let!(:link_1) { create(:release_link, name: 'alpha', release: release, created_at: 1.day.ago) }
+ let!(:link_2) { create(:release_link, name: 'beta', release: release, created_at: 2.days.ago) }
+
+ it 'returns a list of links by created_at order' do
+ is_expected.to eq([link_1, link_2])
+ end
+ end
+
+ describe '#internal?' do
+ subject { link.internal? }
+
+ let(:link) { build(:release_link, release: release, url: url) }
+ let(:url) { "#{project.web_url}/-/jobs/140463678/artifacts/download" }
+
+ it { is_expected.to be_truthy }
+
+ context 'when link does not include project web url' do
+ let(:url) { 'https://google.com/-/jobs/140463678/artifacts/download' }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#external?' do
+ subject { link.external? }
+
+ let(:link) { build(:release_link, release: release, url: url) }
+ let(:url) { 'https://google.com/-/jobs/140463678/artifacts/download' }
+
+ it { is_expected.to be_truthy }
+ end
+end
diff --git a/spec/models/releases/source_spec.rb b/spec/models/releases/source_spec.rb
new file mode 100644
index 00000000000..c5213196962
--- /dev/null
+++ b/spec/models/releases/source_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Releases::Source do
+ set(:project) { create(:project, :repository, name: 'finance-cal') }
+ let(:tag_name) { 'v1.0' }
+
+ describe '.all' do
+ subject { described_class.all(project, tag_name) }
+
+ it 'returns all formats of sources' do
+ expect(subject.map(&:format))
+ .to match_array(described_class::FORMATS)
+ end
+ end
+
+ describe '#url' do
+ subject { source.url }
+
+ let(:source) do
+ described_class.new(project: project, tag_name: tag_name, format: format)
+ end
+
+ let(:format) { 'zip' }
+
+ it 'returns zip archived source url' do
+ is_expected
+ .to eq("#{project.web_url}/-/archive/v1.0/finance-cal-v1.0.zip")
+ end
+
+ context 'when ref is directory structure' do
+ let(:tag_name) { 'beta/v1.0' }
+
+ it 'converts slash to dash' do
+ is_expected
+ .to eq("#{project.web_url}/-/archive/beta/v1.0/finance-cal-beta-v1.0.zip")
+ end
+ end
+ end
+end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 224bc9ed935..c06e9a08ab4 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -303,6 +303,25 @@ describe RemoteMirror, :mailer do
end
end
+ context '#url=' do
+ let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
+
+ it 'resets all the columns when URL changes' do
+ remote_mirror.update(last_error: Time.now,
+ last_update_at: Time.now,
+ last_successful_update_at: Time.now,
+ update_status: 'started',
+ error_notification_sent: true)
+
+ expect { remote_mirror.update_attribute(:url, 'http://new.example.com') }
+ .to change { remote_mirror.last_error }.to(nil)
+ .and change { remote_mirror.last_update_at }.to(nil)
+ .and change { remote_mirror.last_successful_update_at }.to(nil)
+ .and change { remote_mirror.update_status }.to('finished')
+ .and change { remote_mirror.error_notification_sent }.to(false)
+ end
+ end
+
context '#updated_since?' do
let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
let(:timestamp) { Time.now - 5.minutes }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 2063b4bbe75..ac5874fd0f7 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2400,22 +2400,4 @@ describe Repository do
repository.merge_base('master', 'fix')
end
end
-
- describe '#cache' do
- subject(:cache) { repository.send(:cache) }
-
- it 'returns a RepositoryCache' do
- expect(subject).to be_kind_of Gitlab::RepositoryCache
- end
-
- it 'when is_wiki it includes wiki as part of key' do
- allow(repository).to receive(:is_wiki) { true }
-
- expect(subject.namespace).to include('wiki')
- end
-
- it 'when is_wiki is false extra_namespace is nil' do
- expect(subject.namespace).not_to include('wiki')
- end
- end
end
diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb
index e7e3f7376e6..3797960ac3d 100644
--- a/spec/models/resource_label_event_spec.rb
+++ b/spec/models/resource_label_event_spec.rb
@@ -88,7 +88,7 @@ RSpec.describe ResourceLabelEvent, type: :model do
end
it 'returns false if label and reference are set' do
- subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION }
+ subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION }
expect(subject.outdated_markdown?).to be false
end
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index 3809692b373..397eaee068c 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -128,6 +128,17 @@ describe PersonalSnippetPolicy do
end
end
+ context 'admin user' do
+ subject { permissions(admin_user) }
+
+ it do
+ is_expected.to be_allowed(:read_personal_snippet)
+ is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(:award_emoji)
+ is_expected.to be_disallowed(*author_permissions)
+ end
+ end
+
context 'external user' do
subject { permissions(external_user) }
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 9cb20854f6e..7705704a07f 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -24,7 +24,7 @@ describe ProjectPolicy do
download_code fork_project create_project_snippet update_issue
admin_issue admin_label admin_list read_commit_status read_build
read_container_image read_pipeline read_environment read_deployment
- read_merge_request download_wiki_code
+ read_merge_request download_wiki_code read_sentry_issue
]
end
@@ -69,7 +69,7 @@ describe ProjectPolicy do
end
# Used in EE specs
- let(:additional_guest_permissions) { [] }
+ let(:additional_guest_permissions) { [] }
let(:additional_reporter_permissions) { [] }
let(:additional_maintainer_permissions) { [] }
@@ -236,7 +236,7 @@ describe ProjectPolicy do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) }
let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
- let(:anonymous_permissions) { guest_permissions - user_permissions }
+ let(:anonymous_permissions) { guest_permissions - user_permissions }
subject { described_class.new(nil, project) }
diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
index 2cc0076d695..001545bb5df 100644
--- a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
+++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Projects::Settings::DeployKeysPresenter do
let(:project) { create(:project) }
let(:user) { create(:user) }
- let(:deploy_key) { create(:deploy_key, public: true) }
+ let(:deploy_key) { create(:deploy_key, public: true) }
let!(:deploy_keys_project) do
create(:deploy_keys_project, project: project, deploy_key: deploy_key)
diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb
index 17e66725dc9..9bc49bd5982 100644
--- a/spec/requests/api/avatar_spec.rb
+++ b/spec/requests/api/avatar_spec.rb
@@ -65,7 +65,7 @@ describe API::Avatar do
expect(GravatarService).to receive(:new).and_return(gravatar_service)
expect(gravatar_service).to(
receive(:execute)
- .with('private@example.com', nil, 2, { username: nil })
+ .with('private@example.com', nil, 2, { username: nil })
.and_return('https://gravatar'))
end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 22f6fcdc922..6c67d84b59b 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -7,7 +7,7 @@ describe API::AwardEmoji do
set(:award_emoji) { create(:award_emoji, awardable: issue, user: user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) }
- set(:note) { create(:note, project: project, noteable: issue) }
+ set(:note) { create(:note, project: project, noteable: issue) }
before do
project.add_maintainer(user)
@@ -144,7 +144,7 @@ describe API::AwardEmoji do
end
describe "POST /projects/:id/awardable/:awardable_id/award_emoji" do
- let(:issue2) { create(:issue, project: project, author: user) }
+ let(:issue2) { create(:issue, project: project, author: user) }
context "on an issue" do
it "creates a new award emoji" do
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index ab4f42cad47..de79e8c4c5c 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -31,7 +31,7 @@ describe API::Boards do
set(:board_label) { create(:label, project: board_parent) }
set(:board) { create(:board, project: board_parent, lists: [dev_list, test_list]) }
- it_behaves_like 'group and project boards', "/projects/:id/boards"
+ it_behaves_like 'group and project boards', "/projects/:id/boards"
describe "POST /projects/:id/boards/lists" do
let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}/lists" }
diff --git a/spec/requests/api/container_registry_spec.rb b/spec/requests/api/container_registry_spec.rb
new file mode 100644
index 00000000000..ea035a8be4a
--- /dev/null
+++ b/spec/requests/api/container_registry_spec.rb
@@ -0,0 +1,224 @@
+require 'spec_helper'
+
+describe API::ContainerRegistry do
+ set(:project) { create(:project, :private) }
+ set(:maintainer) { create(:user) }
+ set(:developer) { create(:user) }
+ set(:reporter) { create(:user) }
+ set(:guest) { create(:user) }
+
+ let(:root_repository) { create(:container_repository, :root, project: project) }
+ let(:test_repository) { create(:container_repository, project: project) }
+
+ let(:api_user) { maintainer }
+
+ before do
+ project.add_maintainer(maintainer)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ project.add_guest(guest)
+
+ stub_feature_flags(container_registry_api: true)
+ stub_container_registry_config(enabled: true)
+
+ root_repository
+ test_repository
+ end
+
+ shared_examples 'being disallowed' do |param|
+ context "for #{param}" do
+ let(:api_user) { public_send(param) }
+
+ it 'returns access denied' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context "for anonymous" do
+ let(:api_user) { nil }
+
+ it 'returns not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/registry/repositories' do
+ subject { get api("/projects/#{project.id}/registry/repositories", api_user) }
+
+ it_behaves_like 'being disallowed', :guest
+
+ context 'for reporter' do
+ let(:api_user) { reporter }
+
+ it 'returns a list of repositories' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
+ root_repository.id, test_repository.id)
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/repositories')
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/registry/repositories/:repository_id' do
+ subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}", api_user) }
+
+ it_behaves_like 'being disallowed', :developer
+
+ context 'for maintainer' do
+ let(:api_user) { maintainer }
+
+ it 'schedules removal of repository' do
+ expect(DeleteContainerRepositoryWorker).to receive(:perform_async)
+ .with(maintainer.id, root_repository.id)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/registry/repositories/:repository_id/tags' do
+ subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user) }
+
+ it_behaves_like 'being disallowed', :guest
+
+ context 'for reporter' do
+ let(:api_user) { reporter }
+
+ before do
+ stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest))
+ end
+
+ it 'returns a list of tags' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |repository| repository['name'] }).to eq %w(latest rootA)
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/tags')
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags' do
+ subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user), params: params }
+
+ it_behaves_like 'being disallowed', :developer do
+ let(:params) do
+ { name_regex: 'v10.*' }
+ end
+ end
+
+ context 'for maintainer' do
+ let(:api_user) { maintainer }
+
+ context 'without required parameters' do
+ let(:params) { }
+
+ it 'returns bad request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'passes all declared parameters' do
+ let(:params) do
+ { name_regex: 'v10.*',
+ keep_n: 100,
+ older_than: '1 day',
+ other: 'some value' }
+ end
+
+ let(:worker_params) do
+ { name_regex: 'v10.*',
+ keep_n: 100,
+ older_than: '1 day' }
+ end
+
+ it 'schedules cleanup of tags repository' do
+ expect(CleanupContainerRepositoryWorker).to receive(:perform_async)
+ .with(maintainer.id, root_repository.id, worker_params)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
+ subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
+
+ it_behaves_like 'being disallowed', :guest
+
+ context 'for reporter' do
+ let(:api_user) { reporter }
+
+ before do
+ stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
+ end
+
+ it 'returns a details of tag' do
+ subject
+
+ expect(json_response).to include(
+ 'name' => 'rootA',
+ 'digest' => 'sha256:4c8e63ca4cb663ce6c688cb06f1c372b088dac5b6d7ad7d49cd620d85cf72a15',
+ 'revision' => 'd7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac',
+ 'total_size' => 2319870)
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/tag')
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
+ subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
+
+ it_behaves_like 'being disallowed', :developer
+
+ context 'for maintainer' do
+ let(:api_user) { maintainer }
+
+ before do
+ stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
+ end
+
+ it 'properly removes tag' do
+ expect_any_instance_of(ContainerRegistry::Client)
+ .to receive(:delete_repository_tag).with(root_repository.path,
+ 'sha256:4c8e63ca4cb663ce6c688cb06f1c372b088dac5b6d7ad7d49cd620d85cf72a15')
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index ef34192f888..35c448d187d 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::Discussions do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, :repository, namespace: user.namespace) }
- let(:private_user) { create(:user) }
+ let(:private_user) { create(:user) }
before do
project.add_developer(user)
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 7d3eff7d32d..22a9e36ca31 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -129,6 +129,40 @@ describe API::Features do
end
end
+ context 'when enabling for a project by path' do
+ context 'when the project exists' do
+ let!(:project) { create(:project) }
+
+ it 'sets the feature gate' do
+ post api("/features/#{feature_name}", admin), params: { value: 'true', project: project.full_path }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'actors', 'value' => ["Project:#{project.id}"] }
+ ])
+ end
+ end
+
+ context 'when the project does not exist' do
+ it 'sets no new values' do
+ post api("/features/#{feature_name}", admin), params: { value: 'true', project: 'mep/to/the/mep/mep' }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq(
+ "name" => "my_feature",
+ "state" => "off",
+ "gates" => [
+ { "key" => "boolean", "value" => false }
+ ]
+ )
+ end
+ end
+ end
+
it 'creates a feature with the given percentage if passed an integer' do
post api("/features/#{feature_name}", admin), params: { value: '50' }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index a0aee937185..9b32dc78274 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -183,14 +183,15 @@ describe API::Files do
get api(url, current_user), params: params
expect(response).to have_gitlab_http_status(200)
+ expect(headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
- it 'forces attachment content disposition' do
+ it 'sets inline content disposition by default' do
url = route(file_path) + "/raw"
get api(url, current_user), params: params
- expect(headers['Content-Disposition']).to eq('attachment; filename="popen.rb"')
+ expect(headers['Content-Disposition']).to eq('inline; filename="popen.rb"')
end
context 'when mandatory params are not given' do
diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb
index 108721c6655..6980eb7f55d 100644
--- a/spec/requests/api/group_milestones_spec.rb
+++ b/spec/requests/api/group_milestones_spec.rb
@@ -8,7 +8,7 @@ describe API::GroupMilestones do
let!(:closed_milestone) { create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone') }
let!(:milestone) { create(:milestone, group: group, title: 'version2', description: 'open milestone') }
- it_behaves_like 'group and project milestones', "/groups/:id/milestones" do
+ it_behaves_like 'group and project milestones', "/groups/:id/milestones" do
let(:route) { "/groups/#{group.id}/milestones" }
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index c9dfc5c4a7e..7176bc23e34 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -382,6 +382,20 @@ describe API::Groups do
expect(response_project_ids(json_response, 'shared_projects'))
.to contain_exactly(projects[:public].id, projects[:internal].id)
end
+
+ it 'avoids N+1 queries' do
+ get api("/groups/#{group1.id}", admin)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/groups/#{group1.id}", admin)
+ end.count
+
+ create(:project, namespace: group1)
+
+ expect do
+ get api("/groups/#{group1.id}", admin)
+ end.not_to exceed_query_limit(control_count)
+ end
end
context "when authenticated as admin" do
diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb
new file mode 100644
index 00000000000..aceff9b4aa6
--- /dev/null
+++ b/spec/requests/api/import_github_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe API::ImportGithub do
+ include ApiHelpers
+
+ let(:token) { "asdasd12345" }
+ let(:provider) { :github }
+ let(:access_params) { { github_access_token: token } }
+
+ describe "POST /import/github" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:provider_username) { user.username }
+ let(:provider_user) { OpenStruct.new(login: provider_username) }
+ let(:provider_repo) do
+ OpenStruct.new(
+ name: 'vim',
+ full_name: "#{provider_username}/vim",
+ owner: OpenStruct.new(login: provider_username)
+ )
+ end
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repo: provider_repo).as_null_object)
+ end
+ end
+
+ it 'returns 201 response when the project is imported successfully' do
+ allow(Gitlab::LegacyGithubImport::ProjectCreator)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .and_return(double(execute: project))
+
+ post api("/import/github", user), params: {
+ target_namespace: user.namespace_path,
+ personal_access_token: token,
+ repo_id: 1234
+ }
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to be_a Hash
+ expect(json_response['name']).to eq(project.name)
+ end
+
+ it 'returns 422 response when user can not create projects in the chosen namespace' do
+ other_namespace = create(:group, name: 'other_namespace')
+
+ post api("/import/github", user), params: {
+ target_namespace: other_namespace.name,
+ personal_access_token: token,
+ repo_id: 1234
+ }
+
+ expect(response).to have_gitlab_http_status(422)
+ end
+ end
+end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 0fe63e2e517..6a943b5237a 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -32,7 +32,7 @@ describe API::Internal do
context 'broadcast message exists' do
let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) }
- it 'returns one broadcast message' do
+ it 'returns one broadcast message' do
get api('/internal/broadcast_message'), params: { secret_token: secret_token }
expect(response).to have_gitlab_http_status(200)
@@ -41,7 +41,7 @@ describe API::Internal do
end
context 'broadcast message does not exist' do
- it 'returns nothing' do
+ it 'returns nothing' do
get api('/internal/broadcast_message'), params: { secret_token: secret_token }
expect(response).to have_gitlab_http_status(200)
@@ -867,7 +867,7 @@ describe API::Internal do
context 'broadcast message exists' do
let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) }
- it 'returns one broadcast message' do
+ it 'returns one broadcast message' do
post api("/internal/post_receive"), params: valid_params
expect(response).to have_gitlab_http_status(200)
@@ -876,7 +876,7 @@ describe API::Internal do
end
context 'broadcast message does not exist' do
- it 'returns empty string' do
+ it 'returns empty string' do
post api("/internal/post_receive"), params: valid_params
expect(response).to have_gitlab_http_status(200)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 0fd465da4f8..e0f1e303e96 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -92,12 +92,10 @@ describe API::Issues do
end
context "when authenticated" do
- let(:first_issue) { json_response.first }
-
it "returns an array of issues" do
get api("/issues", user)
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([issue.id, closed_issue.id])
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.last).to have_key('web_url')
end
@@ -105,23 +103,19 @@ describe API::Issues do
it 'returns an array of closed issues' do
get api('/issues', user), params: { state: :closed }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an array of opened issues' do
get api('/issues', user), params: { state: :opened }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of all issues' do
get api('/issues', user), params: { state: :all }
- expect_paginated_array_response(size: 2)
- expect(first_issue['id']).to eq(issue.id)
- expect(json_response.second['id']).to eq(closed_issue.id)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns issues assigned to me' do
@@ -129,8 +123,7 @@ describe API::Issues do
get api('/issues', user2), params: { scope: 'assigned_to_me' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues assigned to me (kebab-case)' do
@@ -138,8 +131,7 @@ describe API::Issues do
get api('/issues', user2), params: { scope: 'assigned-to-me' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues authored by the given author id' do
@@ -147,8 +139,7 @@ describe API::Issues do
get api('/issues', user), params: { author_id: user2.id, scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues assigned to the given assignee id' do
@@ -156,8 +147,7 @@ describe API::Issues do
get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues authored by the given author id and assigned to the given assignee id' do
@@ -165,8 +155,7 @@ describe API::Issues do
get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues with no assignee' do
@@ -174,8 +163,7 @@ describe API::Issues do
get api('/issues', user), params: { assignee_id: 0, scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues with no assignee' do
@@ -183,8 +171,7 @@ describe API::Issues do
get api('/issues', user), params: { assignee_id: 'None', scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues with any assignee' do
@@ -193,18 +180,17 @@ describe API::Issues do
get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' }
- expect_paginated_array_response(size: 3)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns issues reacted by the authenticated user' do
issue2 = create(:issue, project: project, author: user, assignees: [user])
create(:award_emoji, awardable: issue2, user: user2, name: 'star')
-
create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup')
get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([issue2.id, issue.id])
end
it 'returns issues not reacted by the authenticated user' do
@@ -213,21 +199,19 @@ describe API::Issues do
get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns issues matching given search string for title' do
get api("/issues", user), params: { search: issue.title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns issues matching given search string for description' do
get api("/issues", user), params: { search: issue.description }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
context 'filtering before a specific date' do
@@ -236,15 +220,13 @@ describe API::Issues do
it 'returns issues created before a specific date' do
get api('/issues?created_before=2000-01-02T00:00:00.060Z', user)
- expect(json_response.size).to eq(1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues updated before a specific date' do
get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user)
- expect(json_response.size).to eq(1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
end
@@ -254,23 +236,21 @@ describe API::Issues do
it 'returns issues created after a specific date' do
get api("/issues?created_after=#{issue2.created_at}", user)
- expect(json_response.size).to eq(1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues updated after a specific date' do
get api("/issues?updated_after=#{issue2.updated_at}", user)
- expect(json_response.size).to eq(1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
end
it 'returns an array of labeled issues' do
get api("/issues", user), params: { labels: label.title }
- expect_paginated_array_response(size: 1)
- expect(first_issue['labels']).to eq([label.title])
+ expect_paginated_array_response(issue.id)
+ expect(json_response.first['labels']).to eq([label.title])
end
it 'returns an array of labeled issues when all labels matches' do
@@ -282,20 +262,20 @@ describe API::Issues do
get api("/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(issue.id)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
end
it 'returns an empty array if no issue matches labels' do
get api('/issues', user), params: { labels: 'foo,bar' }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of labeled issues matching given state' do
get api("/issues", user), params: { labels: label.title, state: :opened }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(issue.id)
expect(json_response.first['labels']).to eq([label.title])
expect(json_response.first['state']).to eq('opened')
end
@@ -303,112 +283,96 @@ describe API::Issues do
it 'returns an empty array if no issue matches labels and state filters' do
get api("/issues", user), params: { labels: label.title, state: :closed }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of issues with any label' do
get api("/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of issues with no label' do
get api("/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ 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(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an empty array if no issue matches milestone' do
get api("/issues?milestone=#{empty_milestone.title}", user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if milestone does not exist' do
get api("/issues?milestone=foo", user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of issues in given milestone' do
get api("/issues?milestone=#{milestone.title}", user)
- expect_paginated_array_response(size: 2)
- expect(json_response.first['id']).to eq(issue.id)
- expect(json_response.second['id']).to eq(closed_issue.id)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns an array of issues matching state in milestone' do
get api("/issues?milestone=#{milestone.title}"\
'&state=closed', user)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an array of issues with no milestone' do
get api("/issues?milestone=#{no_milestone_title}", author)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(confidential_issue.id)
+ expect_paginated_array_response(confidential_issue.id)
end
it 'returns an array of issues found by iids' do
get api('/issues', user), params: { iids: [closed_issue.iid] }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an empty array if iid does not exist' do
get api("/issues", user), params: { iids: [99999] }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'sorts by created_at descending by default' do
get api('/issues', user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'sorts ascending when requested' do
get api('/issues?sort=asc', user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([closed_issue.id, issue.id])
end
it 'sorts by updated_at descending when requested' do
get api('/issues?order_by=updated_at', user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ issue.touch(:updated_at)
- expect_paginated_array_response(size: 2)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'sorts by updated_at ascending when requested' do
get api('/issues?order_by=updated_at&sort=asc', user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ issue.touch(:updated_at)
- expect_paginated_array_response(size: 2)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([closed_issue.id, issue.id])
end
it 'matches V4 response schema' do
@@ -430,7 +394,8 @@ describe API::Issues do
project: group_project,
state: :closed,
milestone: group_milestone,
- updated_at: 3.hours.ago
+ updated_at: 3.hours.ago,
+ created_at: 1.day.ago
end
let!(:group_confidential_issue) do
create :issue,
@@ -438,7 +403,8 @@ describe API::Issues do
project: group_project,
author: author,
assignees: [assignee],
- updated_at: 2.hours.ago
+ updated_at: 2.hours.ago,
+ created_at: 2.days.ago
end
let!(:group_issue) do
create :issue,
@@ -448,7 +414,8 @@ describe API::Issues do
milestone: group_milestone,
updated_at: 1.hour.ago,
title: issue_title,
- description: issue_description
+ description: issue_description,
+ created_at: 5.days.ago
end
let!(:group_label) do
create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
@@ -479,10 +446,7 @@ describe API::Issues do
it 'also returns subgroups projects issues' do
get api(base_url, user)
- issue_ids = json_response.map { |issue| issue['id'] }
-
- expect_paginated_array_response(size: 5)
- expect(issue_ids).to include(issue_1.id, issue_2.id)
+ expect_paginated_array_response([issue_2.id, issue_1.id, group_closed_issue.id, group_confidential_issue.id, group_issue.id])
end
end
@@ -490,7 +454,7 @@ describe API::Issues do
it 'lists all issues in public projects' do
get api(base_url)
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_closed_issue.id, group_issue.id])
end
end
@@ -502,65 +466,62 @@ describe API::Issues do
it 'returns all group issues (including opened and closed)' do
get api(base_url, admin)
- expect_paginated_array_response(size: 3)
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
end
it 'returns group issues without confidential issues for non project members' do
get api(base_url, non_member), params: { state: :opened }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['title']).to eq(group_issue.title)
+ expect_paginated_array_response(group_issue.id)
end
it 'returns group confidential issues for author' do
get api(base_url, author), params: { state: :opened }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end
it 'returns group confidential issues for assignee' do
get api(base_url, assignee), params: { state: :opened }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end
it 'returns group issues with confidential issues for project members' do
get api(base_url, user), params: { state: :opened }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end
it 'returns group confidential issues for admin' do
get api(base_url, admin), params: { state: :opened }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end
it 'returns an array of labeled group issues' do
get api(base_url, user), params: { labels: group_label.title }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(group_issue.id)
expect(json_response.first['labels']).to eq([group_label.title])
end
it 'returns an array of labeled group issues where all labels match' do
get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns issues matching given search string for title' do
get api(base_url, user), params: { search: group_issue.title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_issue.id)
+ expect_paginated_array_response(group_issue.id)
end
it 'returns issues matching given search string for description' do
get api(base_url, user), params: { search: group_issue.description }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_issue.id)
+ expect_paginated_array_response(group_issue.id)
end
it 'returns an array of labeled issues when all labels matches' do
@@ -572,69 +533,64 @@ describe API::Issues do
get api(base_url, user), params: { labels: "#{group_label.title},#{label_b.title},#{label_c.title}" }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(group_issue.id)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
end
it 'returns an array of issues found by iids' do
get api(base_url, user), params: { iids: [group_issue.iid] }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(group_issue.id)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an empty array if iid does not exist' do
get api(base_url, user), params: { iids: [99999] }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if no group issue matches labels' do
get api(base_url, user), params: { labels: 'foo,bar' }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of group issues with any label' do
get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(group_issue.id)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an array of group issues with no label' do
get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE }
- response_ids = json_response.map { |issue| issue['id'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_ids).to contain_exactly(group_closed_issue.id, group_confidential_issue.id)
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id])
end
it 'returns an empty array if no issue matches milestone' do
get api(base_url, user), params: { milestone: group_empty_milestone.title }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if milestone does not exist' do
get api(base_url, user), params: { milestone: 'foo' }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of issues in given milestone' do
get api(base_url, user), params: { state: :opened, milestone: group_milestone.title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_issue.id)
+ expect_paginated_array_response(group_issue.id)
end
it 'returns an array of issues matching state in milestone' do
get api(base_url, user), params: { milestone: group_milestone.title, state: :closed }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_closed_issue.id)
+ expect_paginated_array_response(group_closed_issue.id)
end
it 'returns an array of issues with no milestone' do
@@ -642,44 +598,33 @@ describe API::Issues do
expect(response).to have_gitlab_http_status(200)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_confidential_issue.id)
+ expect_paginated_array_response(group_confidential_issue.id)
end
it 'sorts by created_at descending by default' do
get api(base_url, user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
end
it 'sorts ascending when requested' do
get api("#{base_url}?sort=asc", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
end
it 'sorts by updated_at descending when requested' do
get api("#{base_url}?order_by=updated_at", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ group_issue.touch(:updated_at)
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
end
it 'sorts by updated_at ascending when requested' do
get api(base_url, user), params: { order_by: :updated_at, sort: :asc }
- response_dates = json_response.map { |issue| issue['updated_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
end
end
end
@@ -691,8 +636,7 @@ describe API::Issues do
it 'returns public project issues' do
get api("/projects/#{project.id}/issues")
- expect_paginated_array_response(size: 2)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
end
@@ -731,56 +675,49 @@ describe API::Issues do
get api("/projects/#{restricted_project.id}/issues", non_member)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns project issues without confidential issues for non project members' do
get api("#{base_url}/issues", non_member)
- expect_paginated_array_response(size: 2)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns project issues without confidential issues for project members with guest role' do
get api("#{base_url}/issues", guest)
- expect_paginated_array_response(size: 2)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns project confidential issues for author' do
get api("#{base_url}/issues", author)
- expect_paginated_array_response(size: 3)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns project confidential issues for assignee' do
get api("#{base_url}/issues", assignee)
- expect_paginated_array_response(size: 3)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
- expect_paginated_array_response(size: 3)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns project confidential issues for admin' do
get api("#{base_url}/issues", admin)
- expect_paginated_array_response(size: 3)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns an array of labeled project issues' do
get api("#{base_url}/issues", user), params: { labels: label.title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['labels']).to eq([label.title])
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of labeled issues when all labels matches' do
@@ -792,142 +729,117 @@ describe API::Issues do
get api("#{base_url}/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
+ expect_paginated_array_response(issue.id)
end
it 'returns issues matching given search string for title' do
get api("#{base_url}/issues?search=#{issue.title}", user)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns issues matching given search string for description' do
get api("#{base_url}/issues?search=#{issue.description}", user)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of issues found by iids' do
get api("#{base_url}/issues", user), params: { iids: [issue.iid] }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an empty array if iid does not exist' do
get api("#{base_url}/issues", user), params: { iids: [99999] }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if not all labels matches' do
get api("#{base_url}/issues?labels=#{label.title},foo", user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of project issues with any label' do
get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of project issues with no label' do
get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
- response_ids = json_response.map { |issue| issue['id'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_ids).to contain_exactly(closed_issue.id, confidential_issue.id)
+ expect_paginated_array_response([confidential_issue.id, closed_issue.id])
end
it 'returns an empty array if no project issue matches labels' do
get api("#{base_url}/issues", user), params: { labels: 'foo,bar' }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if no issue matches milestone' do
get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if milestone does not exist' do
get api("#{base_url}/issues", user), params: { milestone: :foo }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of issues in given milestone' do
get api("#{base_url}/issues", user), params: { milestone: milestone.title }
- expect_paginated_array_response(size: 2)
- expect(json_response.first['id']).to eq(issue.id)
- expect(json_response.second['id']).to eq(closed_issue.id)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns an array of issues matching state in milestone' do
get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an array of issues with no milestone' do
get api("#{base_url}/issues", user), params: { milestone: no_milestone_title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(confidential_issue.id)
+ expect_paginated_array_response(confidential_issue.id)
end
it 'returns an array of issues with any milestone' do
get api("#{base_url}/issues", user), params: { milestone: any_milestone_title }
- response_ids = json_response.map { |issue| issue['id'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_ids).to contain_exactly(closed_issue.id, issue.id)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'sorts ascending when requested' do
get api("#{base_url}/issues", user), params: { sort: :asc }
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
end
it 'sorts by updated_at descending when requested' do
get api("#{base_url}/issues", user), params: { order_by: :updated_at }
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ issue.touch(:updated_at)
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'sorts by updated_at ascending when requested' do
get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc }
- response_dates = json_response.map { |issue| issue['updated_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
end
end
@@ -1081,7 +993,7 @@ describe API::Issues do
end
context 'user does not have permissions to create issue' do
- let(:not_member) { create(:user) }
+ let(:not_member) { create(:user) }
before do
project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE)
@@ -1828,21 +1740,21 @@ describe API::Issues do
it 'return public project issues' do
get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by")
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(merge_request.id)
end
end
it 'returns merge requests that will close issue on merge' do
get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user)
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(merge_request.id)
end
context 'when no merge requests will close issue' do
it 'returns empty array' do
get api("/projects/#{project.id}/issues/#{closed_issue.iid}/closed_by", user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
end
@@ -1878,7 +1790,7 @@ describe API::Issues do
it 'return list of referenced merge requests from issue' do
get_related_merge_requests(project.id, issue.iid)
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(related_mr.id)
end
it 'renders 404 if project is not visible' do
@@ -1902,15 +1814,14 @@ describe API::Issues do
get_related_merge_requests(project.id, issue.iid, user)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(related_mr.id)
+ expect_paginated_array_response(related_mr.id)
end
context 'no merge request mentioned a issue' do
it 'returns empty array' do
get_related_merge_requests(project.id, closed_issue.iid, user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
end
@@ -1948,13 +1859,6 @@ describe API::Issues do
end
end
- def expect_paginated_array_response(size: nil)
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(size) if size
- end
-
describe 'GET projects/:id/issues/:issue_iid/participants' do
it_behaves_like 'issuable participants endpoint' do
let(:entity) { issue }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index dd40f3d1561..4d42bc39ac3 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -10,7 +10,7 @@ describe API::MergeRequests do
let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) }
let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
let(:pipeline) { create(:ci_empty_pipeline) }
- let(:milestone1) { create(:milestone, title: '0.9', project: project) }
+ let(:milestone1) { create(:milestone, title: '0.9', project: project) }
let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
let!(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
@@ -698,7 +698,7 @@ describe API::MergeRequests do
let!(:user2) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let!(:forked_project) { fork_project(project, user2, repository: true) }
- let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
+ let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
before do
forked_project.add_reporter(user2)
@@ -1189,13 +1189,13 @@ describe API::MergeRequests do
end
it 'returns 404 if the merge request is not found' do
- post api("/projects/#{project.id}/merge_requests/123/merge_when_pipeline_succeeds", user)
+ post api("/projects/#{project.id}/merge_requests/123/cancel_merge_when_pipeline_succeeds", user)
expect(response).to have_gitlab_http_status(404)
end
it 'returns 404 if the merge request id is used instead of iid' do
- post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge_when_pipeline_succeeds", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/cancel_merge_when_pipeline_succeeds", user)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 9bf753fe049..424f0a82e43 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::Notes do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, namespace: user.namespace) }
- let(:private_user) { create(:user) }
+ let(:private_user) { create(:user) }
before do
project.add_reporter(user)
@@ -46,7 +46,7 @@ describe API::Notes do
create(:project, namespace: private_user.namespace)
.tap { |p| p.add_maintainer(private_user) }
end
- let(:private_issue) { create(:issue, project: private_project) }
+ let(:private_issue) { create(:issue, project: private_project) }
let(:ext_proj) { create(:project, :public) }
let(:ext_issue) { create(:issue, project: ext_proj) }
diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb
index d69c15b0477..c647537038e 100644
--- a/spec/requests/api/pages/private_access_spec.rb
+++ b/spec/requests/api/pages/private_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Private Project Pages Access" do
+describe "Private Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb
index 882ca26ac51..16cc5697f30 100644
--- a/spec/requests/api/pages/public_access_spec.rb
+++ b/spec/requests/api/pages/public_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Public Project Pages Access" do
+describe "Public Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index eb002de62a2..52599db9a9e 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -456,8 +456,8 @@ describe API::Pipelines do
expect(json_response['message']).to eq '404 Not found'
end
- it 'logs an audit event' do
- expect { delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner) }.to change { SecurityEvent.count }.by(1)
+ it 'does not log an audit event' do
+ expect { delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner) }.not_to change { SecurityEvent.count }
end
context 'when the pipeline has jobs' do
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
new file mode 100644
index 00000000000..9bab1f95150
--- /dev/null
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -0,0 +1,467 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::ProjectClusters do
+ include KubernetesHelpers
+
+ let(:current_user) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:project) { create(:project, :repository) }
+
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ describe 'GET /projects/:id/clusters' do
+ let!(:extra_cluster) { create(:cluster, :provided_by_gcp, :project) }
+
+ let!(:clusters) do
+ create_list(:cluster, 5, :provided_by_gcp, :project, :production_environment,
+ projects: [project])
+ end
+
+ context 'non-authorized user' do
+ it 'should respond with 404' do
+ get api("/projects/#{project.id}/clusters", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ get api("/projects/#{project.id}/clusters", current_user)
+ end
+
+ it 'should respond with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'should include pagination headers' do
+ expect(response).to include_pagination_headers
+ end
+
+ it 'should only include authorized clusters' do
+ cluster_ids = json_response.map { |cluster| cluster['id'] }
+
+ expect(cluster_ids).to match_array(clusters.pluck(:id))
+ expect(cluster_ids).not_to include(extra_cluster.id)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/clusters/:cluster_id' do
+ let(:cluster_id) { cluster.id }
+
+ let(:platform_kubernetes) do
+ create(:cluster_platform_kubernetes, :configured,
+ namespace: 'project-namespace')
+ end
+
+ let(:cluster) do
+ create(:cluster, :project, :provided_by_gcp,
+ platform_kubernetes: platform_kubernetes,
+ user: current_user,
+ projects: [project])
+ end
+
+ context 'non-authorized user' do
+ it 'should respond with 404' do
+ get api("/projects/#{project.id}/clusters/#{cluster_id}", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ get api("/projects/#{project.id}/clusters/#{cluster_id}", current_user)
+ end
+
+ it 'returns specific cluster' do
+ expect(json_response['id']).to eq(cluster.id)
+ end
+
+ it 'returns cluster information' do
+ expect(json_response['provider_type']).to eq('gcp')
+ expect(json_response['platform_type']).to eq('kubernetes')
+ expect(json_response['environment_scope']).to eq('*')
+ expect(json_response['cluster_type']).to eq('project_type')
+ end
+
+ it 'returns project information' do
+ cluster_project = json_response['project']
+
+ expect(cluster_project['id']).to eq(project.id)
+ expect(cluster_project['name']).to eq(project.name)
+ expect(cluster_project['path']).to eq(project.path)
+ end
+
+ it 'returns kubernetes platform information' do
+ platform = json_response['platform_kubernetes']
+
+ expect(platform['api_url']).to eq('https://kubernetes.example.com')
+ expect(platform['namespace']).to eq('project-namespace')
+ expect(platform['ca_cert']).to be_present
+ end
+
+ it 'returns user information' do
+ user = json_response['user']
+
+ expect(user['id']).to eq(current_user.id)
+ expect(user['username']).to eq(current_user.username)
+ end
+
+ it 'returns GCP provider information' do
+ gcp_provider = json_response['provider_gcp']
+
+ expect(gcp_provider['cluster_id']).to eq(cluster.id)
+ expect(gcp_provider['status_name']).to eq('created')
+ expect(gcp_provider['gcp_project_id']).to eq('test-gcp-project')
+ expect(gcp_provider['zone']).to eq('us-central1-a')
+ expect(gcp_provider['machine_type']).to eq('n1-standard-2')
+ expect(gcp_provider['num_nodes']).to eq(3)
+ expect(gcp_provider['endpoint']).to eq('111.111.111.111')
+ end
+
+ context 'when cluster has no provider' do
+ let(:cluster) do
+ create(:cluster, :project, :provided_by_user,
+ projects: [project])
+ end
+
+ it 'should not include GCP provider info' do
+ expect(json_response['provider_gcp']).not_to be_present
+ end
+ end
+
+ context 'with non-existing cluster' do
+ let(:cluster_id) { 123 }
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+
+ shared_context 'kubernetes calls stubbed' do
+ before do
+ stub_kubeclient_discover(api_url)
+ stub_kubeclient_get_namespace(api_url, namespace: namespace)
+ stub_kubeclient_get_service_account(api_url, "#{namespace}-service-account", namespace: namespace)
+ stub_kubeclient_put_service_account(api_url, "#{namespace}-service-account", namespace: namespace)
+
+ stub_kubeclient_get_secret(
+ api_url,
+ {
+ metadata_name: "#{namespace}-token",
+ token: Base64.encode64('sample-token'),
+ namespace: namespace
+ }
+ )
+
+ stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
+ stub_kubeclient_get_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
+ stub_kubeclient_put_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
+ end
+ end
+
+ describe 'POST /projects/:id/clusters/user' do
+ include_context 'kubernetes calls stubbed'
+
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:namespace) { project.path }
+ let(:authorization_type) { 'rbac' }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ namespace: namespace,
+ authorization_type: authorization_type
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'non-authorized user' do
+ it 'should respond with 404' do
+ post api("/projects/#{project.id}/clusters/user", non_member), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
+ end
+
+ context 'with valid params' do
+ it 'should respond with 201' do
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'should create a new Cluster::Cluster' do
+ cluster_result = Clusters::Cluster.find(json_response["id"])
+ platform_kubernetes = cluster_result.platform
+
+ expect(cluster_result).to be_user
+ expect(cluster_result).to be_kubernetes
+ expect(cluster_result.project).to eq(project)
+ expect(cluster_result.name).to eq('test-cluster')
+ expect(platform_kubernetes.rbac?).to be_truthy
+ expect(platform_kubernetes.api_url).to eq(api_url)
+ expect(platform_kubernetes.namespace).to eq(namespace)
+ expect(platform_kubernetes.token).to eq('sample-token')
+ end
+ end
+
+ context 'when user does not indicate authorization type' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ namespace: namespace
+ }
+ end
+
+ it 'defaults to RBAC' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform_kubernetes.rbac?).to be_truthy
+ end
+ end
+
+ context 'when user sets authorization type as ABAC' do
+ let(:authorization_type) { 'abac' }
+
+ it 'should create an ABAC cluster' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform.abac?).to be_truthy
+ end
+ end
+
+ context 'with invalid params' do
+ let(:namespace) { 'invalid_namespace' }
+
+ it 'should respond with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'should not create a new Clusters::Cluster' do
+ expect(project.reload.clusters).to be_empty
+ end
+
+ it 'should return validation errors' do
+ expect(json_response['message']['platform_kubernetes.namespace'].first).to be_present
+ end
+ end
+ end
+
+ context 'when user tries to add multiple clusters' do
+ before do
+ create(:cluster, :provided_by_gcp, :project,
+ projects: [project])
+
+ post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
+ end
+
+ it 'should respond with 403' do
+ expect(response).to have_gitlab_http_status(403)
+ end
+
+ it 'should return an appropriate message' do
+ expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters')
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/clusters/:cluster_id' do
+ include_context 'kubernetes calls stubbed'
+
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:namespace) { 'new-namespace' }
+ let(:platform_kubernetes_attributes) { { namespace: namespace } }
+
+ let(:update_params) do
+ {
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ let!(:kubernetes_namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ project: project)
+ end
+
+ let(:cluster) do
+ create(:cluster, :project, :provided_by_gcp,
+ projects: [project])
+ end
+
+ context 'non-authorized user' do
+ it 'should respond with 404' do
+ put api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: update_params
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ put api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: update_params
+
+ cluster.reload
+ end
+
+ context 'with valid params' do
+ it 'should respond with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'should update cluster attributes' do
+ expect(cluster.platform_kubernetes.namespace).to eq('new-namespace')
+ expect(cluster.kubernetes_namespace.namespace).to eq('new-namespace')
+ end
+ end
+
+ context 'with invalid params' do
+ let(:namespace) { 'invalid_namespace' }
+
+ it 'should respond with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'should not update cluster attributes' do
+ expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace')
+ expect(cluster.kubernetes_namespace.namespace).not_to eq('invalid_namespace')
+ end
+
+ it 'should return validation errors' do
+ expect(json_response['message']['platform_kubernetes.namespace'].first).to match('can contain only lowercase letters')
+ end
+ end
+
+ context 'with a GCP cluster' do
+ context 'when user tries to change GCP specific fields' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: 'https://new-api-url.com',
+ token: 'new-sample-token'
+ }
+ end
+
+ it 'should respond with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'should return validation error' do
+ expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster')
+ end
+ end
+
+ context 'when user tries to change namespace' do
+ let(:namespace) { 'new-namespace' }
+
+ it 'should respond with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+
+ context 'with an user cluster' do
+ let(:api_url) { 'https://new-api-url.com' }
+
+ let(:cluster) do
+ create(:cluster, :project, :provided_by_user,
+ projects: [project])
+ end
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ namespace: 'new-namespace',
+ token: 'new-sample-token'
+ }
+ end
+
+ let(:update_params) do
+ {
+ name: 'new-name',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ it 'should respond with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'should update platform kubernetes attributes' do
+ platform_kubernetes = cluster.platform_kubernetes
+
+ expect(cluster.name).to eq('new-name')
+ expect(platform_kubernetes.namespace).to eq('new-namespace')
+ expect(platform_kubernetes.api_url).to eq('https://new-api-url.com')
+ expect(platform_kubernetes.token).to eq('new-sample-token')
+ end
+ end
+
+ context 'with a cluster that does not belong to user' do
+ let(:cluster) { create(:cluster, :project, :provided_by_user) }
+
+ it 'should respond with 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/clusters/:cluster_id' do
+ let(:cluster_params) { { cluster_id: cluster.id } }
+
+ let(:cluster) do
+ create(:cluster, :project, :provided_by_gcp,
+ projects: [project])
+ end
+
+ context 'non-authorized user' do
+ it 'should respond with 404' do
+ delete api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ delete api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: cluster_params
+ end
+
+ it 'should respond with 204' do
+ expect(response).to have_gitlab_http_status(204)
+ end
+
+ it 'should delete the cluster' do
+ expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy
+ end
+
+ context 'with a cluster that does not belong to user' do
+ let(:cluster) { create(:cluster, :project, :provided_by_user) }
+
+ it 'should respond with 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
index 0fa13dd71e2..49b5dfb0b33 100644
--- a/spec/requests/api/project_milestones_spec.rb
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -10,7 +10,7 @@ describe API::ProjectMilestones do
project.add_developer(user)
end
- it_behaves_like 'group and project milestones', "/projects/:id/milestones" do
+ it_behaves_like 'group and project milestones', "/projects/:id/milestones" do
let(:route) { "/projects/#{project.id}/milestones" }
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index ffe4512fa6f..7248908b494 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -948,6 +948,7 @@ describe API::Projects do
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_full_path']).to eq(group.full_path)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]['expires_at']).to be_nil
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
@@ -967,6 +968,7 @@ describe API::Projects do
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_full_path']).to eq(group.full_path)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]['expires_at']).to eq(expires_at.to_s)
end
@@ -1145,6 +1147,40 @@ describe API::Projects do
.to eq(Gitlab::Access::OWNER)
end
end
+
+ context 'nested group project', :nested_groups do
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+ let(:project2) { create(:project, group: nested_group) }
+
+ before do
+ project2.group.parent.add_owner(user)
+ end
+
+ it 'sets group access and return 200' do
+ get api("/projects/#{project2.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['permissions']['project_access']).to be_nil
+ expect(json_response['permissions']['group_access']['access_level'])
+ .to eq(Gitlab::Access::OWNER)
+ end
+
+ context 'with various access levels across nested groups' do
+ before do
+ project2.group.add_maintainer(user)
+ end
+
+ it 'sets the maximum group access and return 200' do
+ get api("/projects/#{project2.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['permissions']['project_access']).to be_nil
+ expect(json_response['permissions']['group_access']['access_level'])
+ .to eq(Gitlab::Access::OWNER)
+ end
+ end
+ end
end
end
end
diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb
new file mode 100644
index 00000000000..ba948e37e2f
--- /dev/null
+++ b/spec/requests/api/release/links_spec.rb
@@ -0,0 +1,359 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Release::Links do
+ let(:project) { create(:project, :repository, :private) }
+ let(:maintainer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:non_project_member) { create(:user) }
+ let(:commit) { create(:commit, project: project) }
+
+ let!(:release) do
+ create(:release,
+ project: project,
+ tag: 'v0.1',
+ author: maintainer)
+ end
+
+ before do
+ project.add_maintainer(maintainer)
+ project.add_reporter(reporter)
+
+ project.repository.add_tag(maintainer, 'v0.1', commit.id)
+ end
+
+ describe 'GET /projects/:id/releases/:tag_name/assets/links' do
+ context 'when there are two release links' do
+ let!(:release_link_1) { create(:release_link, release: release, created_at: 2.days.ago) }
+ let!(:release_link_2) { create(:release_link, release: release, created_at: 1.day.ago) }
+
+ it 'returns 200 HTTP status' do
+ get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns release links ordered by created_at' do
+ get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer)
+
+ expect(json_response.count).to eq(2)
+ expect(json_response.first['name']).to eq(release_link_2.name)
+ expect(json_response.second['name']).to eq(release_link_1.name)
+ end
+
+ it 'matches response schema' do
+ get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer)
+
+ expect(response).to match_response_schema('release/links')
+ end
+ end
+
+ context 'when release does not exist' do
+ let!(:release) { }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer) }
+ let(:message) { '404 Not found' }
+ end
+ end
+
+ context 'when user is not a project member' do
+ it_behaves_like '404 response' do
+ let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member) }
+ let(:message) { '404 Project Not Found' }
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'allows the request' do
+ get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/releases/:tag_name/assets/links/:link_id' do
+ let!(:release_link) { create(:release_link, release: release) }
+
+ it 'returns 200 HTTP status' do
+ get api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns a link entry' do
+ get api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
+
+ expect(json_response['name']).to eq(release_link.name)
+ expect(json_response['url']).to eq(release_link.url)
+ end
+
+ it 'matches response schema' do
+ get api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
+
+ expect(response).to match_response_schema('release/link')
+ end
+
+ context 'when specified tag is not found in the project' do
+ it_behaves_like '404 response' do
+ let(:request) { get api("/projects/#{project.id}/releases/non_existing_tag/assets/links/#{release_link.id}", maintainer) }
+ end
+ end
+
+ context 'when user is not a project member' do
+ it_behaves_like '404 response' do
+ let(:request) { get api("/projects/#{project.id}/releases/non_existing_tag/assets/links/#{release_link.id}", non_project_member) }
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'allows the request' do
+ get api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/releases/:tag_name/assets/links' do
+ let(:params) do
+ {
+ name: 'awesome-app.dmg',
+ url: 'https://example.com/download/awesome-app.dmg'
+ }
+ end
+
+ it 'accepts the request' do
+ post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'creates a new release' do
+ expect do
+ post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params
+ end.to change { Releases::Link.count }.by(1)
+
+ release.reload
+ expect(release.links.last.name).to eq('awesome-app.dmg')
+ expect(release.links.last.url).to eq('https://example.com/download/awesome-app.dmg')
+ end
+
+ it 'matches response schema' do
+ post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer), params: params
+
+ expect(response).to match_response_schema('release/link')
+ end
+
+ context 'when name is empty' do
+ let(:params) do
+ {
+ name: '',
+ url: 'https://example.com/download/awesome-app.dmg'
+ }
+ end
+
+ it_behaves_like '400 response' do
+ let(:request) do
+ post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer),
+ params: params
+ end
+ end
+ end
+
+ context 'when user is a reporter' do
+ it_behaves_like '403 response' do
+ let(:request) do
+ post api("/projects/#{project.id}/releases/v0.1/assets/links", reporter),
+ params: params
+ end
+ end
+ end
+
+ context 'when user is not a project member' do
+ it 'forbids the request' do
+ post api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member),
+ params: params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'forbids the request' do
+ post api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member),
+ params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'when the same link already exists' do
+ before do
+ create(:release_link,
+ release: release,
+ name: 'awesome-app.dmg',
+ url: 'https://example.com/download/awesome-app.dmg')
+ end
+
+ it_behaves_like '400 response' do
+ let(:request) do
+ post api("/projects/#{project.id}/releases/v0.1/assets/links", maintainer),
+ params: params
+ end
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/releases/:tag_name/assets/links/:link_id' do
+ let(:params) { { name: 'awesome-app.msi' } }
+ let!(:release_link) { create(:release_link, release: release) }
+
+ it 'accepts the request' do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
+ params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'updates the name' do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
+ params: params
+
+ expect(json_response['name']).to eq('awesome-app.msi')
+ end
+
+ it 'does not update the url' do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
+ params: params
+
+ expect(json_response['url']).to eq(release_link.url)
+ end
+
+ it 'matches response schema' do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
+ params: params
+
+ expect(response).to match_response_schema('release/link')
+ end
+
+ context 'when params is empty' do
+ let(:params) { {} }
+
+ it 'does not allow the request' do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
+ params: params
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when there are no corresponding release link' do
+ let!(:release_link) { }
+
+ it_behaves_like '404 response' do
+ let(:request) do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/1", maintainer),
+ params: params
+ end
+ end
+ end
+
+ context 'when user is a reporter' do
+ it_behaves_like '403 response' do
+ let(:request) do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", reporter),
+ params: params
+ end
+ end
+ end
+
+ context 'when user is not a project member' do
+ it_behaves_like '404 response' do
+ let(:request) do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member),
+ params: params
+ end
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it_behaves_like '403 response' do
+ let(:request) do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member),
+ params: params
+ end
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/releases/:tag_name/assets/links/:link_id' do
+ let!(:release_link) do
+ create(:release_link, release: release)
+ end
+
+ it 'accepts the request' do
+ delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'destroys the release link' do
+ expect do
+ delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
+ end.to change { Releases::Link.count }.by(-1)
+ end
+
+ it 'matches response schema' do
+ delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
+
+ expect(response).to match_response_schema('release/link')
+ end
+
+ context 'when there are no corresponding release link' do
+ let!(:release_link) { }
+
+ it_behaves_like '404 response' do
+ let(:request) do
+ delete api("/projects/#{project.id}/releases/v0.1/assets/links/1", maintainer)
+ end
+ end
+ end
+
+ context 'when user is a reporter' do
+ it_behaves_like '403 response' do
+ let(:request) do
+ delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", reporter)
+ end
+ end
+ end
+
+ context 'when user is not a project member' do
+ it_behaves_like '404 response' do
+ let(:request) do
+ delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member)
+ end
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it_behaves_like '403 response' do
+ let(:request) do
+ delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", non_project_member)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index a44e37f9cd5..811e23fb854 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -83,18 +83,6 @@ describe API::Releases do
end
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(releases_page: false)
- end
-
- it 'cannot find the API' do
- get api("/projects/#{project.id}/releases", maintainer)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
end
describe 'GET /projects/:id/releases/:tag_name' do
@@ -121,6 +109,7 @@ describe API::Releases do
expect(json_response['description']).to eq('This is v0.1')
expect(json_response['author']['name']).to eq(maintainer.name)
expect(json_response['commit']['id']).to eq(commit.id)
+ expect(json_response['assets']['count']).to eq(4)
end
it 'matches response schema' do
@@ -128,6 +117,53 @@ describe API::Releases do
expect(response).to match_response_schema('release')
end
+
+ it 'contains source information as assets' do
+ get api("/projects/#{project.id}/releases/v0.1", maintainer)
+
+ expect(json_response['assets']['sources'].map { |h| h['format'] })
+ .to match_array(release.sources.map(&:format))
+ expect(json_response['assets']['sources'].map { |h| h['url'] })
+ .to match_array(release.sources.map(&:url))
+ end
+
+ context 'when release has link asset' do
+ let!(:link) do
+ create(:release_link,
+ release: release,
+ name: 'release-18.04.dmg',
+ url: url)
+ end
+
+ let(:url) { 'https://my-external-hosting.example.com/scrambled-url/app.zip' }
+
+ it 'contains link information as assets' do
+ get api("/projects/#{project.id}/releases/v0.1", maintainer)
+
+ expect(json_response['assets']['links'].count).to eq(1)
+ expect(json_response['assets']['links'].first['id']).to eq(link.id)
+ expect(json_response['assets']['links'].first['name'])
+ .to eq('release-18.04.dmg')
+ expect(json_response['assets']['links'].first['url'])
+ .to eq('https://my-external-hosting.example.com/scrambled-url/app.zip')
+ expect(json_response['assets']['links'].first['external'])
+ .to be_truthy
+ end
+
+ context 'when link is internal' do
+ let(:url) do
+ "#{project.web_url}/-/jobs/artifacts/v11.6.0-rc4/download?" \
+ "job=rspec-mysql+41%2F50"
+ end
+
+ it 'has external false' do
+ get api("/projects/#{project.id}/releases/v0.1", maintainer)
+
+ expect(json_response['assets']['links'].first['external'])
+ .to be_falsy
+ end
+ end
+ end
end
context 'when specified tag is not found in the project' do
@@ -157,18 +193,6 @@ describe API::Releases do
end
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(releases_page: false)
- end
-
- it 'cannot find the API' do
- get api("/projects/#{project.id}/releases/v0.1", maintainer)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
end
describe 'POST /projects/:id/releases' do
@@ -254,6 +278,90 @@ describe API::Releases do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
+
+ context 'when create assets altogether' do
+ let(:base_params) do
+ {
+ name: 'New release',
+ tag_name: 'v0.1',
+ description: 'Super nice release'
+ }
+ end
+
+ context 'when create one asset' do
+ let(:params) do
+ base_params.merge({
+ assets: {
+ links: [{ name: 'beta', url: 'https://dosuken.example.com/inspection.exe' }]
+ }
+ })
+ end
+
+ it 'accepts the request' do
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'creates an asset with specified parameters' do
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+
+ expect(json_response['assets']['links'].count).to eq(1)
+ expect(json_response['assets']['links'].first['name']).to eq('beta')
+ expect(json_response['assets']['links'].first['url'])
+ .to eq('https://dosuken.example.com/inspection.exe')
+ end
+
+ it 'matches response schema' do
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+
+ expect(response).to match_response_schema('release')
+ end
+ end
+
+ context 'when create two assets' do
+ let(:params) do
+ base_params.merge({
+ assets: {
+ links: [
+ { name: 'alpha', url: 'https://dosuken.example.com/alpha.exe' },
+ { name: 'beta', url: 'https://dosuken.example.com/beta.exe' }
+ ]
+ }
+ })
+ end
+
+ it 'creates two assets with specified parameters' do
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+
+ expect(json_response['assets']['links'].count).to eq(2)
+ expect(json_response['assets']['links'].map { |h| h['name'] })
+ .to match_array(%w[alpha beta])
+ expect(json_response['assets']['links'].map { |h| h['url'] })
+ .to match_array(%w[https://dosuken.example.com/alpha.exe
+ https://dosuken.example.com/beta.exe])
+ end
+
+ context 'when link names are duplicates' do
+ let(:params) do
+ base_params.merge({
+ assets: {
+ links: [
+ { name: 'alpha', url: 'https://dosuken.example.com/alpha.exe' },
+ { name: 'alpha', url: 'https://dosuken.example.com/beta.exe' }
+ ]
+ }
+ })
+ end
+
+ it 'recognizes as a bad request' do
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+ end
end
context 'when tag does not exist in git repository' do
@@ -326,18 +434,6 @@ describe API::Releases do
expect(response).to have_gitlab_http_status(:conflict)
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(releases_page: false)
- end
-
- it 'cannot find the API' do
- post api("/projects/#{project.id}/releases", maintainer), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
end
describe 'PUT /projects/:id/releases/:tag_name' do
@@ -433,19 +529,6 @@ describe API::Releases do
end
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(releases_page: false)
- end
-
- it 'cannot find the API' do
- put api("/projects/#{project.id}/releases/v0.1", non_project_member),
- params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
end
describe 'DELETE /projects/:id/releases/:tag_name' do
@@ -516,17 +599,5 @@ describe API::Releases do
end
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(releases_page: false)
- end
-
- it 'cannot find the API' do
- delete api("/projects/#{project.id}/releases/v0.1", non_project_member)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index b6b57803a6a..0adc95cfbeb 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -166,12 +166,13 @@ describe API::Repositories do
get api(route, current_user)
expect(response).to have_gitlab_http_status(200)
+ expect(headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
- it 'forces attachment content disposition' do
+ it 'sets inline content disposition by default' do
get api(route, current_user)
- expect(headers['Content-Disposition']).to eq 'attachment'
+ expect(headers['Content-Disposition']).to eq 'inline'
end
context 'when sha does not exist' do
diff --git a/spec/requests/api/resource_label_events_spec.rb b/spec/requests/api/resource_label_events_spec.rb
index b7d4a5152cc..37b46eaeb86 100644
--- a/spec/requests/api/resource_label_events_spec.rb
+++ b/spec/requests/api/resource_label_events_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe API::ResourceLabelEvents do
set(:user) { create(:user) }
set(:project) { create(:project, :public, :repository, namespace: user.namespace) }
- set(:private_user) { create(:user) }
+ set(:private_user) { create(:user) }
before do
project.add_developer(user)
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 2f322cc7054..ed0108c846a 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -210,8 +210,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it "sets the runner's ip_address" do
post api('/runners'),
- params: { token: registration_token },
- headers: { 'REMOTE_ADDR' => '123.111.123.111' }
+ params: { token: registration_token },
+ headers: { 'X-Forwarded-For' => '123.111.123.111' }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.ip_address).to eq('123.111.123.111')
@@ -287,7 +287,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:job) do
create(:ci_build, :artifacts, :extended_options,
- pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0, commands: "ls\ndate")
+ pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
end
describe 'POST /api/v4/jobs/request' do
@@ -422,7 +422,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let(:expected_steps) do
[{ 'name' => 'script',
- 'script' => %w(ls date),
+ 'script' => %w(echo),
'timeout' => job.metadata_timeout,
'when' => 'on_success',
'allow_failure' => false },
@@ -520,7 +520,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it "sets the runner's ip_address" do
post api('/jobs/request'),
params: { token: runner.token },
- headers: { 'User-Agent' => user_agent, 'REMOTE_ADDR' => '123.222.123.222' }
+ headers: { 'User-Agent' => user_agent, 'X-Forwarded-For' => '123.222.123.222' }
expect(response).to have_gitlab_http_status 201
expect(runner.reload.ip_address).to eq('123.222.123.222')
@@ -588,7 +588,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let!(:test_job) do
create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'deploy',
stage: 'deploy', stage_idx: 1,
- options: { dependencies: [job2.name] })
+ options: { script: ['bash'], dependencies: [job2.name] })
end
before do
@@ -612,7 +612,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let!(:empty_dependencies_job) do
create(:ci_build, pipeline: pipeline, token: 'test-job-token', name: 'empty_dependencies_job',
stage: 'deploy', stage_idx: 1,
- options: { dependencies: [] })
+ options: { script: ['bash'], dependencies: [] })
end
before do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index cfbda63bb30..45fb1562e84 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -63,7 +63,8 @@ describe API::Settings, 'Settings' do
terms: 'Hello world!',
performance_bar_allowed_group_path: group.full_path,
instance_statistics_visibility_private: true,
- diff_max_patch_bytes: 150_000
+ diff_max_patch_bytes: 150_000,
+ default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE
}
expect(response).to have_gitlab_http_status(200)
@@ -88,6 +89,7 @@ describe API::Settings, 'Settings' do
expect(json_response['performance_bar_allowed_group_id']).to eq(group.id)
expect(json_response['instance_statistics_visibility_private']).to be(true)
expect(json_response['diff_max_patch_bytes']).to eq(150_000)
+ expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
end
end
diff --git a/spec/requests/api/submodules_spec.rb b/spec/requests/api/submodules_spec.rb
index c482a85c68f..064392fb185 100644
--- a/spec/requests/api/submodules_spec.rb
+++ b/spec/requests/api/submodules_spec.rb
@@ -64,7 +64,7 @@ describe API::Submodules do
expect(response).to have_gitlab_http_status(400)
end
- it 'returns the commmit' do
+ it 'returns the commit' do
head_commit = project.repository.commit.id
put api(route(submodule), user), params: params
@@ -81,7 +81,7 @@ describe API::Submodules do
let(:branch) { 'submodule_inside_folder' }
let(:encoded_submodule) { CGI.escape(submodule) }
- it 'returns the commmit' do
+ it 'returns the commit' do
expect(Submodules::UpdateService)
.to receive(:new)
.with(any_args, hash_including(submodule: submodule))
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index d09b6fe72b1..fffe878ddbd 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -54,6 +54,18 @@ describe API::Tags do
end
end
+ context 'searching' do
+ it 'only returns searched tags' do
+ get api("#{route}", user), params: { search: 'v1.1.0' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response[0]['name']).to eq('v1.1.0')
+ end
+ end
+
shared_examples_for 'repository tags' do
it 'returns the repository tags' do
get api(route, current_user)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index f3431e0be3d..89151021f90 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1785,7 +1785,7 @@ describe API::Users do
end
describe 'POST /users/:id/unblock' do
- let(:blocked_user) { create(:user, state: 'blocked') }
+ let(:blocked_user) { create(:user, state: 'blocked') }
before do
admin
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index f5092e8e2b5..6109829aad1 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -22,7 +22,7 @@ describe API::Wikis do
context 'when wiki has pages' do
let!(:pages) do
[create(:wiki_page, wiki: project_wiki, attrs: { title: 'page1', content: 'content of page1' }),
- create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2', content: 'content of page2' })]
+ create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2.with.dot', content: 'content of page2' })]
end
it 'returns the list of wiki pages without content' do
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 939e870ec53..5b625fd47be 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -387,7 +387,7 @@ describe 'Git HTTP requests' do
it "responds with status 401" do
expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4')
+ allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return('1.2.3.4')
clone_get(path, env)
@@ -548,7 +548,7 @@ describe 'Git HTTP requests' do
maxretry = options[:maxretry] - 1
ip = '1.2.3.4'
- allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
+ allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return(ip)
Rack::Attack::Allow2Ban.reset(ip, options)
maxretry.times.each do
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index 28cb90e450e..5b7b3d2fdd6 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Git LFS File Locking API' do
include WorkhorseHelpers
- let(:project) { create(:project) }
+ let(:project) { create(:project) }
let(:maintainer) { create(:user) }
let(:developer) { create(:user) }
let(:guest) { create(:user) }
@@ -132,6 +132,17 @@ describe 'Git LFS File Locking API' do
expect(json_response['lock'].keys).to match_array(%w(id path locked_at owner))
end
+
+ context 'when a maintainer uses force' do
+ let(:authorization) { authorize_user(maintainer) }
+
+ it 'deletes the lock' do
+ project.add_maintainer(maintainer)
+ post_lfs_json url, { force: true }, headers
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
end
end
@@ -149,7 +160,7 @@ describe 'Git LFS File Locking API' do
post(url, params: body.try(:to_json), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end
- def do_get(url, params = nil, headers = nil)
+ def do_get(url, params = nil, headers = nil)
get(url, params: (params || {}), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index bcc3e3a2678..49412b628b3 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'cycle analytics events' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, public_builds: false) }
- let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
+ let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
describe 'GET /:namespace/:project/cycle_analytics/events/issues' do
before do
diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb
index 284a51fcc32..75b22b1879b 100644
--- a/spec/requests/request_profiler_spec.rb
+++ b/spec/requests/request_profiler_spec.rb
@@ -18,7 +18,7 @@ describe 'Request Profiler' do
path = "/#{project.full_path}"
Timecop.freeze(time) do
- get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token }
+ get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token }
end
profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}.html"
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 5c3b37ef11c..a0d01fc8263 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -122,6 +122,10 @@ describe 'project routing' do
route_to('projects#preview_markdown', namespace_id: 'gitlab', id: 'gitlabhq')
)
end
+
+ it 'to #resolve' do
+ expect(get('/projects/1')).to route_to('projects#resolve', id: '1')
+ end
end
# members_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/members(.:format) projects/autocomplete_sources#members
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index 08ffc3c3a53..0ff777388e5 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -19,6 +19,41 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
SOURCE
end
+ it 'does not flag the use of `prepend EEFoo` in the middle of a file' do
+ expect_no_offenses(<<~SOURCE)
+ class Foo
+ prepend EEFoo
+ end
+ SOURCE
+ end
+
+ it 'flags the use of `prepend EE::Foo::Bar` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend EE::Foo::Bar
+ ^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
+ it 'flags the use of `prepend(EE::Foo::Bar)` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend(EE::Foo::Bar)
+ ^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
+ it 'flags the use of `prepend EE::Foo::Bar::Baz` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend EE::Foo::Bar::Baz
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
it 'flags the use of `prepend ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
diff --git a/spec/serializers/container_repository_entity_spec.rb b/spec/serializers/container_repository_entity_spec.rb
index c589cd18f77..15466bce514 100644
--- a/spec/serializers/container_repository_entity_spec.rb
+++ b/spec/serializers/container_repository_entity_spec.rb
@@ -19,7 +19,7 @@ describe ContainerRepositoryEntity do
allow(request).to receive(:current_user).and_return(user)
end
- it 'exposes required informations' do
+ it 'exposes required informations' do
expect(subject).to include(:id, :path, :location, :tags_path)
end
diff --git a/spec/serializers/container_tag_entity_spec.rb b/spec/serializers/container_tag_entity_spec.rb
index 4beb50c70f8..ceb828a1cc5 100644
--- a/spec/serializers/container_tag_entity_spec.rb
+++ b/spec/serializers/container_tag_entity_spec.rb
@@ -16,7 +16,7 @@ describe ContainerTagEntity do
before do
stub_container_registry_config(enabled: true)
- stub_container_registry_tags(repository: /image/, tags: %w[test])
+ stub_container_registry_tags(repository: /image/, tags: %w[test], with_manifest: true)
allow(request).to receive(:project).and_return(project)
allow(request).to receive(:current_user).and_return(user)
end
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 8793a762f9d..cfa5414b40f 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -11,7 +11,7 @@ describe DeploymentEntity do
allow(request).to receive(:current_user).and_return(user)
end
- it 'exposes internal deployment id' do
+ it 'exposes internal deployment id' do
expect(subject).to include(:iid)
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index cf57776346a..79aa32b29bb 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -144,7 +144,7 @@ describe PipelineSerializer do
# pipeline. With the same ref this check is cached but if refs are
# different then there is an extra query per ref
# https://gitlab.com/gitlab-org/gitlab-ce/issues/46368
- expect(recorded.count).to be_within(2).of(34)
+ expect(recorded.count).to be_within(2).of(38)
expect(recorded.cached_count).to eq(0)
end
end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index daf5dfba6b1..a4a733eff77 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -17,7 +17,7 @@ describe ApplicationSettings::UpdateService do
describe 'updating terms' do
context 'when the passed terms are blank' do
- let(:params) { { terms: '' } }
+ let(:params) { { terms: '' } }
it 'does not create terms' do
expect { subject.execute }.not_to change { ApplicationSetting::Term.count }
@@ -25,7 +25,7 @@ describe ApplicationSettings::UpdateService do
end
context 'when passing terms' do
- let(:params) { { terms: 'Be nice! ' } }
+ let(:params) { { terms: 'Be nice! ' } }
it 'creates the terms' do
expect { subject.execute }.to change { ApplicationSetting::Term.count }.by(1)
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index 010679b5360..aaad29536af 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -12,7 +12,7 @@ describe Boards::Issues::ListService do
let(:bug) { create(:label, project: project, name: 'Bug') }
let(:development) { create(:label, project: project, name: 'Development') }
- let(:testing) { create(:label, project: project, name: 'Testing') }
+ let(:testing) { create(:label, project: project, name: 'Testing') }
let(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
let(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
@@ -63,7 +63,7 @@ describe Boards::Issues::ListService do
let(:bug) { create(:group_label, group: group, name: 'Bug') }
let(:development) { create(:group_label, group: group, name: 'Development') }
- let(:testing) { create(:group_label, group: group, name: 'Testing') }
+ let(:testing) { create(:group_label, group: group, name: 'Testing') }
let(:p1) { create(:group_label, title: 'P1', group: group) }
let(:p2) { create(:group_label, title: 'P2', group: group) }
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index dd0ad5f11bd..6020f0771e5 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -10,7 +10,7 @@ describe Boards::Issues::MoveService do
let(:bug) { create(:label, project: project, name: 'Bug') }
let(:development) { create(:label, project: project, name: 'Development') }
- let(:testing) { create(:label, project: project, name: 'Testing') }
+ let(:testing) { create(:label, project: project, name: 'Testing') }
let(:regression) { create(:label, project: project, name: 'Regression') }
let!(:list1) { create(:list, board: board1, label: development, position: 0) }
@@ -35,7 +35,7 @@ describe Boards::Issues::MoveService do
let(:bug) { create(:group_label, group: group, name: 'Bug') }
let(:development) { create(:group_label, group: group, name: 'Development') }
- let(:testing) { create(:group_label, group: group, name: 'Testing') }
+ let(:testing) { create(:group_label, group: group, name: 'Testing') }
let(:regression) { create(:group_label, group: group, name: 'Regression') }
let!(:list1) { create(:list, board: board1, label: development, position: 0) }
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 87b60387c52..8497e90bd8b 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -143,7 +143,8 @@ describe Ci::CreatePipelineService do
target_branch: "branch_1",
source_project: project)
- allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(false)
+ allow_any_instance_of(MergeRequest)
+ .to receive(:find_actual_head_pipeline) { }
execute_service
diff --git a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
new file mode 100644
index 00000000000..80d82ba3ac9
--- /dev/null
+++ b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared_state do
+ include ExclusiveLeaseHelpers
+
+ describe '.execute' do
+ subject { service.execute }
+
+ let(:service) { described_class.new }
+ let!(:artifact) { create(:ci_job_artifact, expire_at: 1.day.ago) }
+
+ it 'destroys expired job artifacts' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(-1)
+ end
+
+ context 'when artifact is not expired' do
+ let!(:artifact) { create(:ci_job_artifact, expire_at: 1.day.since) }
+
+ it 'does not destroy expired job artifacts' do
+ expect { subject }.not_to change { Ci::JobArtifact.count }
+ end
+ end
+
+ context 'when artifact is permanent' do
+ let!(:artifact) { create(:ci_job_artifact, expire_at: nil) }
+
+ it 'does not destroy expired job artifacts' do
+ expect { subject }.not_to change { Ci::JobArtifact.count }
+ end
+ end
+
+ context 'when failed to destroy artifact' do
+ before do
+ stub_const('Ci::DestroyExpiredJobArtifactsService::LOOP_LIMIT', 10)
+
+ allow_any_instance_of(Ci::JobArtifact)
+ .to receive(:destroy!)
+ .and_raise(ActiveRecord::RecordNotDestroyed)
+ end
+
+ it 'raises an exception and stop destroying' do
+ expect { subject }.to raise_error(ActiveRecord::RecordNotDestroyed)
+ end
+ end
+
+ 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 destroying' do
+ expect { subject }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
+ end
+ end
+
+ context 'when timeout happens' do
+ before do
+ stub_const('Ci::DestroyExpiredJobArtifactsService::LOOP_TIMEOUT', 1.second)
+ allow_any_instance_of(described_class).to receive(:destroy_batch) { true }
+ end
+
+ it 'returns false and does not continue destroying' do
+ is_expected.to be_falsy
+ end
+ end
+
+ context 'when loop reached loop limit' do
+ before do
+ stub_const('Ci::DestroyExpiredJobArtifactsService::LOOP_LIMIT', 1)
+ stub_const('Ci::DestroyExpiredJobArtifactsService::BATCH_SIZE', 1)
+ end
+
+ let!(:artifact) { create_list(:ci_job_artifact, 2, expire_at: 1.day.ago) }
+
+ it 'raises an error and does not continue destroying' do
+ is_expected.to be_falsy
+ end
+
+ it 'destroys one artifact' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(-1)
+ end
+ end
+
+ context 'when there are no artifacts' do
+ let!(:artifact) { }
+
+ it 'does not raise error' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when there are artifacts more than batch sizes' do
+ before do
+ stub_const('Ci::DestroyExpiredJobArtifactsService::BATCH_SIZE', 1)
+ end
+
+ let!(:artifact) { create_list(:ci_job_artifact, 2, expire_at: 1.day.ago) }
+
+ it 'destroys all expired artifacts' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(-2)
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb
index 097daf67feb..d896f990470 100644
--- a/spec/services/ci/destroy_pipeline_service_spec.rb
+++ b/spec/services/ci/destroy_pipeline_service_spec.rb
@@ -17,8 +17,8 @@ describe ::Ci::DestroyPipelineService do
expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
- it 'logs an audit event' do
- expect { subject }.to change { SecurityEvent.count }.by(1)
+ it 'does not log an audit event' do
+ expect { subject }.not_to change { SecurityEvent.count }
end
context 'when the pipeline has jobs' do
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index 538992b621e..6674d89518e 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -671,9 +671,9 @@ describe Ci::ProcessPipelineService, '#execute' do
context 'when builds with auto-retries are configured' do
before do
- create_build('build:1', stage_idx: 0, user: user, options: { retry: { max: 2 } })
+ create_build('build:1', stage_idx: 0, user: user, options: { script: 'aa', retry: 2 })
create_build('test:1', stage_idx: 1, user: user, when: :on_failure)
- create_build('test:2', stage_idx: 1, user: user, options: { retry: { max: 1 } })
+ create_build('test:2', stage_idx: 1, user: user, options: { script: 'aa', retry: 1 })
end
it 'automatically retries builds in a valid order' do
@@ -762,7 +762,7 @@ describe Ci::ProcessPipelineService, '#execute' do
end
def manual_actions
- pipeline.manual_actions(true)
+ pipeline.manual_actions.reload
end
def create_build(name, **opts)
@@ -770,7 +770,7 @@ describe Ci::ProcessPipelineService, '#execute' do
end
def delayed_options
- { when: 'delayed', options: { start_in: '1 minute' } }
+ { when: 'delayed', options: { script: %w(echo), start_in: '1 minute' } }
end
def unschedule
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 9d65ac15213..20181387612 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -460,7 +460,12 @@ module Ci
end
let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) }
- let!(:pending_job) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['test'] } ) }
+
+ let!(:pending_job) do
+ create(:ci_build, :pending,
+ pipeline: pipeline, stage_idx: 1,
+ options: { script: ["bash"], dependencies: ['test'] })
+ end
subject { execute(specific_runner) }
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index 55445e71539..75042b29bea 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -285,7 +285,7 @@ describe Ci::RetryPipelineService, '#execute' do
end
it 'allows to retry failed pipeline' do
- allow_any_instance_of(Project).to receive(:fetch_branch_allows_collaboration?).and_return(true)
+ allow_any_instance_of(Project).to receive(:branch_allows_collaboration?).and_return(true)
allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false)
service.execute(pipeline)
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
index fe785735fef..18f218fc236 100644
--- a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
@@ -27,6 +27,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
stub_kubeclient_get_secret_error(api_url, 'gitlab-token')
stub_kubeclient_create_secret(api_url)
+ stub_kubeclient_get_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
+ stub_kubeclient_put_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
stub_kubeclient_get_namespace(api_url, namespace: namespace)
stub_kubeclient_get_service_account_error(api_url, "#{namespace}-service-account", namespace: namespace)
stub_kubeclient_create_service_account(api_url, namespace: namespace)
diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb
new file mode 100644
index 00000000000..d9dab1d705c
--- /dev/null
+++ b/spec/services/error_tracking/list_issues_service_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ErrorTracking::ListIssuesService do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+
+ let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
+ let(:token) { 'test-token' }
+ let(:result) { subject.execute }
+
+ let(:error_tracking_setting) do
+ create(:project_error_tracking_setting, api_url: sentry_url, token: token, project: project)
+ end
+
+ subject { described_class.new(project, user) }
+
+ before do
+ expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting)
+
+ project.add_reporter(user)
+ end
+
+ describe '#execute' do
+ context 'with authorized user' do
+ context 'when list_sentry_issues returns issues' do
+ let(:issues) { [:list, :of, :issues] }
+
+ before do
+ expect(error_tracking_setting)
+ .to receive(:list_sentry_issues).and_return(issues: issues)
+ end
+
+ it 'returns the issues' do
+ expect(result).to eq(status: :success, issues: issues)
+ end
+ end
+
+ context 'when list_sentry_issues returns nil' do
+ before do
+ expect(error_tracking_setting)
+ .to receive(:list_sentry_issues).and_return(nil)
+ end
+
+ it 'result is not ready' do
+ expect(result).to eq(
+ status: :error, http_status: :no_content, message: 'not ready')
+ end
+ end
+ end
+
+ context 'with unauthorized user' do
+ let(:unauthorized_user) { create(:user) }
+
+ subject { described_class.new(project, unauthorized_user) }
+
+ it 'returns error' do
+ result = subject.execute
+
+ expect(result).to include(status: :error, message: 'access denied')
+ end
+ end
+
+ context 'with error tracking disabled' do
+ before do
+ error_tracking_setting.enabled = false
+ end
+
+ it 'raises error' do
+ result = subject.execute
+
+ expect(result).to include(status: :error, message: 'not enabled')
+ end
+ end
+ end
+
+ describe '#sentry_external_url' do
+ let(:external_url) { 'https://sentrytest.gitlab.com/sentry-org/sentry-project' }
+
+ it 'calls ErrorTracking::ProjectErrorTrackingSetting' do
+ expect(error_tracking_setting).to receive(:sentry_external_url).and_call_original
+
+ subject.external_url
+ end
+ end
+end
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 68e310b0506..443665c9959 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -144,7 +144,7 @@ describe EventCreateService do
it 'updates user last activity' do
expect { service.push(project, user, push_data) }
- .to change { user.last_activity_on }.to(Date.today)
+ .to change { user.last_activity_on }.to(Date.today)
end
it 'caches the last push event for the user' do
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 45ef26aebbd..e8fce951155 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -15,7 +15,7 @@ describe GitPushService, services: true do
end
describe 'with remote mirrors' do
- let(:project) { create(:project, :repository, :remote_mirror) }
+ let(:project) { create(:project, :repository, :remote_mirror) }
subject do
described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref)
@@ -547,7 +547,7 @@ describe GitPushService, services: true do
end
context "closing an issue" do
- let(:message) { "this is some work.\n\ncloses JIRA-1" }
+ let(:message) { "this is some work.\n\ncloses JIRA-1" }
let(:comment_body) do
{
body: "Issue solved with [#{closing_commit.id}|http://#{Gitlab.config.gitlab.host}/#{project.full_path}/commit/#{closing_commit.id}]."
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index c61c1ddcb3d..715b1168bfb 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -172,7 +172,7 @@ describe Issues::CreateService do
end
it 'removes assignee when user id is 0' do
- opts = { title: 'Title', description: 'Description', assignee_ids: [0] }
+ opts = { title: 'Title', description: 'Description', assignee_ids: [0] }
issue = described_class.new(project, user, opts).execute
diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb
new file mode 100644
index 00000000000..516a1137319
--- /dev/null
+++ b/spec/services/issues/import_csv_service_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Issues::ImportCsvService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ subject do
+ uploader = FileUploader.new(project)
+ uploader.store!(file)
+
+ described_class.new(user, project, uploader).execute
+ end
+
+ describe '#execute' do
+ context 'invalid file' do
+ let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }
+
+ it 'returns invalid file error' do
+ expect_any_instance_of(Notify).to receive(:import_issues_csv_email)
+
+ expect(subject[:success]).to eq(0)
+ expect(subject[:parse_error]).to eq(true)
+ end
+ end
+
+ context 'comma delimited file' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') }
+
+ it 'imports CSV without errors' do
+ expect_any_instance_of(Notify).to receive(:import_issues_csv_email)
+
+ expect(subject[:success]).to eq(3)
+ expect(subject[:error_lines]).to eq([])
+ expect(subject[:parse_error]).to eq(false)
+ end
+ end
+
+ context 'tab delimited file with error row' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_tab.csv') }
+
+ it 'imports CSV with some error rows' do
+ expect_any_instance_of(Notify).to receive(:import_issues_csv_email)
+
+ expect(subject[:success]).to eq(2)
+ expect(subject[:error_lines]).to eq([3])
+ expect(subject[:parse_error]).to eq(false)
+ end
+ end
+
+ context 'semicolon delimited file with CRLF' do
+ let(:file) { fixture_file_upload('spec/fixtures/csv_semicolon.csv') }
+
+ it 'imports CSV with a blank row' do
+ expect_any_instance_of(Notify).to receive(:import_issues_csv_email)
+
+ expect(subject[:success]).to eq(3)
+ expect(subject[:error_lines]).to eq([4])
+ expect(subject[:parse_error]).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb
index 97ba2742392..7af514a5bea 100644
--- a/spec/services/labels/find_or_create_service_spec.rb
+++ b/spec/services/labels/find_or_create_service_spec.rb
@@ -46,7 +46,7 @@ describe Labels::FindOrCreateService do
end
context 'when include_ancestor_groups is true' do
- let(:group) { create(:group, :nested) }
+ let(:group) { create(:group, :nested) }
let(:params) do
{
title: 'Audit',
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
index aa9aba6bdff..c4c7f33e36a 100644
--- a/spec/services/labels/promote_service_spec.rb
+++ b/spec/services/labels/promote_service_spec.rb
@@ -5,9 +5,9 @@ describe Labels::PromoteService do
let!(:user) { create(:user) }
context 'project without group' do
- let!(:project_1) { create(:project) }
+ let!(:project_1) { create(:project) }
- let!(:project_label_1_1) { create(:label, project: project_1) }
+ let!(:project_label_1_1) { create(:label, project: project_1) }
subject(:service) { described_class.new(project_1, user) }
diff --git a/spec/services/lfs/unlock_file_service_spec.rb b/spec/services/lfs/unlock_file_service_spec.rb
index 539417644db..fe42ca41633 100644
--- a/spec/services/lfs/unlock_file_service_spec.rb
+++ b/spec/services/lfs/unlock_file_service_spec.rb
@@ -62,7 +62,7 @@ describe Lfs::UnlockFileService do
context 'when forced' do
let(:developer) { create(:user) }
- let(:maintainer) { create(:user) }
+ let(:maintainer) { create(:user) }
before do
project.add_developer(developer)
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 5a3ecb1019b..4e64b0c9414 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -128,9 +128,9 @@ describe MergeRequests::CreateService do
end
context 'when head pipelines already exist for merge request source branch' do
- let(:sha) { project.commit(opts[:source_branch]).id }
- let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) }
- let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: sha) }
+ let(:shas) { project.repository.commits(opts[:source_branch], limit: 2).map(&:id) }
+ let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
+ let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[0]) }
let!(:pipeline_3) { create(:ci_pipeline, project: project, ref: "other_branch", project_id: project.id) }
before do
@@ -144,17 +144,30 @@ describe MergeRequests::CreateService do
it 'sets head pipeline' do
merge_request = service.execute
- expect(merge_request.head_pipeline).to eq(pipeline_2)
+ expect(merge_request.reload.head_pipeline).to eq(pipeline_2)
expect(merge_request).to be_persisted
end
- context 'when merge request head commit sha does not match pipeline sha' do
- it 'sets the head pipeline correctly' do
- pipeline_2.update(sha: 1234)
+ context 'when the new pipeline is associated with an old sha' do
+ let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[0]) }
+ let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
+ it 'sets an old pipeline with associated with the latest sha as the head pipeline' do
merge_request = service.execute
- expect(merge_request.head_pipeline).to eq(pipeline_1)
+ expect(merge_request.reload.head_pipeline).to eq(pipeline_1)
+ expect(merge_request).to be_persisted
+ end
+ end
+
+ context 'when there are no pipelines with the diff head sha' do
+ let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
+ let!(:pipeline_2) { create(:ci_pipeline, project: project, ref: opts[:source_branch], project_id: project.id, sha: shas[1]) }
+
+ it 'does not set the head pipeline' do
+ merge_request = service.execute
+
+ expect(merge_request.reload.head_pipeline).to be_nil
expect(merge_request).to be_persisted
end
end
@@ -288,7 +301,7 @@ describe MergeRequests::CreateService do
end
it 'removes assignee_id when user id is 0' do
- opts = { title: 'Title', description: 'Description', assignee_id: 0 }
+ opts = { title: 'Title', description: 'Description', assignee_id: 0 }
merge_request = described_class.new(project, user, opts).execute
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 1d9c75dedce..1169ed5f9f2 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -509,7 +509,7 @@ describe MergeRequests::RefreshService do
committed_date: Time.now
)
- allow_any_instance_of(MergeRequest).to receive(:commits).and_return([commit])
+ allow_any_instance_of(MergeRequest).to receive(:commits).and_return(CommitCollection.new(@project, [commit], 'feature'))
end
context 'when the merge request is sourced from the same project' do
diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb
index 59c08b30f9f..bc5366a3339 100644
--- a/spec/services/projects/after_rename_service_spec.rb
+++ b/spec/services/projects/after_rename_service_spec.rb
@@ -4,53 +4,45 @@ require 'spec_helper'
describe Projects::AfterRenameService do
let(:rugged_config) { rugged_repo(project.repository).config }
+ let(:legacy_storage) { Storage::LegacyProject.new(project) }
+ let(:hashed_storage) { Storage::HashedProject.new(project) }
+ let!(:path_before_rename) { project.path }
+ let!(:full_path_before_rename) { project.full_path }
+ let!(:path_after_rename) { "#{project.path}-renamed" }
+ let!(:full_path_after_rename) { "#{project.full_path}-renamed" }
describe '#execute' do
context 'using legacy storage' do
- let(:project) { create(:project, :repository, :legacy_storage) }
- let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:project) { create(:project, :repository, :wiki_repo, :legacy_storage) }
let(:project_storage) { project.send(:storage) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
before do
# Project#gitlab_shell returns a new instance of Gitlab::Shell on every
# call. This makes testing a bit easier.
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- allow(project)
- .to receive(:previous_changes)
- .and_return('path' => ['foo'])
-
- allow(project)
- .to receive(:path_was)
- .and_return('foo')
-
stub_feature_flags(skip_hashed_storage_upgrade: false)
end
it 'renames a repository' do
stub_container_registry_config(enabled: false)
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage, "#{project.namespace.full_path}/foo", "#{project.full_path}")
- .and_return(true)
-
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
- .and_return(true)
-
expect_any_instance_of(SystemHooksService)
.to receive(:execute_hooks_for)
.with(project, :rename)
expect_any_instance_of(Gitlab::UploadsTransfer)
.to receive(:rename_project)
- .with('foo', project.path, project.namespace.full_path)
+ .with(path_before_rename, path_after_rename, project.namespace.full_path)
- expect(project).to receive(:expire_caches_before_rename)
+ expect_repository_exist("#{full_path_before_rename}.git")
+ expect_repository_exist("#{full_path_before_rename}.wiki.git")
- described_class.new(project).execute
+ service_execute
+
+ expect_repository_exist("#{full_path_after_rename}.git")
+ expect_repository_exist("#{full_path_after_rename}.wiki.git")
end
context 'container registry with images' do
@@ -63,8 +55,7 @@ describe Projects::AfterRenameService do
end
it 'raises a RenameFailedError' do
- expect { described_class.new(project).execute }
- .to raise_error(described_class::RenameFailedError)
+ expect { service_execute }.to raise_error(described_class::RenameFailedError)
end
end
@@ -76,7 +67,7 @@ describe Projects::AfterRenameService do
it 'moves pages folder to new location' do
expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
- described_class.new(project).execute
+ service_execute
end
end
@@ -88,14 +79,12 @@ describe Projects::AfterRenameService do
it 'moves uploads folder to new location' do
expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project)
- described_class.new(project).execute
+ service_execute
end
end
it 'updates project full path in .git/config' do
- allow(project_storage).to receive(:rename_repo).and_return(true)
-
- described_class.new(project).execute
+ service_execute
expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
end
@@ -103,13 +92,25 @@ describe Projects::AfterRenameService do
it 'updates storage location' do
allow(project_storage).to receive(:rename_repo).and_return(true)
- described_class.new(project).execute
+ service_execute
expect(project.project_repository).to have_attributes(
disk_path: project.disk_path,
shard_name: project.repository_storage
)
end
+
+ context 'with hashed storage upgrade when renaming enabled' do
+ it 'calls HashedStorageMigrationService with correct options' do
+ stub_application_setting(hashed_storage_enabled: true)
+
+ expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service|
+ expect(service).to receive(:execute).and_return(true)
+ end
+
+ service_execute
+ end
+ end
end
context 'using hashed storage' do
@@ -123,25 +124,11 @@ describe Projects::AfterRenameService do
# Project#gitlab_shell returns a new instance of Gitlab::Shell on every
# call. This makes testing a bit easier.
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
stub_feature_flags(skip_hashed_storage_upgrade: false)
stub_application_setting(hashed_storage_enabled: true)
end
- context 'migration to hashed storage' do
- it 'calls HashedStorageMigrationService with correct options' do
- project = create(:project, :repository, :legacy_storage)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
-
- expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service|
- expect(service).to receive(:execute).and_return(true)
- end
-
- described_class.new(project).execute
- end
- end
-
it 'renames a repository' do
stub_container_registry_config(enabled: false)
@@ -153,7 +140,7 @@ describe Projects::AfterRenameService do
expect(project).to receive(:expire_caches_before_rename)
- described_class.new(project).execute
+ service_execute
end
context 'container registry with images' do
@@ -166,7 +153,7 @@ describe Projects::AfterRenameService do
end
it 'raises a RenameFailedError' do
- expect { described_class.new(project).execute }
+ expect { service_execute }
.to raise_error(described_class::RenameFailedError)
end
end
@@ -175,38 +162,46 @@ describe Projects::AfterRenameService do
it 'moves pages folder to new location' do
expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
- described_class.new(project).execute
+ service_execute
end
end
context 'attachments' do
+ let(:uploader) { create(:upload, :issuable_upload, :with_file, model: project) }
+ let(:file_uploader) { build(:file_uploader, project: project) }
+ let(:legacy_storage_path) { File.join(file_uploader.root, legacy_storage.disk_path) }
+ let(:hashed_storage_path) { File.join(file_uploader.root, hashed_storage.disk_path) }
+
it 'keeps uploads folder location unchanged' do
expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project)
- described_class.new(project).execute
+ service_execute
end
context 'when not rolled out' do
let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) }
- it 'moves pages folder to hashed storage' do
- expect_next_instance_of(Projects::HashedStorage::MigrateAttachmentsService) do |service|
- expect(service).to receive(:execute)
- end
+ it 'moves attachments folder to hashed storage' do
+ expect(File.directory?(legacy_storage_path)).to be_truthy
+ expect(File.directory?(hashed_storage_path)).to be_falsey
- described_class.new(project).execute
+ service_execute
+ expect(project.reload.hashed_storage?(:attachments)).to be_truthy
+
+ expect(File.directory?(legacy_storage_path)).to be_falsey
+ expect(File.directory?(hashed_storage_path)).to be_truthy
end
end
end
it 'updates project full path in .git/config' do
- described_class.new(project).execute
+ service_execute
expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
end
it 'updates storage location' do
- described_class.new(project).execute
+ service_execute
expect(project.project_repository).to have_attributes(
disk_path: project.disk_path,
@@ -215,4 +210,21 @@ describe Projects::AfterRenameService do
end
end
end
+
+ def service_execute
+ # AfterRenameService is called by UpdateService after a successful model.update
+ # the initialization will include before and after paths values
+ project.update(path: path_after_rename)
+
+ described_class.new(project, path_before: path_before_rename, full_path_before: full_path_before_rename).execute
+ end
+
+ def expect_repository_exist(full_path_with_extension)
+ expect(
+ gitlab_shell.exists?(
+ project.repository_storage,
+ full_path_with_extension
+ )
+ ).to be_truthy
+ end
end
diff --git a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
new file mode 100644
index 00000000000..0659130bed2
--- /dev/null
+++ b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::ContainerRepository::CleanupTagsService do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :private) }
+ set(:repository) { create(:container_repository, :root, project: project) }
+
+ let(:service) { described_class.new(project, user, params) }
+
+ before do
+ project.add_maintainer(user)
+
+ stub_feature_flags(container_registry_cleanup: true)
+
+ stub_container_registry_config(enabled: true)
+
+ stub_container_registry_tags(
+ repository: repository.path,
+ tags: %w(latest A Ba Bb C D E))
+
+ stub_tag_digest('latest', 'sha256:configA')
+ stub_tag_digest('A', 'sha256:configA')
+ stub_tag_digest('Ba', 'sha256:configB')
+ stub_tag_digest('Bb', 'sha256:configB')
+ stub_tag_digest('C', 'sha256:configC')
+ stub_tag_digest('D', 'sha256:configD')
+ stub_tag_digest('E', nil)
+
+ stub_digest_config('sha256:configA', 1.hour.ago)
+ stub_digest_config('sha256:configB', 5.days.ago)
+ stub_digest_config('sha256:configC', 1.month.ago)
+ stub_digest_config('sha256:configD', nil)
+ end
+
+ describe '#execute' do
+ subject { service.execute(repository) }
+
+ context 'when no params are specified' do
+ let(:params) { {} }
+
+ it 'does not remove anything' do
+ expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag)
+
+ is_expected.to include(status: :success, deleted: [])
+ end
+ end
+
+ context 'when regex matching everything is specified' do
+ let(:params) do
+ { 'name_regex' => '.*' }
+ end
+
+ it 'does remove B* and C' do
+ # The :A cannot be removed as config is shared with :latest
+ # The :E cannot be removed as it does not have valid manifest
+
+ expect_delete('sha256:configB').twice
+ expect_delete('sha256:configC')
+ expect_delete('sha256:configD')
+
+ is_expected.to include(status: :success, deleted: %w(D Bb Ba C))
+ end
+ end
+
+ context 'when regex matching specific tags is used' do
+ let(:params) do
+ { 'name_regex' => 'C|D' }
+ end
+
+ it 'does remove C and D' do
+ expect_delete('sha256:configC')
+ expect_delete('sha256:configD')
+
+ is_expected.to include(status: :success, deleted: %w(D C))
+ end
+ end
+
+ context 'when removing a tagged image that is used by another tag' do
+ let(:params) do
+ { 'name_regex' => 'Ba' }
+ end
+
+ it 'does not remove the tag' do
+ # Issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/21405
+
+ is_expected.to include(status: :success, deleted: [])
+ end
+ end
+
+ context 'when removing keeping only 3' do
+ let(:params) do
+ { 'name_regex' => '.*',
+ 'keep_n' => 3 }
+ end
+
+ it 'does remove C as it is oldest' do
+ expect_delete('sha256:configC')
+
+ is_expected.to include(status: :success, deleted: %w(C))
+ end
+ end
+
+ context 'when removing older than 1 day' do
+ let(:params) do
+ { 'name_regex' => '.*',
+ 'older_than' => '1 day' }
+ end
+
+ it 'does remove B* and C as they are older than 1 day' do
+ expect_delete('sha256:configB').twice
+ expect_delete('sha256:configC')
+
+ is_expected.to include(status: :success, deleted: %w(Bb Ba C))
+ end
+ end
+
+ context 'when combining all parameters' do
+ let(:params) do
+ { 'name_regex' => '.*',
+ 'keep_n' => 1,
+ 'older_than' => '1 day' }
+ end
+
+ it 'does remove B* and C' do
+ expect_delete('sha256:configB').twice
+ expect_delete('sha256:configC')
+
+ is_expected.to include(status: :success, deleted: %w(Bb Ba C))
+ end
+ end
+ end
+
+ private
+
+ def stub_tag_digest(tag, digest)
+ allow_any_instance_of(ContainerRegistry::Client)
+ .to receive(:repository_tag_digest)
+ .with(repository.path, tag) { digest }
+
+ allow_any_instance_of(ContainerRegistry::Client)
+ .to receive(:repository_manifest)
+ .with(repository.path, tag) do
+ { 'config' => { 'digest' => digest } } if digest
+ end
+ end
+
+ def stub_digest_config(digest, created_at)
+ allow_any_instance_of(ContainerRegistry::Client)
+ .to receive(:blob)
+ .with(repository.path, digest, nil) do
+ { 'created' => created_at.to_datetime.rfc3339 }.to_json if created_at
+ end
+ end
+
+ def expect_delete(digest)
+ expect_any_instance_of(ContainerRegistry::Client)
+ .to receive(:delete_repository_tag)
+ .with(repository.path, digest)
+ end
+end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 12ddf8447bd..dfbdfa2ab69 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -281,6 +281,40 @@ describe Projects::DestroyService do
end
end
+ context 'repository +deleted path removal' do
+ def removal_path(path)
+ "#{path}+#{project.id}#{described_class::DELETED_FLAG}"
+ end
+
+ context 'regular phase' do
+ it 'schedules +deleted removal of existing repos' do
+ service = described_class.new(project, user, {})
+ allow(service).to receive(:schedule_stale_repos_removal)
+
+ expect(GitlabShellWorker).to receive(:perform_in)
+ .with(5.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path))
+
+ service.execute
+ end
+ end
+
+ context 'stale cleanup' do
+ let!(:async) { true }
+
+ it 'schedules +deleted wiki and repo removal' do
+ allow(ProjectDestroyWorker).to receive(:perform_async)
+
+ expect(GitlabShellWorker).to receive(:perform_in)
+ .with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path))
+
+ expect(GitlabShellWorker).to receive(:perform_in)
+ .with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.wiki.disk_path))
+
+ destroy_project(project, user, {})
+ end
+ end
+ end
+
context '#attempt_restore_repositories' do
let(:path) { project.disk_path + '.git' }
diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
index 0e82194e9ee..b720f37ffdb 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -15,6 +15,20 @@ describe Projects::HashedStorage::MigrateRepositoryService do
allow(service).to receive(:gitlab_shell) { gitlab_shell }
end
+ context 'repository lock' do
+ it 'tries to lock the repository' do
+ expect(service).to receive(:try_to_set_repository_read_only!)
+
+ service.execute
+ end
+
+ it 'fails when a git operation is in progress' do
+ allow(project).to receive(:repo_reference_count) { 1 }
+
+ expect { service.execute }.to raise_error(Projects::HashedStorage::RepositoryMigrationError)
+ end
+ end
+
context 'when succeeds' do
it 'renames project and wiki repositories' do
service.execute
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 95c9b6e63b8..fcc87196d5a 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
@@ -107,7 +107,7 @@ describe Projects::LfsPointers::LfsDownloadService do
end
end
- context 'when an lfs object with the same oid already exists' do
+ context 'when an lfs object with the same oid already exists' do
before do
create(:lfs_object, oid: 'oid')
end
diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb
new file mode 100644
index 00000000000..6afae3da80c
--- /dev/null
+++ b/spec/services/projects/operations/update_service_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::Operations::UpdateService do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+
+ let(:result) { subject.execute }
+
+ subject { described_class.new(project, user, params) }
+
+ describe '#execute' do
+ context 'error tracking' do
+ context 'with existing error tracking setting' do
+ let(:params) do
+ {
+ error_tracking_setting_attributes: {
+ enabled: false,
+ api_url: 'http://gitlab.com/api/0/projects/org/project',
+ token: 'token'
+ }
+ }
+ end
+
+ before do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ it 'updates the settings' do
+ expect(result[:status]).to eq(:success)
+
+ project.reload
+ expect(project.error_tracking_setting).not_to be_enabled
+ expect(project.error_tracking_setting.api_url).to eq('http://gitlab.com/api/0/projects/org/project')
+ expect(project.error_tracking_setting.token).to eq('token')
+ end
+ end
+
+ context 'without an existing error tracking setting' do
+ let(:params) do
+ {
+ error_tracking_setting_attributes: {
+ enabled: true,
+ api_url: 'http://gitlab.com/api/0/projects/org/project',
+ token: 'token'
+ }
+ }
+ end
+
+ it 'creates a setting' do
+ expect(result[:status]).to eq(:success)
+
+ expect(project.error_tracking_setting).to be_enabled
+ expect(project.error_tracking_setting.api_url).to eq('http://gitlab.com/api/0/projects/org/project')
+ expect(project.error_tracking_setting.token).to eq('token')
+ end
+ end
+
+ context 'with invalid parameters' do
+ let(:params) { {} }
+
+ let!(:error_tracking_setting) do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ it 'does nothing' do
+ expect(result[:status]).to eq(:success)
+ expect(project.reload.error_tracking_setting)
+ .to eq(error_tracking_setting)
+ end
+ end
+ end
+
+ context 'with inappropriate params' do
+ let(:params) { { name: '' } }
+
+ let!(:original_name) { project.name }
+
+ it 'ignores params' do
+ expect(result[:status]).to eq(:success)
+ expect(project.reload.name).to eq(original_name)
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/protect_default_branch_service_spec.rb b/spec/services/projects/protect_default_branch_service_spec.rb
new file mode 100644
index 00000000000..c145b2c06c6
--- /dev/null
+++ b/spec/services/projects/protect_default_branch_service_spec.rb
@@ -0,0 +1,242 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::ProtectDefaultBranchService do
+ let(:service) { described_class.new(project) }
+ let(:project) { instance_spy(Project) }
+
+ describe '#execute' do
+ before do
+ allow(service)
+ .to receive(:protect_default_branch)
+ end
+
+ context 'without a default branch' do
+ it 'does nothing' do
+ allow(service)
+ .to receive(:default_branch)
+ .and_return(nil)
+
+ service.execute
+
+ expect(service)
+ .not_to have_received(:protect_default_branch)
+ end
+ end
+
+ context 'with a default branch' do
+ it 'protects the default branch' do
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ service.execute
+
+ expect(service)
+ .to have_received(:protect_default_branch)
+ end
+ end
+ end
+
+ describe '#protect_default_branch' do
+ before do
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ allow(project)
+ .to receive(:change_head)
+ .with('master')
+
+ allow(service)
+ .to receive(:create_protected_branch)
+ end
+
+ context 'when branch protection is needed' do
+ before do
+ allow(service)
+ .to receive(:protect_branch?)
+ .and_return(true)
+
+ allow(service)
+ .to receive(:create_protected_branch)
+ end
+
+ it 'changes the HEAD of the project' do
+ service.protect_default_branch
+
+ expect(project)
+ .to have_received(:change_head)
+ end
+
+ it 'protects the default branch' do
+ service.protect_default_branch
+
+ expect(service)
+ .to have_received(:create_protected_branch)
+ end
+ end
+
+ context 'when branch protection is not needed' do
+ before do
+ allow(service)
+ .to receive(:protect_branch?)
+ .and_return(false)
+ end
+
+ it 'changes the HEAD of the project' do
+ service.protect_default_branch
+
+ expect(project)
+ .to have_received(:change_head)
+ end
+
+ it 'does not protect the default branch' do
+ service.protect_default_branch
+
+ expect(service)
+ .not_to have_received(:create_protected_branch)
+ end
+ end
+ end
+
+ describe '#create_protected_branch' do
+ it 'creates the protected branch' do
+ creator = instance_spy(User)
+ create_service = instance_spy(ProtectedBranches::CreateService)
+ access_level = Gitlab::Access::DEVELOPER
+ params = {
+ name: 'master',
+ push_access_levels_attributes: [{ access_level: access_level }],
+ merge_access_levels_attributes: [{ access_level: access_level }]
+ }
+
+ allow(project)
+ .to receive(:creator)
+ .and_return(creator)
+
+ allow(ProtectedBranches::CreateService)
+ .to receive(:new)
+ .with(project, creator, params)
+ .and_return(create_service)
+
+ allow(service)
+ .to receive(:push_access_level)
+ .and_return(access_level)
+
+ allow(service)
+ .to receive(:merge_access_level)
+ .and_return(access_level)
+
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ allow(create_service)
+ .to receive(:execute)
+ .with(skip_authorization: true)
+
+ service.create_protected_branch
+
+ expect(create_service)
+ .to have_received(:execute)
+ end
+ end
+
+ describe '#protect_branch?' do
+ context 'when default branch protection is disabled' do
+ it 'returns false' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_NONE)
+
+ expect(service.protect_branch?).to eq(false)
+ end
+ end
+
+ context 'when default branch protection is enabled' do
+ before do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+ end
+
+ it 'returns false if the branch is already protected' do
+ allow(ProtectedBranch)
+ .to receive(:protected?)
+ .with(project, 'master')
+ .and_return(true)
+
+ expect(service.protect_branch?).to eq(false)
+ end
+
+ it 'returns true if the branch is not yet protected' do
+ allow(ProtectedBranch)
+ .to receive(:protected?)
+ .with(project, 'master')
+ .and_return(false)
+
+ expect(service.protect_branch?).to eq(true)
+ end
+ end
+ end
+
+ describe '#default_branch' do
+ it 'returns the default branch of the project' do
+ allow(project)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ expect(service.default_branch).to eq('master')
+ end
+ end
+
+ describe '#push_access_level' do
+ context 'when developers can push' do
+ it 'returns the DEVELOPER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ expect(service.push_access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ context 'when developers can not push' do
+ it 'returns the MAINTAINER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ expect(service.push_access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+ end
+
+ describe '#merge_access_level' do
+ context 'when developers can merge' do
+ it 'returns the DEVELOPER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ expect(service.merge_access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ context 'when developers can not merge' do
+ it 'returns the MAINTAINER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ expect(service.merge_access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+ end
+end
diff --git a/spec/services/resource_events/change_labels_service_spec.rb b/spec/services/resource_events/change_labels_service_spec.rb
index 4c9138fb1ef..070964eb1ec 100644
--- a/spec/services/resource_events/change_labels_service_spec.rb
+++ b/spec/services/resource_events/change_labels_service_spec.rb
@@ -10,7 +10,7 @@ describe ResourceEvents::ChangeLabelsService do
describe '.change_labels' do
subject { described_class.new(resource, author).execute(added_labels: added, removed_labels: removed) }
- let(:labels) { create_list(:label, 2, project: project) }
+ let(:labels) { create_list(:label, 2, project: project) }
def expect_label_event(event, label, action)
expect(event.user).to eq(author)
diff --git a/spec/services/resource_events/merge_into_notes_service_spec.rb b/spec/services/resource_events/merge_into_notes_service_spec.rb
index c76f6e6f77e..14c43b46c15 100644
--- a/spec/services/resource_events/merge_into_notes_service_spec.rb
+++ b/spec/services/resource_events/merge_into_notes_service_spec.rb
@@ -16,8 +16,8 @@ describe ResourceEvents::MergeIntoNotesService do
create(:note_on_issue, opts.merge(params))
end
- set(:project) { create(:project) }
- set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
set(:resource) { create(:issue, project: project) }
set(:label) { create(:label, project: project) }
set(:label2) { create(:label, project: project) }
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index 3a483717756..e5ca1c155ed 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -117,13 +117,184 @@ describe Suggestions::ApplyService do
expect(commit.committer_email).to eq(user.commit_email)
expect(commit.author_name).to eq(user.name)
end
+
+ context 'when it fails to apply because the file was changed' do
+ it 'returns error message' do
+ service = instance_double(Files::UpdateService)
+
+ expect(Files::UpdateService).to receive(:new)
+ .and_return(service)
+
+ allow(service).to receive(:execute)
+ .and_raise(Files::UpdateService::FileChangedError)
+
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'The file has been changed', status: :error)
+ end
+ end
+
+ context 'when diff ref from position is different from repo diff refs' do
+ it 'returns error message' do
+ outdated_refs = Gitlab::Diff::DiffRefs.new(base_sha: 'foo', start_sha: 'bar', head_sha: 'outdated')
+
+ allow(suggestion).to receive(:appliable?) { true }
+ allow(suggestion.position).to receive(:diff_refs) { outdated_refs }
+
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'The file has been changed', status: :error)
+ end
+ end
+
+ context 'multiple suggestions applied' do
+ let(:expected_content) do
+ <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ # v1 change
+ end
+
+ path ||= Dir.pwd
+ # v1 change
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+ # v2 change
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ # v2 change
+
+ 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(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
+
+ def create_suggestion(diff, old_line: nil, new_line: nil, from_content:, to_content:, path:)
+ position = Gitlab::Diff::Position.new(old_path: path,
+ new_path: path,
+ old_line: old_line,
+ new_line: new_line,
+ diff_refs: diff.diff_refs)
+
+ suggestion_note = create(:diff_note_on_merge_request, noteable: merge_request,
+ original_position: position,
+ position: position,
+ project: project)
+ create(:suggestion, note: suggestion_note,
+ from_content: from_content,
+ to_content: to_content)
+ end
+
+ def apply_suggestion(suggestion)
+ suggestion.note.reload
+ merge_request.reload
+ merge_request.clear_memoized_shas
+
+ result = subject.execute(suggestion)
+ refresh = MergeRequests::RefreshService.new(project, user)
+ refresh.execute(merge_request.diff_head_sha,
+ suggestion.commit_id,
+ merge_request.source_branch_ref)
+
+ result
+ end
+
+ def fetch_raw_diff(suggestion)
+ project.reload.commit(suggestion.commit_id).diffs.diff_files.first.diff.diff
+ end
+
+ it 'applies multiple suggestions in subsequent versions correctly' do
+ diff = merge_request.merge_request_diff
+ path = 'files/ruby/popen.rb'
+
+ suggestion_1_changes = { old_line: nil,
+ new_line: 13,
+ from_content: "\n",
+ to_content: "# v1 change\n",
+ path: path }
+
+ suggestion_2_changes = { old_line: 24,
+ new_line: 31,
+ from_content: " @cmd_output << stderr.read\n",
+ to_content: "# v2 change\n",
+ path: path }
+
+ suggestion_1 = create_suggestion(diff, suggestion_1_changes)
+ suggestion_2 = create_suggestion(diff, suggestion_2_changes)
+
+ apply_suggestion(suggestion_1)
+
+ suggestion_1_diff = fetch_raw_diff(suggestion_1)
+
+ # rubocop: disable Layout/TrailingWhitespace
+ expected_suggestion_1_diff = <<-CONTENT.strip_heredoc
+ @@ -10,7 +10,7 @@ module Popen
+ end
+
+ path ||= Dir.pwd
+ -
+ +# v1 change
+ vars = {
+ "PWD" => path
+ }
+ CONTENT
+ # rubocop: enable Layout/TrailingWhitespace
+
+ apply_suggestion(suggestion_2)
+
+ suggestion_2_diff = fetch_raw_diff(suggestion_2)
+
+ # rubocop: disable Layout/TrailingWhitespace
+ expected_suggestion_2_diff = <<-CONTENT.strip_heredoc
+ @@ -28,7 +28,7 @@ module Popen
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ - @cmd_output << stderr.read
+ +# v2 change
+ @cmd_status = wait_thr.value.exitstatus
+ end
+ CONTENT
+ # rubocop: enable Layout/TrailingWhitespace
+
+ expect(suggestion_1_diff.strip).to eq(expected_suggestion_1_diff.strip)
+ expect(suggestion_2_diff.strip).to eq(expected_suggestion_2_diff.strip)
+ end
+ end
end
context 'fork-project' do
let(:project) { create(:project, :public, :repository) }
let(:forked_project) do
- fork_project_with_submodules(project, user)
+ fork_project_with_submodules(project, user, repository: project.repository)
end
let(:merge_request) do
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 0fbfcb34e50..82544ab0413 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -819,7 +819,7 @@ describe SystemNoteService do
end
context 'for issues' do
- let(:issue) { create(:issue, project: project) }
+ let(:issue) { create(:issue, project: project) }
it "creates comment" do
result = described_class.cross_reference(jira_issue, issue, author)
@@ -1131,7 +1131,7 @@ describe SystemNoteService do
end
context 'across different projects' do
- let(:other_project) { create(:project) }
+ let(:other_project) { create(:project) }
let(:canonical_issue) { create(:issue, project: other_project) }
it_behaves_like 'a system note' do
@@ -1156,7 +1156,7 @@ describe SystemNoteService do
end
context 'across different projects' do
- let(:other_project) { create(:project) }
+ let(:other_project) { create(:project) }
let(:duplicate_issue) { create(:issue, project: other_project) }
it_behaves_like 'a system note' do
@@ -1168,7 +1168,7 @@ describe SystemNoteService do
end
describe '.discussion_lock' do
- subject { described_class.discussion_lock(noteable, author) }
+ subject { described_class.discussion_lock(noteable, author) }
context 'discussion unlocked' do
it_behaves_like 'a system note' do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 253f2e44d10..8631f3f9a33 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -457,7 +457,7 @@ describe TodoService do
end
context 'on commit' do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository) }
it 'creates a todo for each valid mentioned user when leaving a note on commit' do
service.new_note(note_on_commit, john_doe)
diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb
index 8cb91e7c1b9..4b238280848 100644
--- a/spec/services/todos/destroy/entity_leave_service_spec.rb
+++ b/spec/services/todos/destroy/entity_leave_service_spec.rb
@@ -173,10 +173,10 @@ describe Todos::Destroy::EntityLeaveService do
let(:subproject) { create(:project, group: subgroup) }
let(:subproject2) { create(:project, group: subgroup2) }
- let!(:todo_subproject_user) { create(:todo, user: user, project: subproject) }
- let!(:todo_subproject2_user) { create(:todo, user: user, project: subproject2) }
- let!(:todo_subgroup_user) { create(:todo, user: user, group: subgroup) }
- let!(:todo_subgroup2_user) { create(:todo, user: user, group: subgroup2) }
+ let!(:todo_subproject_user) { create(:todo, user: user, project: subproject) }
+ let!(:todo_subproject2_user) { create(:todo, user: user, project: subproject2) }
+ let!(:todo_subgroup_user) { create(:todo, user: user, group: subgroup) }
+ let!(:todo_subgroup2_user) { create(:todo, user: user, group: subgroup2) }
let!(:todo_subproject_user2) { create(:todo, user: user2, project: subproject) }
let!(:todo_subpgroup_user2) { create(:todo, user: user2, group: subgroup) }
diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb
index 2f49b68f544..5cefbdd35ab 100644
--- a/spec/services/todos/destroy/group_private_service_spec.rb
+++ b/spec/services/todos/destroy/group_private_service_spec.rb
@@ -40,7 +40,7 @@ describe Todos::Destroy::GroupPrivateService do
let(:parent_member) { create(:user) }
let(:subgroup_member) { create(:user) }
- let(:subgproject_member) { create(:user) }
+ let(:subgproject_member) { create(:user) }
let!(:todo_parent_member) { create(:todo, user: parent_member, group: group) }
let!(:todo_subgroup_member) { create(:todo, user: subgroup_member, group: group) }
diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb
index 9b232a52efa..4a809d5bf18 100644
--- a/spec/services/upload_service_spec.rb
+++ b/spec/services/upload_service_spec.rb
@@ -63,11 +63,11 @@ describe UploadService do
@link_to_file = upload_file(@project, txt)
end
- it { expect(@link_to_file).to eq(nil) }
+ it { expect(@link_to_file).to eq({}) }
end
end
def upload_file(project, file)
- described_class.new(project, file, FileUploader).execute
+ described_class.new(project, file, FileUploader).execute.to_h
end
end
diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb
index ac3a8738cac..68b0f79c6d1 100644
--- a/spec/services/users/migrate_to_ghost_user_service_spec.rb
+++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb
@@ -6,7 +6,7 @@ describe Users::MigrateToGhostUserService do
let(:service) { described_class.new(user) }
context "migrating a user's associated records to the ghost user" do
- context 'issues' do
+ context 'issues' do
context 'deleted user is present as both author and edited_user' do
include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:author, :last_edited_by] do
let(:created_record) do
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index b1cc6d2eb83..5945a7dc0ad 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -61,7 +61,7 @@ describe WebHookService do
end
context 'when auth credentials are present' do
- let(:url) {'https://example.org'}
+ let(:url) {'https://example.org'}
let(:project_hook) { create(:project_hook, url: 'https://demo:demo@example.org/') }
it 'uses the credentials' do
@@ -76,7 +76,7 @@ describe WebHookService do
end
context 'when auth credentials are partial present' do
- let(:url) {'https://example.org'}
+ let(:url) {'https://example.org'}
let(:project_hook) { create(:project_hook, url: 'https://demo@example.org/') }
it 'uses the credentials anyways' do
diff --git a/spec/sidekiq/cron/job_gem_dependency_spec.rb b/spec/sidekiq/cron/job_gem_dependency_spec.rb
index 2e30cf025b0..2e7de75fd08 100644
--- a/spec/sidekiq/cron/job_gem_dependency_spec.rb
+++ b/spec/sidekiq/cron/job_gem_dependency_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Sidekiq::Cron::Job do
describe 'cron jobs' do
- context 'when rufus-scheduler depends on ZoTime or EoTime' do
+ context 'when Fugit depends on ZoTime or EoTime' do
before do
described_class
.create(name: 'TestCronWorker',
@@ -10,7 +10,7 @@ describe Sidekiq::Cron::Job do
class: Settings.cron_jobs[:pipeline_schedule_worker]['job_class'])
end
- it 'does not get "Rufus::Scheduler::ZoTime/EtOrbi::EoTime into an exact number"' do
+ it 'does not get any errors' do
expect { described_class.all.first.should_enque?(Time.now) }.not_to raise_error
end
end
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
index 25ddf932d42..82236bb4201 100644
--- a/spec/simplecov_env.rb
+++ b/spec/simplecov_env.rb
@@ -1,5 +1,6 @@
require 'simplecov'
require 'active_support/core_ext/numeric/time'
+require_relative '../lib/gitlab/utils'
module SimpleCovEnv
extend self
@@ -16,8 +17,9 @@ module SimpleCovEnv
def configure_job
SimpleCov.configure do
if ENV['CI_JOB_NAME']
- coverage_dir "coverage/#{ENV['CI_JOB_NAME']}"
- command_name ENV['CI_JOB_NAME']
+ job_name = Gitlab::Utils.slugify(ENV['CI_JOB_NAME'])
+ coverage_dir "coverage/#{job_name}"
+ command_name job_name
end
if ENV['CI']
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 89357056c93..72684caad32 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -22,7 +22,7 @@ if rspec_profiling_is_configured && (!ENV.key?('CI') || branch_can_be_profiled)
require 'rspec_profiling/rspec'
end
-if ENV['CI'] && !ENV['NO_KNAPSACK']
+if ENV['CI'] && ENV['KNAPSACK_GENERATE_REPORT'] && !ENV['NO_KNAPSACK']
require 'knapsack'
Knapsack::Adapters::RSpecAdapter.bind
end
@@ -127,6 +127,16 @@ RSpec.configure do |config|
.and_return(false)
end
+ config.before(:suite) do
+ # Set latest release blog post URL for "What's new?" link
+ Gitlab::ReleaseBlogPost.instance.instance_variable_set(:@url, 'https://about.gitlab.com')
+ end
+
+ config.before(:example, :quarantine) do
+ # Skip tests in quarantine unless we explicitly focus on them.
+ skip('In quarantine') unless config.inclusion_filter[:quarantine]
+ end
+
config.before(:example, :request_store) do
RequestStore.begin!
end
diff --git a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
index 4a946995f84..38e5fb155a4 100644
--- a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
+++ b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
@@ -5,7 +5,7 @@ shared_examples 'creating an issue for a discussion' do
expect(title_field.value).to include(merge_request.title)
end
- it 'has a mention of the discussion in the description' do
+ it 'has a mention of the discussion in the description' do
description_field = page.find_field('issue[description]')
expect(description_field.value).to include(discussion.first_note.note)
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index a57a3b2cf34..4a9ce9beb78 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -36,4 +36,11 @@ module ApiHelpers
full_path
end
+
+ def expect_paginated_array_response(items)
+ 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 { |item| item['id'] }).to eq(Array(items))
+ end
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index dd32ea3985f..ea3a03879c5 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -149,7 +149,7 @@ module GraphqlHelpers
# - List
# - String!
# - String
- field_type = field_type.of_type while field_type.respond_to?(:of_type)
+ field_type = field_type.of_type while field_type.respond_to?(:of_type)
field_type
end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index e7d97561bfc..6930b809048 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -20,6 +20,13 @@ module KubernetesHelpers
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_service_pods(response = nil)
+ stub_kubeclient_discover(service.api_url)
+ pods_url = service.api_url + "/api/v1/pods"
+
+ WebMock.stub_request(:get, pods_url).to_return(response || kube_pods_response)
+ end
+
def stub_kubeclient_pods(response = nil)
stub_kubeclient_discover(service.api_url)
pods_url = service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods"
@@ -212,6 +219,13 @@ module KubernetesHelpers
}
end
+ def kube_knative_pods_body(name, namespace)
+ {
+ "kind" => "PodList",
+ "items" => [kube_knative_pod(name: name, namespace: namespace)]
+ }
+ end
+
def kube_knative_services_body(**options)
{
"kind" => "List",
@@ -242,6 +256,28 @@ module KubernetesHelpers
}
end
+ # Similar to a kube_pod, but should contain a running service
+ def kube_knative_pod(name: "kube-pod", namespace: "default", status: "Running")
+ {
+ "metadata" => {
+ "name" => name,
+ "namespace" => namespace,
+ "generate_name" => "generated-name-with-suffix",
+ "creationTimestamp" => "2016-11-25T19:55:19Z",
+ "labels" => {
+ "serving.knative.dev/service" => name
+ }
+ },
+ "spec" => {
+ "containers" => [
+ { "name" => "container-0" },
+ { "name" => "container-1" }
+ ]
+ },
+ "status" => { "phase" => status }
+ }
+ end
+
def kube_deployment(name: "kube-deployment", app: "valid-deployment-label", track: nil)
{
"metadata" => {
@@ -265,10 +301,10 @@ module KubernetesHelpers
def kube_service(name: "kubetest", namespace: "default", domain: "example.com")
{
"metadata" => {
- "creationTimestamp" => "2018-11-21T06:16:33Z",
- "name" => name,
- "namespace" => namespace,
- "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}"
+ "creationTimestamp" => "2018-11-21T06:16:33Z",
+ "name" => name,
+ "namespace" => namespace,
+ "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}"
},
"spec" => {
"generation" => 2
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 87cfb6c04dc..3fee6872498 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -157,7 +157,7 @@ module LoginHelpers
env['omniauth.error.strategy'] = strategy
end
- def stub_omniauth_saml_config(messages, context: Rails.application)
+ def stub_omniauth_saml_config(messages, context: Rails.application)
set_devise_mapping(context: context)
routes = Rails.application.routes
routes.disable_clear_and_finalize = true
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index 2933f2c78dc..370d20df444 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -36,31 +36,41 @@ module StubGitlabCalls
.to receive(:full_access_token).and_return('token')
end
- def stub_container_registry_tags(repository: :any, tags:)
+ def stub_container_registry_tags(repository: :any, tags: [], with_manifest: false)
repository = any_args if repository == :any
allow_any_instance_of(ContainerRegistry::Client)
.to receive(:repository_tags).with(repository)
.and_return({ 'tags' => tags })
- allow_any_instance_of(ContainerRegistry::Client)
- .to receive(:repository_manifest).with(repository, anything)
- .and_return(stub_container_registry_tag_manifest)
+ if with_manifest
+ tags.each do |tag|
+ allow_any_instance_of(ContainerRegistry::Client)
+ .to receive(:repository_tag_digest)
+ .with(repository, tag)
+ .and_return('sha256:4c8e63ca4cb663ce6c688cb06f1c3' \
+ '72b088dac5b6d7ad7d49cd620d85cf72a15')
+ end
- allow_any_instance_of(ContainerRegistry::Client)
- .to receive(:blob).with(repository, anything, 'application/octet-stream')
- .and_return(stub_container_registry_blob)
+ allow_any_instance_of(ContainerRegistry::Client)
+ .to receive(:repository_manifest).with(repository, anything)
+ .and_return(stub_container_registry_tag_manifest_content)
+
+ allow_any_instance_of(ContainerRegistry::Client)
+ .to receive(:blob).with(repository, anything, 'application/octet-stream')
+ .and_return(stub_container_registry_blob_content)
+ end
end
private
- def stub_container_registry_tag_manifest
+ def stub_container_registry_tag_manifest_content
fixture_path = 'spec/fixtures/container_registry/tag_manifest.json'
JSON.parse(File.read(Rails.root + fixture_path))
end
- def stub_container_registry_blob
+ def stub_container_registry_blob_content
fixture_path = 'spec/fixtures/container_registry/config_blob.json'
File.read(Rails.root + fixture_path)
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index d352a7cdf1a..f485eb7b0eb 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -160,11 +160,12 @@ module TestEnv
def setup_gitaly
socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
gitaly_dir = File.dirname(socket_path)
+ install_gitaly_args = [gitaly_dir, repos_path, gitaly_url].compact.join(',')
component_timed_setup('Gitaly',
install_dir: gitaly_dir,
version: Gitlab::GitalyClient.expected_server_version,
- task: "gitlab:gitaly:install[#{gitaly_dir},#{repos_path}]") do
+ task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
start_gitaly(gitaly_dir)
@@ -215,6 +216,10 @@ module TestEnv
# The process can already be gone if the test run was INTerrupted.
end
+ def gitaly_url
+ ENV.fetch('GITALY_REPO_URL', nil)
+ end
+
def setup_factory_repo
setup_repo(factory_repo_path, factory_repo_path_bare, factory_repo_name,
BRANCH_SHA)
diff --git a/spec/support/migrations_helpers/cluster_helpers.rb b/spec/support/migrations_helpers/cluster_helpers.rb
new file mode 100644
index 00000000000..b54af15c29e
--- /dev/null
+++ b/spec/support/migrations_helpers/cluster_helpers.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module MigrationHelpers
+ module ClusterHelpers
+ # Creates a list of cluster projects.
+ def create_cluster_project_list(quantity)
+ group = namespaces_table.create(name: 'gitlab-org', path: 'gitlab-org')
+
+ quantity.times do |id|
+ create_cluster_project(group, id)
+ end
+ end
+
+ # Creates dependencies for a cluster project:
+ # - Group
+ # - Project
+ # - Cluster
+ # - Project - cluster relationship
+ # - GCP provider
+ # - Platform Kubernetes
+ def create_cluster_project(group, id)
+ project = projects_table.create!(
+ name: "project-#{id}",
+ path: "project-#{id}",
+ namespace_id: group.id
+ )
+
+ cluster = clusters_table.create(
+ name: 'test-cluster',
+ cluster_type: 3,
+ provider_type: :gcp,
+ platform_type: :kubernetes
+ )
+
+ cluster_projects_table.create(project_id: project.id, cluster_id: cluster.id)
+
+ provider_gcp_table.create!(
+ gcp_project_id: "test-gcp-project-#{id}",
+ endpoint: '111.111.111.111',
+ cluster_id: cluster.id,
+ status: 3,
+ num_nodes: 1,
+ zone: 'us-central1-a'
+ )
+
+ platform_kubernetes_table.create(
+ cluster_id: cluster.id,
+ api_url: 'https://kubernetes.example.com',
+ encrypted_token: 'a' * 40,
+ encrypted_token_iv: 'a' * 40
+ )
+ end
+
+ # Creates a Kubernetes namespace for a list of clusters
+ def create_kubernetes_namespace(clusters)
+ clusters.each do |cluster|
+ cluster_project = cluster_projects_table.find_by(cluster_id: cluster.id)
+ project = projects_table.find(cluster_project.project_id)
+ namespace = "#{project.path}-#{project.id}"
+
+ cluster_kubernetes_namespaces_table.create(
+ cluster_project_id: cluster_project.id,
+ cluster_id: cluster.id,
+ project_id: cluster_project.project_id,
+ namespace: namespace,
+ service_account_name: "#{namespace}-service-account"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb
index e650a176041..a8b00004fe7 100644
--- a/spec/support/redis/redis_shared_examples.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -1,7 +1,7 @@
RSpec.shared_examples "redis_shared_examples" do
include StubENV
- let(:test_redis_url) { "redis://redishost:#{redis_port}"}
+ let(:test_redis_url) { "redis://redishost:#{redis_port}"}
before do
stub_env(environment_config_file_name, Rails.root.join(config_file_name))
@@ -76,7 +76,7 @@ RSpec.shared_examples "redis_shared_examples" do
context 'when yml file with env variable' do
let(:config_file_name) { config_with_environment_variable_inside }
- before do
+ before do
stub_env(config_env_variable_url, test_redis_url)
end
diff --git a/spec/support/shared_contexts/email_shared_blocks.rb b/spec/support/shared_contexts/email_shared_context.rb
index 9d806fc524d..9d806fc524d 100644
--- a/spec/support/shared_contexts/email_shared_blocks.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index dbdca99b5aa..0acc9e2a836 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -1,8 +1,16 @@
shared_examples 'issuable notes filter' do
+ let(:params) do
+ if issuable_parent.is_a?(Project)
+ { namespace_id: issuable_parent.namespace, project_id: issuable_parent, id: issuable.iid }
+ else
+ { group_id: issuable_parent, id: issuable.to_param }
+ end
+ end
+
it 'sets discussion filter' do
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
expect(user.reload.notes_filter_for(issuable)).to eq(notes_filter)
expect(UserPreference.count).to eq(1)
@@ -13,7 +21,7 @@ shared_examples 'issuable notes filter' do
expect_any_instance_of(issuable.class).to receive(:expire_note_etag_cache)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
end
it 'does not expires notes e-tag cache for issuable if filter did not change' do
@@ -22,14 +30,14 @@ shared_examples 'issuable notes filter' do
expect_any_instance_of(issuable.class).not_to receive(:expire_note_etag_cache)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
end
it 'does not set notes filter when database is in read only mode' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
+ get :discussions, params: params.merge(notes_filter: notes_filter)
expect(user.reload.notes_filter_for(issuable)).to eq(0)
end
@@ -37,7 +45,7 @@ shared_examples 'issuable notes filter' do
it 'returns only user comments' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
+ get :discussions, params: params
discussions = JSON.parse(response.body)
expect(discussions.count).to eq(1)
@@ -47,7 +55,7 @@ shared_examples 'issuable notes filter' do
it 'returns only activity notes' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
+ get :discussions, params: params
discussions = JSON.parse(response.body)
expect(discussions.count).to eq(1)
@@ -60,7 +68,7 @@ shared_examples 'issuable notes filter' do
expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new)
- get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
+ get :discussions, params: params
end
end
end
diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
index ba363593120..52a2ee49495 100644
--- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
+++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
@@ -1,24 +1,36 @@
shared_examples 'dirty submit form' do |selector_args|
selectors = selector_args.is_a?(Array) ? selector_args : [selector_args]
+ def expect_disabled_state(form, submit, is_disabled = true)
+ disabled_selector = is_disabled == true ? '[disabled]' : ':not([disabled])'
+
+ form.find(".js-dirty-submit#{disabled_selector}", match: :first)
+
+ expect(submit.disabled?).to be is_disabled
+ end
+
selectors.each do |selector|
- it "disables #{selector[:form]} submit until there are changes", :js do
+ it "disables #{selector[:form]} submit until there are changes on #{selector[:input]}", :js do
form = find(selector[:form])
submit = form.first('.js-dirty-submit')
input = form.first(selector[:input])
+ is_radio = input[:type] == 'radio'
+ is_checkbox = input[:type] == 'checkbox'
+ is_checkable = is_radio || is_checkbox
original_value = input.value
+ original_checkable = form.find("input[name='#{input[:name]}'][checked]") if is_radio
+ original_checkable = input if is_checkbox
expect(submit.disabled?).to be true
+ expect(input.checked?).to be false
- input.set("#{original_value} changes")
+ is_checkable ? input.click : input.set("#{original_value} changes")
- form.find('.js-dirty-submit:not([disabled])', match: :first)
- expect(submit.disabled?).to be false
+ expect_disabled_state(form, submit, false)
- input.set(original_value)
+ is_checkable ? original_checkable.click : input.set(original_value)
- form.find('.js-dirty-submit[disabled]', match: :first)
- expect(submit.disabled?).to be true
+ expect_disabled_state(form, submit)
end
end
end
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index a096627ee62..eef0327c9a6 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -129,12 +129,12 @@ RSpec.shared_examples 'an editable merge request' do
expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
visit edit_project_merge_request_path(target_project, merge_request)
- uncheck 'Remove source branch when merge request is accepted'
+ uncheck 'Delete source branch when merge request is accepted'
click_button 'Save changes'
expect(page).to have_unchecked_field 'remove-source-branch-input'
- expect(page).to have_content 'Remove source branch'
+ expect(page).to have_content 'Delete source branch'
end
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
index 1f688c0f9d3..dcf7c1a90c2 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
@@ -32,11 +32,13 @@ shared_examples 'backfill migration for project repositories' do |storage|
it 'inserts rows in a single query' do
projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ group2 = namespaces.create!(name: 'gro', path: 'gro')
control_count = ActiveRecord::QueryRecorder.new { described_class.new.perform(1, projects.last.id) }
projects.create!(name: 'bar', path: 'bar', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
- projects.create!(name: 'zoo', path: 'zoo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ projects.create!(name: 'top', path: 'top', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ projects.create!(name: 'zoo', path: 'zoo', namespace_id: group2.id, storage_version: storage_version, repository_storage: shard.name)
expect { described_class.new.perform(1, projects.last.id) }.not_to exceed_query_limit(control_count)
end
diff --git a/spec/support/shared_examples/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
index 65026f1d7c0..db69b75c0c8 100644
--- a/spec/support/shared_examples/malicious_regexp_shared_examples.rb
+++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
@@ -1,7 +1,7 @@
require 'timeout'
shared_examples 'malicious regexp' do
- let(:malicious_text) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' }
+ let(:malicious_text) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' }
let(:malicious_regexp) { '(?i)^(([a-z])+.)+[A-Z]([a-z])+$' }
it 'takes under a second' do
diff --git a/spec/support/shared_examples/mentionable_shared_examples.rb b/spec/support/shared_examples/mentionable_shared_examples.rb
index 1685decbe94..1226841f24c 100644
--- a/spec/support/shared_examples/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/mentionable_shared_examples.rb
@@ -8,8 +8,8 @@ shared_context 'mentionable context' do
let(:project) { subject.project }
let(:author) { subject.author }
- let(:mentioned_issue) { create(:issue, project: project) }
- let!(:mentioned_mr) { create(:merge_request, source_project: project) }
+ let(:mentioned_issue) { create(:issue, project: project) }
+ let!(:mentioned_mr) { create(:merge_request, source_project: project) }
let(:mentioned_commit) { project.commit("HEAD~1") }
let(:ext_proj) { create(:project, :public, :repository) }
diff --git a/spec/support/shared_examples/project_list_shared_examples.rb b/spec/support/shared_examples/project_list_shared_examples.rb
new file mode 100644
index 00000000000..675d489fcab
--- /dev/null
+++ b/spec/support/shared_examples/project_list_shared_examples.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+shared_examples 'shows public projects' do
+ it 'shows projects' do
+ expect(page).to have_content(public_project.title)
+ expect(page).not_to have_content(internal_project.title)
+ expect(page).not_to have_content(private_project.title)
+ expect(page).not_to have_content(archived_project.title)
+ end
+end
+
+shared_examples 'shows public and internal projects' do
+ it 'shows projects' do
+ expect(page).to have_content(public_project.title)
+ expect(page).to have_content(internal_project.title)
+ expect(page).not_to have_content(private_project.title)
+ expect(page).not_to have_content(archived_project.title)
+ end
+end
diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb
index b4757a70984..1f7e2f7ff79 100644
--- a/spec/support/shared_examples/url_validator_examples.rb
+++ b/spec/support/shared_examples/url_validator_examples.rb
@@ -1,5 +1,5 @@
RSpec.shared_examples 'url validator examples' do |protocols|
- let(:validator) { described_class.new(attributes: [:link_url], **options) }
+ let(:validator) { described_class.new(attributes: [:link_url], **options) }
let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
subject { validator.validate_each(badge, :link_url, badge.link_url) }
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
index db9e5eb2ad6..185c62491ce 100644
--- a/spec/uploaders/file_uploader_spec.rb
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -4,7 +4,7 @@ describe FileUploader do
let(:group) { create(:group, name: 'awesome') }
let(:project) { create(:project, :legacy_storage, namespace: group, name: 'project') }
let(:uploader) { described_class.new(project) }
- let(:upload) { double(model: project, path: 'secret/foo.jpg') }
+ let(:upload) { double(model: project, path: 'secret/foo.jpg') }
subject { uploader }
diff --git a/spec/uploaders/import_export_uploader_spec.rb b/spec/uploaders/import_export_uploader_spec.rb
index 51b173b682d..825c1cabc14 100644
--- a/spec/uploaders/import_export_uploader_spec.rb
+++ b/spec/uploaders/import_export_uploader_spec.rb
@@ -4,7 +4,7 @@ describe ImportExportUploader do
let(:model) { build_stubbed(:import_export_upload) }
let(:upload) { create(:upload, model: model) }
- subject { described_class.new(model, :import_file) }
+ subject { described_class.new(model, :import_file) }
context "object_store is REMOTE" do
before do
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index 2896e9a112d..97758f0243e 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -4,19 +4,13 @@ describe PersonalFileUploader do
let(:model) { create(:personal_snippet) }
let(:uploader) { described_class.new(model) }
let(:upload) { create(:upload, :personal_snippet_upload) }
- let(:identifier) { %r{\h+/\S+} }
subject { uploader }
- it_behaves_like 'builds correct paths' do
- let(:patterns) do
- {
- store_dir: %r[uploads/-/system/personal_snippet/\d+],
- upload_path: identifier,
- absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet/\d+/#{identifier}]
- }
- end
- end
+ it_behaves_like 'builds correct paths',
+ store_dir: %r[uploads/-/system/personal_snippet/\d+],
+ upload_path: %r[\h+/\S+],
+ absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet\/\d+\/\h+\/\S+$]
context "object_store is REMOTE" do
before do
@@ -25,13 +19,17 @@ describe PersonalFileUploader do
include_context 'with storage', described_class::Store::REMOTE
- it_behaves_like 'builds correct paths' do
- let(:patterns) do
- {
- store_dir: %r[\d+/\h+],
- upload_path: identifier
- }
- end
+ it_behaves_like 'builds correct paths',
+ store_dir: %r[\d+/\h+],
+ upload_path: %r[^personal_snippet\/\d+\/\h+\/<filename>]
+ end
+
+ describe '#upload_paths' do
+ it 'builds correct paths for both local and remote storage' do
+ paths = uploader.upload_paths('test.jpg')
+
+ expect(paths.first).to match(%r[\h+\/test.jpg])
+ expect(paths.second).to match(%r[^personal_snippet\/\d+\/\h+\/test.jpg])
end
end
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
index f3f3386382f..1bb42382e8a 100644
--- a/spec/validators/url_validator_spec.rb
+++ b/spec/validators/url_validator_spec.rb
@@ -172,4 +172,55 @@ describe UrlValidator do
end
end
end
+
+ context 'when enforce_sanitization is' do
+ let(:validator) { described_class.new(attributes: [:link_url], enforce_sanitization: enforce_sanitization) }
+ let(:unsafe_url) { "https://replaceme.com/'><script>alert(document.cookie)</script>" }
+ let(:safe_url) { 'https://replaceme.com/path/to/somewhere' }
+
+ let(:unsafe_internal_url) do
+ Gitlab.config.gitlab.protocol + '://' + Gitlab.config.gitlab.host +
+ "/'><script>alert(document.cookie)</script>"
+ end
+
+ context 'true' do
+ let(:enforce_sanitization) { true }
+
+ it 'prevents unsafe urls' do
+ badge.link_url = unsafe_url
+
+ subject
+
+ expect(badge.errors.empty?).to be false
+ end
+
+ it 'prevents unsafe internal urls' do
+ badge.link_url = unsafe_internal_url
+
+ subject
+
+ expect(badge.errors.empty?).to be false
+ end
+
+ it 'allows safe urls' do
+ badge.link_url = safe_url
+
+ subject
+
+ expect(badge.errors.empty?).to be true
+ end
+ end
+
+ context 'false' do
+ let(:enforce_sanitization) { false }
+
+ it 'allows unsafe urls' do
+ badge.link_url = unsafe_url
+
+ subject
+
+ expect(badge.errors.empty?).to be true
+ end
+ end
+ end
end
diff --git a/spec/views/help/instance_configuration.html.haml_spec.rb b/spec/views/help/instance_configuration.html.haml_spec.rb
index f30b5881fde..ceb7e34a540 100644
--- a/spec/views/help/instance_configuration.html.haml_spec.rb
+++ b/spec/views/help/instance_configuration.html.haml_spec.rb
@@ -13,9 +13,9 @@ describe 'help/instance_configuration' do
it 'has links to several sections' do
render
- expect(rendered).to have_link(nil, '#ssh-host-keys-fingerprints') if ssh_settings.any?
- expect(rendered).to have_link(nil, '#gitlab-pages')
- expect(rendered).to have_link(nil, '#gitlab-ci')
+ expect(rendered).to have_link(nil, href: '#ssh-host-keys-fingerprints') if ssh_settings.any?
+ expect(rendered).to have_link(nil, href: '#gitlab-pages')
+ expect(rendered).to have_link(nil, href: '#gitlab-ci')
end
it 'has several sections' do
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index ec20c346234..2852aa380b2 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -51,28 +51,10 @@ describe 'layouts/nav/sidebar/_project' do
end
describe 'releases entry' do
- describe 'when releases feature flag is disabled' do
- before do
- stub_feature_flags(releases_page: false)
- end
-
- it 'does not render releases link' do
- render
-
- expect(rendered).not_to have_link('Releases', href: project_releases_path(project))
- end
- end
-
- describe 'when releases feature flags is enabled' do
- before do
- stub_feature_flags(releases_page: true)
- end
-
- it 'renders releases link' do
- render
+ it 'renders releases link' do
+ render
- expect(rendered).to have_link('Releases', href: project_releases_path(project))
- end
+ expect(rendered).to have_link('Releases', href: project_releases_path(project))
end
end
end
diff --git a/spec/views/projects/commit/show.html.haml_spec.rb b/spec/views/projects/commit/show.html.haml_spec.rb
index a9c32122600..d07099489e5 100644
--- a/spec/views/projects/commit/show.html.haml_spec.rb
+++ b/spec/views/projects/commit/show.html.haml_spec.rb
@@ -54,9 +54,9 @@ describe 'projects/commit/show.html.haml' do
end
it 'shows that it is in the context of a merge request' do
- merge_request_url = diffs_project_merge_request_url(project, merge_request, commit_id: commit.id)
+ merge_request_url = diffs_project_merge_request_path(project, merge_request, commit_id: commit.id)
expect(rendered).to have_content("This commit is part of merge request")
- expect(rendered).to have_link(merge_request.to_reference, merge_request_url)
+ expect(rendered).to have_link(merge_request.to_reference, href: merge_request_url)
end
end
end
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
index b0042be339c..d9bda1a3414 100644
--- a/spec/views/projects/merge_requests/show.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -32,11 +32,7 @@ describe 'projects/merge_requests/show.html.haml' do
assign(:noteable, closed_merge_request)
assign(:notes, [])
assign(:pipelines, Ci::Pipeline.none)
- assign(
- :issuable_sidebar,
- MergeRequestSerializer.new(current_user: user, project: project)
- .represent(closed_merge_request, serializer: 'sidebar')
- )
+ assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request))
preload_view_requirements
@@ -45,6 +41,33 @@ describe 'projects/merge_requests/show.html.haml' do
current_application_settings: Gitlab::CurrentSettings.current_application_settings)
end
+ describe 'merge request assignee sidebar' do
+ context 'when assignee is allowed to merge' do
+ it 'does not show a warning icon' do
+ closed_merge_request.update(assignee_id: user.id)
+ project.add_maintainer(user)
+ assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request))
+
+ render
+
+ expect(rendered).not_to have_css('.cannot-be-merged')
+ end
+ end
+
+ context 'when assignee is not allowed to merge' do
+ it 'shows a warning icon' do
+ reporter = create(:user)
+ project.add_reporter(reporter)
+ closed_merge_request.update(assignee_id: reporter.id)
+ assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request))
+
+ render
+
+ expect(rendered).to have_css('.cannot-be-merged')
+ end
+ end
+ end
+
context 'when the merge request is closed' do
it 'shows the "Reopen" button' do
render
@@ -80,4 +103,10 @@ describe 'projects/merge_requests/show.html.haml' do
expect(rendered).to have_css('a', visible: false, text: 'Close')
end
end
+
+ def serialize_issuable_sidebar(user, project, merge_request)
+ MergeRequestSerializer
+ .new(current_user: user, project: project)
+ .represent(closed_merge_request, serializer: 'sidebar')
+ end
end
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
new file mode 100644
index 00000000000..8e34521c7c8
--- /dev/null
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rails_helper'
+
+describe 'projects/settings/operations/show' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ assign :project, project
+ end
+
+ describe 'Operations > Error Tracking' do
+ before do
+ project.add_reporter(user)
+
+ allow(view).to receive(:error_tracking_setting)
+ .and_return(error_tracking_setting)
+ allow(view).to receive(:current_user).and_return(user)
+ end
+
+ let!(:error_tracking_setting) do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ context 'Settings page ' do
+ it 'renders the Operations Settings page' do
+ render
+
+ expect(rendered).to have_content _('Error Tracking')
+ expect(rendered).to have_content _('To link Sentry to GitLab, enter your Sentry URL and Auth Token')
+ expect(rendered).to have_content _('Active')
+ end
+ end
+ end
+end
diff --git a/spec/workers/cleanup_container_repository_worker_spec.rb b/spec/workers/cleanup_container_repository_worker_spec.rb
new file mode 100644
index 00000000000..5bee7294010
--- /dev/null
+++ b/spec/workers/cleanup_container_repository_worker_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe CleanupContainerRepositoryWorker, :clean_gitlab_redis_shared_state do
+ let(:repository) { create(:container_repository) }
+ let(:project) { repository.project }
+ let(:user) { project.owner }
+ let(:params) { { key: 'value' } }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ let(:service) { instance_double(Projects::ContainerRepository::CleanupTagsService) }
+
+ before do
+ allow(Projects::ContainerRepository::CleanupTagsService).to receive(:new)
+ .with(project, user, params).and_return(service)
+ end
+
+ it 'executes the destroy service' do
+ expect(service).to receive(:execute)
+
+ subject.perform(user.id, repository.id, params)
+ end
+
+ it 'does not raise error when user could not be found' do
+ expect do
+ subject.perform(-1, repository.id, params)
+ end.not_to raise_error
+ end
+
+ it 'does not raise error when repository could not be found' do
+ expect do
+ subject.perform(user.id, -1, params)
+ end.not_to raise_error
+ end
+
+ context 'when executed twice in short period' do
+ it 'executes service only for the first time' do
+ expect(service).to receive(:execute).once
+
+ 2.times { subject.perform(user.id, repository.id, params) }
+ end
+ end
+ end
+end
diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb
index b47b4a02a68..27995cf1611 100644
--- a/spec/workers/expire_build_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_artifacts_worker_spec.rb
@@ -11,6 +11,7 @@ describe ExpireBuildArtifactsWorker do
describe '#perform' do
before do
+ stub_feature_flags(ci_new_expire_job_artifacts_service: false)
build
end
@@ -47,4 +48,17 @@ describe ExpireBuildArtifactsWorker do
Sidekiq::Queues.jobs_by_worker['ExpireBuildInstanceArtifactsWorker']
end
end
+
+ describe '#perform with ci_new_expire_job_artifacts_service feature flag' do
+ before do
+ stub_feature_flags(ci_new_expire_job_artifacts_service: true)
+ end
+
+ it 'executes a service' do
+ expect_any_instance_of(Ci::DestroyExpiredJobArtifactsService).to receive(:execute)
+ expect(ExpireBuildInstanceArtifactsWorker).not_to receive(:bulk_perform_async)
+
+ worker.perform
+ end
+ end
end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index a159f24f876..4895a968d6e 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -71,6 +71,17 @@ describe GitGarbageCollectWorker do
subject.perform(project.id)
end
+
+ context 'when the repository has joined a pool' do
+ let!(:pool) { create(:pool_repository, :ready) }
+ let(:project) { pool.source_project }
+
+ it 'ensures the repositories are linked' do
+ expect_any_instance_of(PoolRepository).to receive(:link_repository).once
+
+ subject.perform(project.id)
+ end
+ end
end
context 'when no lease can be obtained' do
diff --git a/spec/workers/import_issues_csv_worker_spec.rb b/spec/workers/import_issues_csv_worker_spec.rb
new file mode 100644
index 00000000000..89370c4890d
--- /dev/null
+++ b/spec/workers/import_issues_csv_worker_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ImportIssuesCsvWorker do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:upload) { create(:upload) }
+
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ it 'calls #execute on Issues::ImportCsvService and destroys upload' do
+ expect_any_instance_of(Issues::ImportCsvService).to receive(:execute).and_return({ success: 5, errors: [], valid_file: true })
+
+ worker.perform(user.id, project.id, upload.id)
+
+ expect { upload.reload }.to raise_error ActiveRecord::RecordNotFound
+ end
+ end
+end
diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb
index b999a6fd5b6..ea2b6ae229e 100644
--- a/spec/workers/prune_old_events_worker_spec.rb
+++ b/spec/workers/prune_old_events_worker_spec.rb
@@ -5,8 +5,8 @@ describe PruneOldEventsWorker do
let(:user) { create(:user) }
let!(:expired_event) { create(:event, :closed, author: user, created_at: 25.months.ago) }
- let!(:not_expired_1_day_event) { create(:event, :closed, author: user, created_at: 1.day.ago) }
- let!(:not_expired_13_month_event) { create(:event, :closed, author: user, created_at: 13.months.ago) }
+ let!(:not_expired_1_day_event) { create(:event, :closed, author: user, created_at: 1.day.ago) }
+ let!(:not_expired_13_month_event) { create(:event, :closed, author: user, created_at: 13.months.ago) }
let!(:not_expired_2_years_event) { create(:event, :closed, author: user, created_at: 2.years.ago) }
it 'prunes events older than 2 years' do
diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb
new file mode 100644
index 00000000000..e3db10ed645
--- /dev/null
+++ b/spec/workers/remote_mirror_notification_worker_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe RemoteMirrorNotificationWorker, :mailer do
+ set(:project) { create(:project, :repository, :remote_mirror) }
+ set(:mirror) { project.remote_mirrors.first }
+
+ describe '#execute' do
+ it 'calls NotificationService#remote_mirror_update_failed when the mirror exists' do
+ mirror.update_column(:last_error, "There was a problem fetching")
+
+ expect(NotificationService).to receive_message_chain(:new, :remote_mirror_update_failed)
+
+ subject.perform(mirror.id)
+
+ expect(mirror.reload.error_notification_sent?).to be_truthy
+ end
+
+ it 'does nothing when the mirror has no errors' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(mirror.id)
+ end
+
+ it 'does nothing when the mirror does not exist' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(RemoteMirror.maximum(:id).to_i.succ)
+ end
+
+ it 'does nothing when a notification has already been sent' do
+ mirror.update_columns(last_error: "There was a problem fetching",
+ error_notification_sent: true)
+
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(mirror.id)
+ end
+ end
+end
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index d73b0b53713..b582a3650b6 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -22,6 +22,13 @@ describe RepositoryUpdateRemoteMirrorWorker do
expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.update_status }.to('finished')
end
+ it 'resets the notification flag upon success' do
+ expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success)
+ remote_mirror.update_column(:error_notification_sent, true)
+
+ expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.error_notification_sent }.to(false)
+ end
+
it 'sets status as failed when update remote mirror service executes with errors' do
error_message = 'fail!'
diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
index a2bc264b0f6..963237ceadf 100644
--- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
+++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
@@ -21,17 +21,17 @@ describe UpdateHeadPipelineForMergeRequestWorker do
merge_request.merge_request_diff.update(head_commit_sha: 'different_sha')
end
- it 'does not update head_pipeline_id' do
- expect { subject.perform(merge_request.id) }.not_to raise_error
-
- expect(merge_request.reload.head_pipeline_id).to eq(nil)
+ it 'does not update head pipeline' do
+ expect { subject.perform(merge_request.id) }
+ .not_to change { merge_request.reload.head_pipeline_id }
end
end
end
context 'when pipeline does not exist for the source project and branch' do
it 'does not update the head_pipeline_id of the merge_request' do
- expect { subject.perform(merge_request.id) }.not_to change { merge_request.reload.head_pipeline_id }
+ expect { subject.perform(merge_request.id) }
+ .not_to change { merge_request.reload.head_pipeline_id }
end
end
diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml
index 02ec3e2d9fe..60241c65202 100644
--- a/vendor/prometheus/values.yaml
+++ b/vendor/prometheus/values.yaml
@@ -141,3 +141,105 @@ serverFiles:
- __meta_kubernetes_pod_name
action: replace
target_label: kubernetes_pod_name
+ # Sourced from Knative monitoring config: https://github.com/knative/serving/blob/master/config/monitoring/metrics/prometheus/100-prometheus-scrape-config.yaml
+ - job_name: autoscaler
+ scrape_interval: 3s
+ scrape_timeout: 3s
+ kubernetes_sd_configs:
+ - role: pod
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_app, __meta_kubernetes_pod_container_port_name]
+ action: keep
+ regex: knative-serving;autoscaler;metrics
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ - job_name: activator
+ scrape_interval: 3s
+ scrape_timeout: 3s
+ kubernetes_sd_configs:
+ - role: pod
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_app, __meta_kubernetes_pod_container_port_name]
+ action: keep
+ regex: knative-serving;activator;metrics-port
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ # Istio mesh
+ - job_name: istio-mesh
+ scrape_interval: 5s
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
+ action: keep
+ regex: istio-system;istio-telemetry;prometheus
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ - job_name: istio-policy
+ scrape_interval: 5s
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
+ action: keep
+ regex: istio-system;istio-policy;http-monitoring
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ # Istio telemetry
+ - job_name: istio-telemetry
+ scrape_interval: 5s
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
+ action: keep
+ regex: istio-system;istio-telemetry;http-monitoring
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
+ # Istio pilot
+ - job_name: istio-pilot
+ scrape_interval: 5s
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ # Scrape only the the targets matching the following metadata
+ - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
+ action: keep
+ regex: istio-system;istio-pilot;http-monitoring
+ # Rename metadata labels to be reader friendly
+ - source_labels: [__meta_kubernetes_namespace]
+ target_label: namespace
+ - source_labels: [__meta_kubernetes_pod_name]
+ target_label: pod
+ - source_labels: [__meta_kubernetes_service_name]
+ target_label: service
diff --git a/yarn.lock b/yarn.lock
index 90d3d6fb7cb..dd027355865 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9,32 +9,32 @@
dependencies:
"@babel/highlight" "^7.0.0"
-"@babel/core@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.1.2.tgz#f8d2a9ceb6832887329a7b60f9d035791400ba4e"
- integrity sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw==
+"@babel/core@^7.2.2":
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.2.tgz#07adba6dde27bb5ad8d8672f15fde3e08184a687"
+ integrity sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.1.2"
- "@babel/helpers" "^7.1.2"
- "@babel/parser" "^7.1.2"
- "@babel/template" "^7.1.2"
- "@babel/traverse" "^7.1.0"
- "@babel/types" "^7.1.2"
+ "@babel/generator" "^7.2.2"
+ "@babel/helpers" "^7.2.0"
+ "@babel/parser" "^7.2.2"
+ "@babel/template" "^7.2.2"
+ "@babel/traverse" "^7.2.2"
+ "@babel/types" "^7.2.2"
convert-source-map "^1.1.0"
- debug "^3.1.0"
- json5 "^0.5.0"
+ debug "^4.1.0"
+ json5 "^2.1.0"
lodash "^4.17.10"
resolve "^1.3.2"
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.0.0", "@babel/generator@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630"
- integrity sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig==
+"@babel/generator@^7.0.0", "@babel/generator@^7.2.2":
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.2.tgz#18c816c70962640eab42fe8cae5f3947a5c65ccc"
+ integrity sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==
dependencies:
- "@babel/types" "^7.1.2"
+ "@babel/types" "^7.2.2"
jsesc "^2.5.1"
lodash "^4.17.10"
source-map "^0.5.0"
@@ -64,6 +64,17 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
+"@babel/helper-create-class-features-plugin@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.0.tgz#2b01a81b3adc2b1287f9ee193688ef8dc71e718f"
+ integrity sha512-DUsQNS2CGLZZ7I3W3fvh0YpPDd6BuWJlDl+qmZZpABZHza2ErE3LxtEzLJFHFC1ZwtlAXvHhbFYbtM5o5B0WBw==
+ dependencies:
+ "@babel/helper-function-name" "^7.1.0"
+ "@babel/helper-member-expression-to-functions" "^7.0.0"
+ "@babel/helper-optimise-call-expression" "^7.0.0"
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/helper-replace-supers" "^7.2.3"
+
"@babel/helper-define-map@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c"
@@ -160,14 +171,14 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-replace-supers@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz#5fc31de522ec0ef0899dc9b3e7cf6a5dd655f362"
- integrity sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ==
+"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.2.3":
+ 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.1.0"
+ "@babel/traverse" "^7.2.3"
"@babel/types" "^7.0.0"
"@babel/helper-simple-access@^7.1.0":
@@ -195,14 +206,14 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helpers@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.1.2.tgz#ab752e8c35ef7d39987df4e8586c63b8846234b5"
- integrity sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA==
+"@babel/helpers@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.2.0.tgz#8335f3140f3144270dc63c4732a4f8b0a50b7a21"
+ integrity sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==
dependencies:
"@babel/template" "^7.1.2"
- "@babel/traverse" "^7.1.0"
- "@babel/types" "^7.1.2"
+ "@babel/traverse" "^7.1.5"
+ "@babel/types" "^7.2.0"
"@babel/highlight@^7.0.0":
version "7.0.0"
@@ -213,149 +224,146 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409"
- integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==
+"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3":
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.3.tgz#32f5df65744b70888d17872ec106b02434ba1489"
+ integrity sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==
-"@babel/plugin-proposal-async-generator-functions@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.1.0.tgz#41c1a702e10081456e23a7b74d891922dd1bb6ce"
- integrity sha512-Fq803F3Jcxo20MXUSDdmZZXrPe6BWyGcWBPPNB/M7WaUYESKDeKMOGIxEzQOjGSmW/NWb6UaPZrtTB2ekhB/ew==
+"@babel/plugin-proposal-async-generator-functions@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e"
+ integrity sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-remap-async-to-generator" "^7.1.0"
- "@babel/plugin-syntax-async-generators" "^7.0.0"
+ "@babel/plugin-syntax-async-generators" "^7.2.0"
-"@babel/plugin-proposal-class-properties@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.1.0.tgz#9af01856b1241db60ec8838d84691aa0bd1e8df4"
- integrity sha512-/PCJWN+CKt5v1xcGn4vnuu13QDoV+P7NcICP44BoonAJoPSGwVkgrXihFIQGiEjjPlUDBIw1cM7wYFLARS2/hw==
+"@babel/plugin-proposal-class-properties@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz#272636bc0fa19a0bc46e601ec78136a173ea36cd"
+ integrity sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg==
dependencies:
- "@babel/helper-function-name" "^7.1.0"
- "@babel/helper-member-expression-to-functions" "^7.0.0"
- "@babel/helper-optimise-call-expression" "^7.0.0"
+ "@babel/helper-create-class-features-plugin" "^7.3.0"
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-replace-supers" "^7.1.0"
- "@babel/plugin-syntax-class-properties" "^7.0.0"
-"@babel/plugin-proposal-json-strings@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz#3b4d7b5cf51e1f2e70f52351d28d44fc2970d01e"
- integrity sha512-kfVdUkIAGJIVmHmtS/40i/fg/AGnw/rsZBCaapY5yjeO5RA9m165Xbw9KMOu2nqXP5dTFjEjHdfNdoVcHv133Q==
+"@babel/plugin-proposal-json-strings@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317"
+ integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/plugin-syntax-json-strings" "^7.0.0"
+ "@babel/plugin-syntax-json-strings" "^7.2.0"
-"@babel/plugin-proposal-object-rest-spread@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz#9a17b547f64d0676b6c9cecd4edf74a82ab85e7e"
- integrity sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw==
+"@babel/plugin-proposal-object-rest-spread@^7.3.1":
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.1.tgz#f69fb6a1ea6a4e1c503994a91d9cf76f3c4b36e8"
+ integrity sha512-Nmmv1+3LqxJu/V5jU9vJmxR/KIRWFk2qLHmbB56yRRRFhlaSuOVXscX3gUmhaKgUhzA3otOHVubbIEVYsZ0eZg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/plugin-syntax-object-rest-spread" "^7.0.0"
+ "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
-"@babel/plugin-proposal-optional-catch-binding@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz#b610d928fe551ff7117d42c8bb410eec312a6425"
- integrity sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw==
+"@babel/plugin-proposal-optional-catch-binding@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5"
+ integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/plugin-syntax-optional-catch-binding" "^7.0.0"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
-"@babel/plugin-proposal-unicode-property-regex@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0.tgz#498b39cd72536cd7c4b26177d030226eba08cd33"
- integrity sha512-tM3icA6GhC3ch2SkmSxv7J/hCWKISzwycub6eGsDrFDgukD4dZ/I+x81XgW0YslS6mzNuQ1Cbzh5osjIMgepPQ==
+"@babel/plugin-proposal-private-methods@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.3.0.tgz#da373257a66525cb76544c37ab2ce4c611568841"
+ integrity sha512-j6luy/F0MX6kd71e9hz97my2tBXTa+czAz+sscJVCRmjB9e9g2D4JN+tyfcwMCXUM2afj/tYCjzNaxwWJ4SdYg==
dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.3.0"
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-regex" "^7.0.0"
- regexpu-core "^4.2.0"
-"@babel/plugin-syntax-async-generators@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz#bf0891dcdbf59558359d0c626fdc9490e20bc13c"
- integrity sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA==
+"@babel/plugin-proposal-unicode-property-regex@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz#abe7281fe46c95ddc143a65e5358647792039520"
+ integrity sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/helper-regex" "^7.0.0"
+ regexpu-core "^4.2.0"
-"@babel/plugin-syntax-class-properties@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0.tgz#e051af5d300cbfbcec4a7476e37a803489881634"
- integrity sha512-cR12g0Qzn4sgkjrbrzWy2GE7m9vMl/sFkqZ3gIpAQdrvPDnLM8180i+ANDFIXfjHo9aqp0ccJlQ0QNZcFUbf9w==
+"@babel/plugin-syntax-async-generators@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f"
+ integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-syntax-dynamic-import@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0.tgz#6dfb7d8b6c3be14ce952962f658f3b7eb54c33ee"
- integrity sha512-Gt9xNyRrCHCiyX/ZxDGOcBnlJl0I3IWicpZRC4CdC0P5a/I07Ya2OAMEBU+J7GmRFVmIetqEYRko6QYRuKOESw==
+"@babel/plugin-syntax-dynamic-import@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612"
+ integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-syntax-import-meta@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.0.0.tgz#ca946b73216c29c39a55ef2d739097fee8a85d69"
- integrity sha512-FEoGvhXVAiWzpDjyZIlBGzKyNk/lnRPy7aPke3PjVkiAY0QFsvFfkjUg5diRwVfowBA8SJqvFt0ZoXNSjl70hQ==
+"@babel/plugin-syntax-import-meta@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.2.0.tgz#2333ef4b875553a3bcd1e93f8ebc09f5b9213a40"
+ integrity sha512-Hq6kFSZD7+PHkmBN8bCpHR6J8QEoCuEV/B38AIQscYjgMZkGlXB7cHNFzP5jR4RCh5545yP1ujHdmO7hAgKtBA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-syntax-json-strings@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz#0d259a68090e15b383ce3710e01d5b23f3770cbd"
- integrity sha512-UlSfNydC+XLj4bw7ijpldc1uZ/HB84vw+U6BTuqMdIEmz/LDe63w/GHtpQMdXWdqQZFeAI9PjnHe/vDhwirhKA==
+"@babel/plugin-syntax-json-strings@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470"
+ integrity sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-syntax-object-rest-spread@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz#37d8fbcaf216bd658ea1aebbeb8b75e88ebc549b"
- integrity sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw==
+"@babel/plugin-syntax-object-rest-spread@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e"
+ integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-syntax-optional-catch-binding@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz#886f72008b3a8b185977f7cb70713b45e51ee475"
- integrity sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw==
+"@babel/plugin-syntax-optional-catch-binding@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c"
+ integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-arrow-functions@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0.tgz#a6c14875848c68a3b4b3163a486535ef25c7e749"
- integrity sha512-2EZDBl1WIO/q4DIkIp4s86sdp4ZifL51MoIviLY/gG/mLSuOIEg7J8o6mhbxOTvUJkaN50n+8u41FVsr5KLy/w==
+"@babel/plugin-transform-arrow-functions@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550"
+ integrity sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-async-to-generator@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.1.0.tgz#109e036496c51dd65857e16acab3bafdf3c57811"
- integrity sha512-rNmcmoQ78IrvNCIt/R9U+cixUHeYAzgusTFgIAv+wQb9HJU4szhpDD6e5GCACmj/JP5KxuCwM96bX3L9v4ZN/g==
+"@babel/plugin-transform-async-to-generator@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz#68b8a438663e88519e65b776f8938f3445b1a2ff"
+ integrity sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-remap-async-to-generator" "^7.1.0"
-"@babel/plugin-transform-block-scoped-functions@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0.tgz#482b3f75103927e37288b3b67b65f848e2aa0d07"
- integrity sha512-AOBiyUp7vYTqz2Jibe1UaAWL0Hl9JUXEgjFvvvcSc9MVDItv46ViXFw2F7SVt1B5k+KWjl44eeXOAk3UDEaJjQ==
+"@babel/plugin-transform-block-scoped-functions@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190"
+ integrity sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-block-scoping@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0.tgz#1745075edffd7cdaf69fab2fb6f9694424b7e9bc"
- integrity sha512-GWEMCrmHQcYWISilUrk9GDqH4enf3UmhOEbNbNrlNAX1ssH3MsS1xLOS6rdjRVPgA7XXVPn87tRkdTEoA/dxEg==
+"@babel/plugin-transform-block-scoping@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz#f17c49d91eedbcdf5dd50597d16f5f2f770132d4"
+ integrity sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
lodash "^4.17.10"
-"@babel/plugin-transform-classes@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.1.0.tgz#ab3f8a564361800cbc8ab1ca6f21108038432249"
- integrity sha512-rNaqoD+4OCBZjM7VaskladgqnZ1LO6o2UxuWSDzljzW21pN1KXkB7BstAVweZdxQkHAujps5QMNOTWesBciKFg==
+"@babel/plugin-transform-classes@^7.2.0":
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz#6c90542f210ee975aa2aa8c8b5af7fa73a126953"
+ integrity sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ==
dependencies:
"@babel/helper-annotate-as-pure" "^7.0.0"
"@babel/helper-define-map" "^7.1.0"
@@ -366,99 +374,106 @@
"@babel/helper-split-export-declaration" "^7.0.0"
globals "^11.1.0"
-"@babel/plugin-transform-computed-properties@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0.tgz#2fbb8900cd3e8258f2a2ede909b90e7556185e31"
- integrity sha512-ubouZdChNAv4AAWAgU7QKbB93NU5sHwInEWfp+/OzJKA02E6Woh9RVoX4sZrbRwtybky/d7baTUqwFx+HgbvMA==
+"@babel/plugin-transform-computed-properties@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da"
+ integrity sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-destructuring@^7.0.0":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.1.2.tgz#5fa77d473f5a0a3f5266ad7ce2e8c995a164d60a"
- integrity sha512-cvToXvp/OsYxtEn57XJu9BvsGSEYjAh9UeUuXpoi7x6QHB7YdWyQ4lRU/q0Fu1IJNT0o0u4FQ1DMQBzJ8/8vZg==
+"@babel/plugin-transform-destructuring@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.2.0.tgz#e75269b4b7889ec3a332cd0d0c8cff8fed0dc6f3"
+ integrity sha512-coVO2Ayv7g0qdDbrNiadE4bU7lvCd9H539m2gMknyVjjMdwF/iCOM7R+E8PkntoqLkltO0rk+3axhpp/0v68VQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-dotall-regex@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz#73a24da69bc3c370251f43a3d048198546115e58"
- integrity sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig==
+"@babel/plugin-transform-dotall-regex@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz#f0aabb93d120a8ac61e925ea0ba440812dbe0e49"
+ integrity sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-regex" "^7.0.0"
regexpu-core "^4.1.3"
-"@babel/plugin-transform-duplicate-keys@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0.tgz#a0601e580991e7cace080e4cf919cfd58da74e86"
- integrity sha512-w2vfPkMqRkdxx+C71ATLJG30PpwtTpW7DDdLqYt2acXU7YjztzeWW2Jk1T6hKqCLYCcEA5UQM/+xTAm+QCSnuQ==
+"@babel/plugin-transform-duplicate-keys@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz#d952c4930f312a4dbfff18f0b2914e60c35530b3"
+ integrity sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-exponentiation-operator@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.1.0.tgz#9c34c2ee7fd77e02779cfa37e403a2e1003ccc73"
- integrity sha512-uZt9kD1Pp/JubkukOGQml9tqAeI8NkE98oZnHZ2qHRElmeKCodbTZgOEUtujSCSLhHSBWbzNiFSDIMC4/RBTLQ==
+"@babel/plugin-transform-exponentiation-operator@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008"
+ integrity sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==
dependencies:
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-for-of@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0.tgz#f2ba4eadb83bd17dc3c7e9b30f4707365e1c3e39"
- integrity sha512-TlxKecN20X2tt2UEr2LNE6aqA0oPeMT1Y3cgz8k4Dn1j5ObT8M3nl9aA37LLklx0PBZKETC9ZAf9n/6SujTuXA==
+"@babel/plugin-transform-for-of@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz#ab7468befa80f764bb03d3cb5eef8cc998e1cad9"
+ integrity sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-function-name@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.1.0.tgz#29c5550d5c46208e7f730516d41eeddd4affadbb"
- integrity sha512-VxOa1TMlFMtqPW2IDYZQaHsFrq/dDoIjgN098NowhexhZcz3UGlvPgZXuE1jEvNygyWyxRacqDpCZt+par1FNg==
+"@babel/plugin-transform-function-name@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz#f7930362829ff99a3174c39f0afcc024ef59731a"
+ integrity sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==
dependencies:
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-literals@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0.tgz#2aec1d29cdd24c407359c930cdd89e914ee8ff86"
- integrity sha512-1NTDBWkeNXgpUcyoVFxbr9hS57EpZYXpje92zv0SUzjdu3enaRwF/l3cmyRnXLtIdyJASyiS6PtybK+CgKf7jA==
+"@babel/plugin-transform-literals@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1"
+ integrity sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-modules-amd@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.1.0.tgz#f9e0a7072c12e296079b5a59f408ff5b97bf86a8"
- integrity sha512-wt8P+xQ85rrnGNr2x1iV3DW32W8zrB6ctuBkYBbf5/ZzJY99Ob4MFgsZDFgczNU76iy9PWsy4EuxOliDjdKw6A==
+"@babel/plugin-transform-modules-amd@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz#82a9bce45b95441f617a24011dc89d12da7f4ee6"
+ integrity sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==
dependencies:
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-modules-commonjs@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.1.0.tgz#0a9d86451cbbfb29bd15186306897c67f6f9a05c"
- integrity sha512-wtNwtMjn1XGwM0AXPspQgvmE6msSJP15CX2RVfpTSTNPLhKhaOjaIfBaVfj4iUZ/VrFSodcFedwtPg/NxwQlPA==
+"@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-systemjs@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0.tgz#8873d876d4fee23209decc4d1feab8f198cf2df4"
- integrity sha512-8EDKMAsitLkiF/D4Zhe9CHEE2XLh4bfLbb9/Zf3FgXYQOZyZYyg7EAel/aT2A7bHv62jwHf09q2KU/oEexr83g==
+"@babel/plugin-transform-modules-systemjs@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.2.0.tgz#912bfe9e5ff982924c81d0937c92d24994bb9068"
+ integrity sha512-aYJwpAhoK9a+1+O625WIjvMY11wkB/ok0WClVwmeo3mCjcNRjt+/8gHWrB5i+00mUju0gWsBkQnPpdvQ7PImmQ==
dependencies:
"@babel/helper-hoist-variables" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-modules-umd@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.1.0.tgz#a29a7d85d6f28c3561c33964442257cc6a21f2a8"
- integrity sha512-enrRtn5TfRhMmbRwm7F8qOj0qEYByqUvTttPEGimcBH4CJHphjyK1Vg7sdU7JjeEmgSpM890IT/efS2nMHwYig==
+"@babel/plugin-transform-modules-umd@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz#7678ce75169f0877b8eb2235538c074268dd01ae"
+ integrity sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==
dependencies:
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-transform-named-capturing-groups-regex@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz#140b52985b2d6ef0cb092ef3b29502b990f9cd50"
+ integrity sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw==
+ dependencies:
+ regexp-tree "^0.1.0"
+
"@babel/plugin-transform-new-target@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a"
@@ -466,18 +481,18 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-object-super@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.1.0.tgz#b1ae194a054b826d8d4ba7ca91486d4ada0f91bb"
- integrity sha512-/O02Je1CRTSk2SSJaq0xjwQ8hG4zhZGNjE8psTsSNPXyLRCODv7/PBozqT5AmQMzp7MI3ndvMhGdqp9c96tTEw==
+"@babel/plugin-transform-object-super@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz#b35d4c10f56bab5d650047dad0f1d8e8814b6598"
+ integrity sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-replace-supers" "^7.1.0"
-"@babel/plugin-transform-parameters@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.1.0.tgz#44f492f9d618c9124026e62301c296bf606a7aed"
- integrity sha512-vHV7oxkEJ8IHxTfRr3hNGzV446GAb+0hgbA7o/0Jd76s+YzccdWuTU296FOCOl/xweU4t/Ya4g41yWz80RFCRw==
+"@babel/plugin-transform-parameters@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz#0d5ad15dc805e2ea866df4dd6682bfe76d1408c2"
+ integrity sha512-kB9+hhUidIgUoBQ0MsxMewhzr8i60nMa2KgeJKQWYrqQpqcBYtnpR+JgkadZVZoaEZ/eKu9mclFaVwhRpLNSzA==
dependencies:
"@babel/helper-call-delegate" "^7.1.0"
"@babel/helper-get-function-arity" "^7.0.0"
@@ -490,127 +505,129 @@
dependencies:
regenerator-transform "^0.13.3"
-"@babel/plugin-transform-shorthand-properties@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz#85f8af592dcc07647541a0350e8c95c7bf419d15"
- integrity sha512-g/99LI4vm5iOf5r1Gdxq5Xmu91zvjhEG5+yZDJW268AZELAu4J1EiFLnkSG3yuUsZyOipVOVUKoGPYwfsTymhw==
+"@babel/plugin-transform-shorthand-properties@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0"
+ integrity sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-spread@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0.tgz#93583ce48dd8c85e53f3a46056c856e4af30b49b"
- integrity sha512-L702YFy2EvirrR4shTj0g2xQp7aNwZoWNCkNu2mcoU0uyzMl0XRwDSwzB/xp6DSUFiBmEXuyAyEN16LsgVqGGQ==
+"@babel/plugin-transform-spread@^7.2.0":
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz#3103a9abe22f742b6d406ecd3cd49b774919b406"
+ integrity sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-sticky-regex@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0.tgz#30a9d64ac2ab46eec087b8530535becd90e73366"
- integrity sha512-LFUToxiyS/WD+XEWpkx/XJBrUXKewSZpzX68s+yEOtIbdnsRjpryDw9U06gYc6klYEij/+KQVRnD3nz3AoKmjw==
+"@babel/plugin-transform-sticky-regex@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1"
+ integrity sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-regex" "^7.0.0"
-"@babel/plugin-transform-template-literals@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0.tgz#084f1952efe5b153ddae69eb8945f882c7a97c65"
- integrity sha512-vA6rkTCabRZu7Nbl9DfLZE1imj4tzdWcg5vtdQGvj+OH9itNNB6hxuRMHuIY8SGnEt1T9g5foqs9LnrHzsqEFg==
+"@babel/plugin-transform-template-literals@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz#d87ed01b8eaac7a92473f608c97c089de2ba1e5b"
+ integrity sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-typeof-symbol@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0.tgz#4dcf1e52e943e5267b7313bff347fdbe0f81cec9"
- integrity sha512-1r1X5DO78WnaAIvs5uC48t41LLckxsYklJrZjNKcevyz83sF2l4RHbw29qrCPr/6ksFsdfRpT/ZgxNWHXRnffg==
+"@babel/plugin-transform-typeof-symbol@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2"
+ integrity sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-unicode-regex@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0.tgz#c6780e5b1863a76fe792d90eded9fcd5b51d68fc"
- integrity sha512-uJBrJhBOEa3D033P95nPHu3nbFwFE9ZgXsfEitzoIXIwqAZWk7uXcg06yFKXz9FSxBH5ucgU/cYdX0IV8ldHKw==
+"@babel/plugin-transform-unicode-regex@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b"
+ integrity sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-regex" "^7.0.0"
regexpu-core "^4.1.3"
-"@babel/preset-env@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.1.0.tgz#e67ea5b0441cfeab1d6f41e9b5c79798800e8d11"
- integrity sha512-ZLVSynfAoDHB/34A17/JCZbyrzbQj59QC1Anyueb4Bwjh373nVPq5/HMph0z+tCmcDjXDe+DlKQq9ywQuvWrQg==
+"@babel/preset-env@^7.3.1":
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.1.tgz#389e8ca6b17ae67aaf9a2111665030be923515db"
+ integrity sha512-FHKrD6Dxf30e8xgHQO0zJZpUPfVZg+Xwgz5/RdSWCbza9QLNk4Qbp40ctRoqDxml3O8RMzB1DU55SXeDG6PqHQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/plugin-proposal-async-generator-functions" "^7.1.0"
- "@babel/plugin-proposal-json-strings" "^7.0.0"
- "@babel/plugin-proposal-object-rest-spread" "^7.0.0"
- "@babel/plugin-proposal-optional-catch-binding" "^7.0.0"
- "@babel/plugin-proposal-unicode-property-regex" "^7.0.0"
- "@babel/plugin-syntax-async-generators" "^7.0.0"
- "@babel/plugin-syntax-object-rest-spread" "^7.0.0"
- "@babel/plugin-syntax-optional-catch-binding" "^7.0.0"
- "@babel/plugin-transform-arrow-functions" "^7.0.0"
- "@babel/plugin-transform-async-to-generator" "^7.1.0"
- "@babel/plugin-transform-block-scoped-functions" "^7.0.0"
- "@babel/plugin-transform-block-scoping" "^7.0.0"
- "@babel/plugin-transform-classes" "^7.1.0"
- "@babel/plugin-transform-computed-properties" "^7.0.0"
- "@babel/plugin-transform-destructuring" "^7.0.0"
- "@babel/plugin-transform-dotall-regex" "^7.0.0"
- "@babel/plugin-transform-duplicate-keys" "^7.0.0"
- "@babel/plugin-transform-exponentiation-operator" "^7.1.0"
- "@babel/plugin-transform-for-of" "^7.0.0"
- "@babel/plugin-transform-function-name" "^7.1.0"
- "@babel/plugin-transform-literals" "^7.0.0"
- "@babel/plugin-transform-modules-amd" "^7.1.0"
- "@babel/plugin-transform-modules-commonjs" "^7.1.0"
- "@babel/plugin-transform-modules-systemjs" "^7.0.0"
- "@babel/plugin-transform-modules-umd" "^7.1.0"
+ "@babel/plugin-proposal-async-generator-functions" "^7.2.0"
+ "@babel/plugin-proposal-json-strings" "^7.2.0"
+ "@babel/plugin-proposal-object-rest-spread" "^7.3.1"
+ "@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.2.0"
+ "@babel/plugin-syntax-async-generators" "^7.2.0"
+ "@babel/plugin-syntax-json-strings" "^7.2.0"
+ "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
+ "@babel/plugin-transform-arrow-functions" "^7.2.0"
+ "@babel/plugin-transform-async-to-generator" "^7.2.0"
+ "@babel/plugin-transform-block-scoped-functions" "^7.2.0"
+ "@babel/plugin-transform-block-scoping" "^7.2.0"
+ "@babel/plugin-transform-classes" "^7.2.0"
+ "@babel/plugin-transform-computed-properties" "^7.2.0"
+ "@babel/plugin-transform-destructuring" "^7.2.0"
+ "@babel/plugin-transform-dotall-regex" "^7.2.0"
+ "@babel/plugin-transform-duplicate-keys" "^7.2.0"
+ "@babel/plugin-transform-exponentiation-operator" "^7.2.0"
+ "@babel/plugin-transform-for-of" "^7.2.0"
+ "@babel/plugin-transform-function-name" "^7.2.0"
+ "@babel/plugin-transform-literals" "^7.2.0"
+ "@babel/plugin-transform-modules-amd" "^7.2.0"
+ "@babel/plugin-transform-modules-commonjs" "^7.2.0"
+ "@babel/plugin-transform-modules-systemjs" "^7.2.0"
+ "@babel/plugin-transform-modules-umd" "^7.2.0"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.3.0"
"@babel/plugin-transform-new-target" "^7.0.0"
- "@babel/plugin-transform-object-super" "^7.1.0"
- "@babel/plugin-transform-parameters" "^7.1.0"
+ "@babel/plugin-transform-object-super" "^7.2.0"
+ "@babel/plugin-transform-parameters" "^7.2.0"
"@babel/plugin-transform-regenerator" "^7.0.0"
- "@babel/plugin-transform-shorthand-properties" "^7.0.0"
- "@babel/plugin-transform-spread" "^7.0.0"
- "@babel/plugin-transform-sticky-regex" "^7.0.0"
- "@babel/plugin-transform-template-literals" "^7.0.0"
- "@babel/plugin-transform-typeof-symbol" "^7.0.0"
- "@babel/plugin-transform-unicode-regex" "^7.0.0"
- browserslist "^4.1.0"
+ "@babel/plugin-transform-shorthand-properties" "^7.2.0"
+ "@babel/plugin-transform-spread" "^7.2.0"
+ "@babel/plugin-transform-sticky-regex" "^7.2.0"
+ "@babel/plugin-transform-template-literals" "^7.2.0"
+ "@babel/plugin-transform-typeof-symbol" "^7.2.0"
+ "@babel/plugin-transform-unicode-regex" "^7.2.0"
+ browserslist "^4.3.4"
invariant "^2.2.2"
js-levenshtein "^1.1.3"
semver "^5.3.0"
-"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
- integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==
+"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2":
+ 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.1.2"
- "@babel/types" "^7.1.2"
+ "@babel/parser" "^7.2.2"
+ "@babel/types" "^7.2.2"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2"
- integrity sha512-bwgln0FsMoxm3pLOgrrnGaXk18sSM9JNf1/nHC/FksmNGFbYnPWY4GYCfLxyP1KRmfsxqkRpfoa6xr6VuuSxdw==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2", "@babel/traverse@^7.2.3":
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8"
+ integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.0.0"
+ "@babel/generator" "^7.2.2"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.0.0"
- "@babel/parser" "^7.1.0"
- "@babel/types" "^7.0.0"
- debug "^3.1.0"
+ "@babel/parser" "^7.2.3"
+ "@babel/types" "^7.2.2"
+ debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.10"
-"@babel/types@^7.0.0", "@babel/types@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0"
- integrity sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg==
+"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2":
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e"
+ integrity sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==
dependencies:
esutils "^2.0.2"
lodash "^4.17.10"
@@ -636,15 +653,15 @@
eslint-plugin-promise "^4.0.1"
eslint-plugin-vue "^5.0.0"
-"@gitlab/svgs@^1.43.0":
- version "1.43.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.43.0.tgz#28dee2122d068cd3b925cd9884d97465ebaca12d"
- integrity sha512-wuN3NITmyBV9bOXsFfAjtndFrjTlH6Kf3+6aqT5kHKKLe/B4w7uTU1L9H/cyR0wGD7HbOh584a05eDcuH04fPA==
+"@gitlab/svgs@^1.47.0":
+ version "1.47.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.47.0.tgz#c03dda41aefd3889cbfed95a391836106ae2ac4d"
+ integrity sha512-0Bx/HxqR8xpqqaLnZiFAHIh1jTAFQPFToVZ6Wi3QyhsAwmXRAbgw1SlkRMZ7w3e6l+G71Wnw+GnI4rx1gK8JLQ==
-"@gitlab/ui@^1.18.0":
- version "1.18.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-1.18.0.tgz#5cc591b2c7958e59fa7b1b443d4235e0e8f956c9"
- integrity sha512-JqmiRSGYmK0DbGBQJBpjeRrcgjK25rCqG6QW6/GPTVLtRjbPPZYGvVg5PyA6nJUGAnwFoeApUZVML6X3OpnV1Q==
+"@gitlab/ui@^1.20.0":
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-1.20.0.tgz#50bd4b092646a2c6337f0f462779af8e702dda05"
+ integrity sha512-EJgrqon/tYCUPoOgnNNAXbrDXOEAajJwKHr4aR2R6vkJI3kVZiq66RNIe5ftGIUoNqYCDnRIkpLyo7MqzJPgcw==
dependencies:
babel-standalone "^6.26.0"
bootstrap-vue "^2.0.0-rc.11"
@@ -662,6 +679,11 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
+"@types/anymatch@*":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.0.tgz#d1d55958d1fccc5527d4aba29fc9c4b942f563ff"
+ integrity sha512-7WcbyctkE8GTzogDb0ulRAEw7v8oIS54ft9mQTU7PfM0hp5e+8kpa+HeQ7IQrFbKtJXBKcZ4bh+Em9dTw5L6AQ==
+
"@types/async@2.0.50":
version "2.0.50"
resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.50.tgz#117540e026d64e1846093abbd5adc7e27fda7bcb"
@@ -716,6 +738,29 @@
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
+"@types/tapable@*":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
+ integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==
+
+"@types/uglify-js@*":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082"
+ integrity sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==
+ dependencies:
+ source-map "^0.6.1"
+
+"@types/webpack@^4.4.19":
+ version "4.4.23"
+ resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.23.tgz#059d6f4598cfd65ddee0e2db38317ef989696712"
+ integrity sha512-WswyG+2mRg0ul/ytPpCSWo+kOlVVPW/fKCBEVwqmPVC/2ffWEwhsCEQgnFbWDf8EWId2qGcpL623EjLfNTRk9A==
+ dependencies:
+ "@types/anymatch" "*"
+ "@types/node" "*"
+ "@types/tapable" "*"
+ "@types/uglify-js" "*"
+ source-map "^0.6.0"
+
"@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"
@@ -743,151 +788,147 @@
dependencies:
lodash "^4.17.4"
-"@webassemblyjs/ast@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.6.tgz#3ef8c45b3e5e943a153a05281317474fef63e21e"
- integrity sha512-8nkZS48EVsMUU0v6F1LCIOw4RYWLm2plMtbhFTjNgeXmsTNLuU3xTRtnljt9BFQB+iPbLRobkNrCWftWnNC7wQ==
- dependencies:
- "@webassemblyjs/helper-module-context" "1.7.6"
- "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
- "@webassemblyjs/wast-parser" "1.7.6"
- mamacro "^0.0.3"
-
-"@webassemblyjs/floating-point-hex-parser@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.6.tgz#7cb37d51a05c3fe09b464ae7e711d1ab3837801f"
- integrity sha512-VBOZvaOyBSkPZdIt5VBMg3vPWxouuM13dPXGWI1cBh3oFLNcFJ8s9YA7S9l4mPI7+Q950QqOmqj06oa83hNWBA==
-
-"@webassemblyjs/helper-api-error@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.6.tgz#99b7e30e66f550a2638299a109dda84a622070ef"
- integrity sha512-SCzhcQWHXfrfMSKcj8zHg1/kL9kb3aa5TN4plc/EREOs5Xop0ci5bdVBApbk2yfVi8aL+Ly4Qpp3/TRAUInjrg==
-
-"@webassemblyjs/helper-buffer@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.6.tgz#ba0648be12bbe560c25c997e175c2018df39ca3e"
- integrity sha512-1/gW5NaGsEOZ02fjnFiU8/OEEXU1uVbv2um0pQ9YVL3IHSkyk6xOwokzyqqO1qDZQUAllb+V8irtClPWntbVqw==
-
-"@webassemblyjs/helper-code-frame@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.6.tgz#5a94d21b0057b69a7403fca0c253c3aaca95b1a5"
- integrity sha512-+suMJOkSn9+vEvDvgyWyrJo5vJsWSDXZmJAjtoUq4zS4eqHyXImpktvHOZwXp1XQjO5H+YQwsBgqTQEc0J/5zg==
- dependencies:
- "@webassemblyjs/wast-printer" "1.7.6"
-
-"@webassemblyjs/helper-fsm@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.6.tgz#ae1741c6f6121213c7a0b587fb964fac492d3e49"
- integrity sha512-HCS6KN3wgxUihGBW7WFzEC/o8Eyvk0d56uazusnxXthDPnkWiMv+kGi9xXswL2cvfYfeK5yiM17z2K5BVlwypw==
-
-"@webassemblyjs/helper-module-context@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.6.tgz#116d19a51a6cebc8900ad53ca34ff8269c668c23"
- integrity sha512-e8/6GbY7OjLM+6OsN7f2krC2qYVNaSr0B0oe4lWdmq5sL++8dYDD1TFbD1TdAdWMRTYNr/Qq7ovXWzia2EbSjw==
- dependencies:
- mamacro "^0.0.3"
-
-"@webassemblyjs/helper-wasm-bytecode@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.6.tgz#98e515eaee611aa6834eb5f6a7f8f5b29fefb6f1"
- integrity sha512-PzYFCb7RjjSdAOljyvLWVqd6adAOabJW+8yRT+NWhXuf1nNZWH+igFZCUK9k7Cx7CsBbzIfXjJc7u56zZgFj9Q==
-
-"@webassemblyjs/helper-wasm-section@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.6.tgz#783835867bdd686df7a95377ab64f51a275e8333"
- integrity sha512-3GS628ppDPSuwcYlQ7cDCGr4W2n9c4hLzvnRKeuz+lGsJSmc/ADVoYpm1ts2vlB1tGHkjtQMni+yu8mHoMlKlA==
- dependencies:
- "@webassemblyjs/ast" "1.7.6"
- "@webassemblyjs/helper-buffer" "1.7.6"
- "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
- "@webassemblyjs/wasm-gen" "1.7.6"
-
-"@webassemblyjs/ieee754@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.6.tgz#c34fc058f2f831fae0632a8bb9803cf2d3462eb1"
- integrity sha512-V4cIp0ruyw+hawUHwQLn6o2mFEw4t50tk530oKsYXQhEzKR+xNGDxs/SFFuyTO7X3NzEu4usA3w5jzhl2RYyzQ==
+"@webassemblyjs/ast@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace"
+ integrity sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==
+ dependencies:
+ "@webassemblyjs/helper-module-context" "1.7.11"
+ "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
+ "@webassemblyjs/wast-parser" "1.7.11"
+
+"@webassemblyjs/floating-point-hex-parser@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz#a69f0af6502eb9a3c045555b1a6129d3d3f2e313"
+ integrity sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==
+
+"@webassemblyjs/helper-api-error@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz#c7b6bb8105f84039511a2b39ce494f193818a32a"
+ integrity sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==
+
+"@webassemblyjs/helper-buffer@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz#3122d48dcc6c9456ed982debe16c8f37101df39b"
+ integrity sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==
+
+"@webassemblyjs/helper-code-frame@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz#cf8f106e746662a0da29bdef635fcd3d1248364b"
+ integrity sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==
+ dependencies:
+ "@webassemblyjs/wast-printer" "1.7.11"
+
+"@webassemblyjs/helper-fsm@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz#df38882a624080d03f7503f93e3f17ac5ac01181"
+ integrity sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==
+
+"@webassemblyjs/helper-module-context@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz#d874d722e51e62ac202476935d649c802fa0e209"
+ integrity sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==
+
+"@webassemblyjs/helper-wasm-bytecode@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz#dd9a1e817f1c2eb105b4cf1013093cb9f3c9cb06"
+ integrity sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==
+
+"@webassemblyjs/helper-wasm-section@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz#9c9ac41ecf9fbcfffc96f6d2675e2de33811e68a"
+ integrity sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==
+ dependencies:
+ "@webassemblyjs/ast" "1.7.11"
+ "@webassemblyjs/helper-buffer" "1.7.11"
+ "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
+ "@webassemblyjs/wasm-gen" "1.7.11"
+
+"@webassemblyjs/ieee754@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz#c95839eb63757a31880aaec7b6512d4191ac640b"
+ integrity sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==
dependencies:
"@xtuc/ieee754" "^1.2.0"
-"@webassemblyjs/leb128@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.6.tgz#197f75376a29f6ed6ace15898a310d871d92f03b"
- integrity sha512-ojdlG8WpM394lBow4ncTGJoIVZ4aAtNOWHhfAM7m7zprmkVcKK+2kK5YJ9Bmj6/ketTtOn7wGSHCtMt+LzqgYQ==
+"@webassemblyjs/leb128@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.11.tgz#d7267a1ee9c4594fd3f7e37298818ec65687db63"
+ integrity sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==
dependencies:
"@xtuc/long" "4.2.1"
-"@webassemblyjs/utf8@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.6.tgz#eb62c66f906af2be70de0302e29055d25188797d"
- integrity sha512-oId+tLxQ+AeDC34ELRYNSqJRaScB0TClUU6KQfpB8rNT6oelYlz8axsPhf6yPTg7PBJ/Z5WcXmUYiHEWgbbHJw==
-
-"@webassemblyjs/wasm-edit@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.6.tgz#fa41929160cd7d676d4c28ecef420eed5b3733c5"
- integrity sha512-pTNjLO3o41v/Vz9VFLl+I3YLImpCSpodFW77pNoH4agn5I6GgSxXHXtvWDTvYJFty0jSeXZWLEmbaSIRUDlekg==
- dependencies:
- "@webassemblyjs/ast" "1.7.6"
- "@webassemblyjs/helper-buffer" "1.7.6"
- "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
- "@webassemblyjs/helper-wasm-section" "1.7.6"
- "@webassemblyjs/wasm-gen" "1.7.6"
- "@webassemblyjs/wasm-opt" "1.7.6"
- "@webassemblyjs/wasm-parser" "1.7.6"
- "@webassemblyjs/wast-printer" "1.7.6"
-
-"@webassemblyjs/wasm-gen@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.6.tgz#695ac38861ab3d72bf763c8c75e5f087ffabc322"
- integrity sha512-mQvFJVumtmRKEUXMohwn8nSrtjJJl6oXwF3FotC5t6e2hlKMh8sIaW03Sck2MDzw9xPogZD7tdP5kjPlbH9EcQ==
- dependencies:
- "@webassemblyjs/ast" "1.7.6"
- "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
- "@webassemblyjs/ieee754" "1.7.6"
- "@webassemblyjs/leb128" "1.7.6"
- "@webassemblyjs/utf8" "1.7.6"
-
-"@webassemblyjs/wasm-opt@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.6.tgz#fbafa78e27e1a75ab759a4b658ff3d50b4636c21"
- integrity sha512-go44K90fSIsDwRgtHhX14VtbdDPdK2sZQtZqUcMRvTojdozj5tLI0VVJAzLCfz51NOkFXezPeVTAYFqrZ6rI8Q==
- dependencies:
- "@webassemblyjs/ast" "1.7.6"
- "@webassemblyjs/helper-buffer" "1.7.6"
- "@webassemblyjs/wasm-gen" "1.7.6"
- "@webassemblyjs/wasm-parser" "1.7.6"
-
-"@webassemblyjs/wasm-parser@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.6.tgz#84eafeeff405ad6f4c4b5777d6a28ae54eed51fe"
- integrity sha512-t1T6TfwNY85pDA/HWPA8kB9xA4sp9ajlRg5W7EKikqrynTyFo+/qDzIpvdkOkOGjlS6d4n4SX59SPuIayR22Yg==
- dependencies:
- "@webassemblyjs/ast" "1.7.6"
- "@webassemblyjs/helper-api-error" "1.7.6"
- "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
- "@webassemblyjs/ieee754" "1.7.6"
- "@webassemblyjs/leb128" "1.7.6"
- "@webassemblyjs/utf8" "1.7.6"
-
-"@webassemblyjs/wast-parser@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.6.tgz#ca4d20b1516e017c91981773bd7e819d6bd9c6a7"
- integrity sha512-1MaWTErN0ziOsNUlLdvwS+NS1QWuI/kgJaAGAMHX8+fMJFgOJDmN/xsG4h/A1Gtf/tz5VyXQciaqHZqp2q0vfg==
- dependencies:
- "@webassemblyjs/ast" "1.7.6"
- "@webassemblyjs/floating-point-hex-parser" "1.7.6"
- "@webassemblyjs/helper-api-error" "1.7.6"
- "@webassemblyjs/helper-code-frame" "1.7.6"
- "@webassemblyjs/helper-fsm" "1.7.6"
+"@webassemblyjs/utf8@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.11.tgz#06d7218ea9fdc94a6793aa92208160db3d26ee82"
+ integrity sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==
+
+"@webassemblyjs/wasm-edit@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz#8c74ca474d4f951d01dbae9bd70814ee22a82005"
+ integrity sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==
+ dependencies:
+ "@webassemblyjs/ast" "1.7.11"
+ "@webassemblyjs/helper-buffer" "1.7.11"
+ "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
+ "@webassemblyjs/helper-wasm-section" "1.7.11"
+ "@webassemblyjs/wasm-gen" "1.7.11"
+ "@webassemblyjs/wasm-opt" "1.7.11"
+ "@webassemblyjs/wasm-parser" "1.7.11"
+ "@webassemblyjs/wast-printer" "1.7.11"
+
+"@webassemblyjs/wasm-gen@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz#9bbba942f22375686a6fb759afcd7ac9c45da1a8"
+ integrity sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==
+ dependencies:
+ "@webassemblyjs/ast" "1.7.11"
+ "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
+ "@webassemblyjs/ieee754" "1.7.11"
+ "@webassemblyjs/leb128" "1.7.11"
+ "@webassemblyjs/utf8" "1.7.11"
+
+"@webassemblyjs/wasm-opt@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz#b331e8e7cef8f8e2f007d42c3a36a0580a7d6ca7"
+ integrity sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==
+ dependencies:
+ "@webassemblyjs/ast" "1.7.11"
+ "@webassemblyjs/helper-buffer" "1.7.11"
+ "@webassemblyjs/wasm-gen" "1.7.11"
+ "@webassemblyjs/wasm-parser" "1.7.11"
+
+"@webassemblyjs/wasm-parser@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz#6e3d20fa6a3519f6b084ef9391ad58211efb0a1a"
+ integrity sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==
+ dependencies:
+ "@webassemblyjs/ast" "1.7.11"
+ "@webassemblyjs/helper-api-error" "1.7.11"
+ "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
+ "@webassemblyjs/ieee754" "1.7.11"
+ "@webassemblyjs/leb128" "1.7.11"
+ "@webassemblyjs/utf8" "1.7.11"
+
+"@webassemblyjs/wast-parser@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz#25bd117562ca8c002720ff8116ef9072d9ca869c"
+ integrity sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.7.11"
+ "@webassemblyjs/floating-point-hex-parser" "1.7.11"
+ "@webassemblyjs/helper-api-error" "1.7.11"
+ "@webassemblyjs/helper-code-frame" "1.7.11"
+ "@webassemblyjs/helper-fsm" "1.7.11"
"@xtuc/long" "4.2.1"
- mamacro "^0.0.3"
-"@webassemblyjs/wast-printer@1.7.6":
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.6.tgz#a6002c526ac5fa230fe2c6d2f1bdbf4aead43a5e"
- integrity sha512-vHdHSK1tOetvDcl1IV1OdDeGNe/NDDQ+KzuZHMtqTVP1xO/tZ/IKNpj5BaGk1OYFdsDWQqb31PIwdEyPntOWRQ==
+"@webassemblyjs/wast-printer@1.7.11":
+ version "1.7.11"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz#c4245b6de242cb50a2cc950174fdbf65c78d7813"
+ integrity sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==
dependencies:
- "@webassemblyjs/ast" "1.7.6"
- "@webassemblyjs/wast-parser" "1.7.6"
+ "@webassemblyjs/ast" "1.7.11"
+ "@webassemblyjs/wast-parser" "1.7.11"
"@xtuc/long" "4.2.1"
"@xtuc/ieee754@^1.2.0":
@@ -923,12 +964,10 @@ accepts@~1.3.4, accepts@~1.3.5:
mime-types "~2.1.18"
negotiator "0.6.1"
-acorn-dynamic-import@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz#901ceee4c7faaef7e07ad2a47e890675da50a278"
- integrity sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==
- dependencies:
- acorn "^5.0.0"
+acorn-dynamic-import@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948"
+ integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==
acorn-globals@^4.1.0:
version "4.3.0"
@@ -948,15 +987,15 @@ acorn-walk@^6.0.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913"
integrity sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==
-acorn@^5.0.0, acorn@^5.5.3, acorn@^5.6.2, acorn@^5.7.3:
+acorn@^5.5.3, acorn@^5.7.3:
version "5.7.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
-acorn@^6.0.1, acorn@^6.0.2:
- version "6.0.4"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.4.tgz#77377e7353b72ec5104550aa2d2097a2fd40b754"
- integrity sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==
+acorn@^6.0.1, acorn@^6.0.2, acorn@^6.0.5:
+ version "6.0.5"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.5.tgz#81730c0815f3f3b34d8efa95cb7430965f4d887a"
+ integrity sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==
after@0.8.2:
version "0.8.2"
@@ -1436,12 +1475,12 @@ babel-jest@^23.6.0:
babel-plugin-istanbul "^4.1.6"
babel-preset-jest "^23.2.0"
-babel-loader@^8.0.4:
- version "8.0.4"
- resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.4.tgz#7bbf20cbe4560629e2e41534147692d3fecbdce6"
- integrity sha512-fhBhNkUToJcW9nV46v8w87AJOwAJDz84c1CL57n3Stj73FANM/b9TbCUK4YhdOwEyZ+OxhYpdeZDNzSI29Firw==
+babel-loader@^8.0.5:
+ version "8.0.5"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.5.tgz#225322d7509c2157655840bba52e46b6c2f2fe33"
+ integrity sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==
dependencies:
- find-cache-dir "^1.0.0"
+ find-cache-dir "^2.0.0"
loader-utils "^1.0.2"
mkdirp "^0.5.1"
util.promisify "^1.0.0"
@@ -1591,6 +1630,11 @@ babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.24.1, babel-types@^6.26.
lodash "^4.17.4"
to-fast-properties "^1.0.3"
+babylon@7.0.0-beta.19:
+ version "7.0.0-beta.19"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503"
+ integrity sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==
+
babylon@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
@@ -1683,10 +1727,10 @@ blob@0.0.4:
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=
-bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1:
- version "3.5.1"
- resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
- integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
+bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@~3.5.0:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
+ integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8"
@@ -1868,14 +1912,14 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"
-browserslist@^4.1.0:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.1.1.tgz#328eb4ff1215b12df6589e9ab82f8adaa4fc8cd6"
- integrity sha512-VBorw+tgpOtZ1BYhrVSVTzTt/3+vSE3eFUh0N2GCFK1HffceOaf32YS/bs6WiFhjDAblAFrx85jMy3BG9fBK2Q==
+browserslist@^4.3.4:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.3.7.tgz#f1de479a6466ea47a0a26dcc725e7504817e624a"
+ integrity sha512-pWQv51Ynb0MNk9JGMCZ8VkM785/4MQNXiFYtPqI7EEP0TJO+/d/NqRVn1uiAN0DNbnlUSpL2sh16Kspasv3pUQ==
dependencies:
- caniuse-lite "^1.0.30000884"
- electron-to-chromium "^1.3.62"
- node-releases "^1.0.0-alpha.11"
+ caniuse-lite "^1.0.30000925"
+ electron-to-chromium "^1.3.96"
+ node-releases "^1.1.3"
bser@^2.0.0:
version "2.0.0"
@@ -1923,43 +1967,24 @@ bytes@3.0.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
-cacache@^10.0.4:
- version "10.0.4"
- resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
- integrity sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==
+cacache@^11.0.2, cacache@^11.2.0:
+ version "11.3.2"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa"
+ integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==
dependencies:
- bluebird "^3.5.1"
- chownr "^1.0.1"
- glob "^7.1.2"
- graceful-fs "^4.1.11"
- lru-cache "^4.1.1"
- mississippi "^2.0.0"
- mkdirp "^0.5.1"
- move-concurrently "^1.0.1"
- promise-inflight "^1.0.1"
- rimraf "^2.6.2"
- ssri "^5.2.4"
- unique-filename "^1.1.0"
- y18n "^4.0.0"
-
-cacache@^11.2.0:
- version "11.2.0"
- resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.2.0.tgz#617bdc0b02844af56310e411c0878941d5739965"
- integrity sha512-IFWl6lfK6wSeYCHUXh+N1lY72UDrpyrYQJNIVQf48paDuWbv5RbAtJYf/4gUQFObTCHZwdZ5sI8Iw7nqwP6nlQ==
- dependencies:
- bluebird "^3.5.1"
- chownr "^1.0.1"
- figgy-pudding "^3.1.0"
- glob "^7.1.2"
- graceful-fs "^4.1.11"
- lru-cache "^4.1.3"
+ bluebird "^3.5.3"
+ chownr "^1.1.1"
+ figgy-pudding "^3.5.1"
+ glob "^7.1.3"
+ graceful-fs "^4.1.15"
+ lru-cache "^5.1.1"
mississippi "^3.0.0"
mkdirp "^0.5.1"
move-concurrently "^1.0.1"
promise-inflight "^1.0.1"
rimraf "^2.6.2"
- ssri "^6.0.0"
- unique-filename "^1.1.0"
+ ssri "^6.0.1"
+ unique-filename "^1.1.1"
y18n "^4.0.0"
cache-base@^1.0.1:
@@ -1977,15 +2002,16 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"
-cache-loader@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/cache-loader/-/cache-loader-1.2.2.tgz#6d5c38ded959a09cc5d58190ab5af6f73bd353f5"
- integrity sha512-rsGh4SIYyB9glU+d0OcHwiXHXBoUgDhHZaQ1KAbiXqfz1CDPxtTboh1gPbJ0q2qdO8a9lfcjgC5CJ2Ms32y5bw==
+cache-loader@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/cache-loader/-/cache-loader-2.0.1.tgz#5758f41a62d7c23941e3c3c7016e6faeb03acb07"
+ integrity sha512-V99T3FOynmGx26Zom+JrVBytLBsmUCzVG2/4NnUKgvXN4bEV42R1ERl1IyiH/cvFIDA1Ytq2lPZ9tXDSahcQpQ==
dependencies:
loader-utils "^1.1.0"
mkdirp "^0.5.1"
- neo-async "^2.5.0"
- schema-utils "^0.4.2"
+ neo-async "^2.6.0"
+ normalize-path "^3.0.0"
+ schema-utils "^1.0.0"
cacheable-request@^2.1.1:
version "2.1.4"
@@ -2027,10 +2053,15 @@ camelcase@^4.0.0, camelcase@^4.1.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
-caniuse-lite@^1.0.30000884:
- version "1.0.30000888"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000888.tgz#22edb50d91dd70612b5898e3b36f460600c6492f"
- integrity sha512-vftg+5p/lPsQGpnhSo/yBuYL36ai/cyjLvU3dOPJY1kkKrekLWIy8SLm+wzjX0hpCUdFTasC4/ZT7uqw4rKOnQ==
+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.30000925:
+ version "1.0.30000927"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000927.tgz#114a9de4ff1e01f5790fe578ecd93421c7524665"
+ integrity sha512-ogq4NbUWf1uG/j66k0AmiO3GjqJAlQyF8n4w8a954cbCyFKmYGvRtgz6qkq2fWuduTXHibX7GyYL5Pg58Aks2g==
capture-exit@^1.2.0:
version "1.2.0"
@@ -2049,6 +2080,13 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
+catharsis@~0.8.9:
+ version "0.8.9"
+ resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.8.9.tgz#98cc890ca652dd2ef0e70b37925310ff9e90fc8b"
+ integrity sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=
+ dependencies:
+ underscore-contrib "~0.3.0"
+
chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -2094,7 +2132,7 @@ check-types@^7.3.0:
resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.3.0.tgz#468f571a4435c24248f5fd0cb0e8d87c3c341e7d"
integrity sha1-Ro9XGkQ1wkJI9f0MsOjYfDw0Hn0=
-chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3:
+chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26"
integrity sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==
@@ -2114,10 +2152,10 @@ chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3:
optionalDependencies:
fsevents "^1.2.2"
-chownr@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
- integrity sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=
+chownr@^1.0.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==
chrome-trace-event@^1.0.0:
version "1.0.0"
@@ -2176,6 +2214,16 @@ cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
+cli-table3@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
+ integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
+ dependencies:
+ object-assign "^4.1.0"
+ string-width "^2.1.1"
+ optionalDependencies:
+ colors "^1.1.2"
+
cli-width@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
@@ -2260,10 +2308,10 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
-colors@^1.1.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
- integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
+colors@^1.1.0, colors@^1.1.2:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
+ integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==
combine-lists@^1.0.0:
version "1.0.1"
@@ -2279,16 +2327,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@2, commander@^2.10.0, commander@^2.18.0, commander@^2.19.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.13.0:
- version "2.13.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
- integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==
-
commander@~2.17.1:
version "2.17.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
@@ -2588,15 +2631,15 @@ crypto-random-string@^1.0.0:
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
css-loader@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.0.tgz#9f46aaa5ca41dbe31860e3b62b8e23c42916bf56"
- integrity sha512-tMXlTYf3mIMt3b0dDCOQFJiVvxbocJ5Ho577WiGPYPZcqVEO218L2iU22pDXzkTZCLDE+9AmGSUkWxeh/nZReA==
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.1.tgz#6885bb5233b35ec47b006057da01cc640b6b79fe"
+ integrity sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw==
dependencies:
babel-code-frame "^6.26.0"
css-selector-tokenizer "^0.7.0"
icss-utils "^2.1.0"
loader-utils "^1.0.2"
- lodash.camelcase "^4.3.0"
+ lodash "^4.17.11"
postcss "^6.0.23"
postcss-modules-extract-imports "^1.2.0"
postcss-modules-local-by-default "^1.2.0"
@@ -2977,7 +3020,7 @@ debug@~3.1.0:
dependencies:
ms "2.0.0"
-decamelize@^1.1.1:
+decamelize@^1.1.1, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -2989,10 +3032,10 @@ decamelize@^2.0.0:
dependencies:
xregexp "4.0.0"
-deckar01-task_list@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/deckar01-task_list/-/deckar01-task_list-2.0.0.tgz#7f7a595430d21b3036ed5dfbf97d6b65de18e2c9"
- integrity sha1-f3pZVDDSGzA27V37+X1rZd4Y4sk=
+deckar01-task_list@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/deckar01-task_list/-/deckar01-task_list-2.0.1.tgz#fdcfb6ab5717055a82f29e863a49990a043a06a9"
+ integrity sha512-i5fT8QxJ9iV6dfgy5U0NHW91O5cKsvDc4u8JNMnZ6efQc356bA9vKuXO3732agSry+bO6TolzTmuqSRi4tkkeA==
decode-uri-component@^0.2.0:
version "0.2.0"
@@ -3135,6 +3178,11 @@ destroy@~1.0.4:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+detect-file@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
+ integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
+
detect-indent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
@@ -3152,10 +3200,10 @@ detect-newline@^2.1.0:
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=
-detect-node@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127"
- integrity sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=
+detect-node@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
+ integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
di@^0.0.1:
version "0.0.1"
@@ -3196,6 +3244,11 @@ dns-txt@^2.0.2:
dependencies:
buffer-indexof "^1.0.0"
+docdash@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/docdash/-/docdash-1.0.2.tgz#0449a8f6bb247f563020b78a5485dea95ae2e094"
+ integrity sha512-IEM57bWPLtVXpUeCKbiGvHsHtW9O9ZiiBPfeQDAZ7JdQiAF3aNWQoJ3e/+uJ63lHO009yn0tbJjtKpXJ2AURCQ==
+
doctrine@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -3288,7 +3341,7 @@ duplexer3@^0.1.4:
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
-duplexer@^0.1.1, duplexer@~0.1.1:
+duplexer@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
@@ -3345,10 +3398,10 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
-electron-to-chromium@^1.3.62:
- version "1.3.73"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.73.tgz#aa67787067d58cc3920089368b3b8d6fe0fc12f6"
- integrity sha512-6PIg7v9zRoVGh6EheRF8h6Plti+3Yo/qtHobS4/Htyt53DNHmKKGFqSae1AIk0k1S4gCQvt7I2WgpbuZNcDY+g==
+electron-to-chromium@^1.3.96:
+ version "1.3.100"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.100.tgz#899fb088def210aee6b838a47655bbb299190e13"
+ integrity sha512-cEUzis2g/RatrVf8x26L8lK5VEls1AGnLHk6msluBUg/NTB4wcXzExTsGscFq+Vs4WBBU2zbLLySvD4C0C3hwg==
elliptic@^6.0.0:
version "6.4.0"
@@ -3432,7 +3485,7 @@ engine.io@~3.2.0:
engine.io-parser "~2.1.0"
ws "~3.3.1"
-enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0:
+enhanced-resolve@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==
@@ -3504,7 +3557,7 @@ escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5, escape-string-regexp@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
@@ -3788,19 +3841,6 @@ eve-raphael@0.5.0:
resolved "https://registry.yarnpkg.com/eve-raphael/-/eve-raphael-0.5.0.tgz#17c754b792beef3fa6684d79cf5a47c63c4cda30"
integrity sha1-F8dUt5K+7z+maE15z1pHxjxM2jA=
-event-stream@~3.3.0:
- version "3.3.4"
- resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571"
- integrity sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=
- dependencies:
- duplexer "~0.1.1"
- from "~0"
- map-stream "~0.1.0"
- pause-stream "0.0.11"
- split "0.3"
- stream-combiner "~0.0.4"
- through "~2.3.1"
-
eventemitter3@1.x.x:
version "1.2.0"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
@@ -3908,6 +3948,13 @@ expand-range@^1.8.1:
dependencies:
fill-range "^2.1.0"
+expand-tilde@^2.0.0, expand-tilde@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
+ integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=
+ dependencies:
+ homedir-polyfill "^1.0.1"
+
expect@^23.6.0:
version "23.6.0"
resolved "https://registry.yarnpkg.com/expect/-/expect-23.6.0.tgz#1e0c8d3ba9a581c87bd71fb9bc8862d443425f98"
@@ -4055,6 +4102,13 @@ fastparse@^1.1.1:
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
integrity sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=
+fault@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa"
+ integrity sha512-o2eo/X2syzzERAtN5LcGbiVQ0WwZSlN3qLtadwAz3X8Bu+XWD16dja/KMsjZLiQr+BLGPDnHGkc4yUJf1Xpkpw==
+ dependencies:
+ format "^0.2.2"
+
faye-websocket@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
@@ -4076,7 +4130,7 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"
-figgy-pudding@^3.1.0, figgy-pudding@^3.5.1:
+figgy-pudding@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
@@ -4096,10 +4150,10 @@ file-entry-cache@^2.0.0:
flat-cache "^1.2.1"
object-assign "^4.0.1"
-file-loader@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-2.0.0.tgz#39749c82f020b9e85901dcff98e8004e6401cfde"
- integrity sha512-YCsBfd1ZGCyonOKLxPiKPdu+8ld9HAaMEvJewzz+b2eTF7uL5Zm/HdBF6FjCrpCMRq25Mi0U1gl4pwn2TlH7hQ==
+file-loader@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa"
+ integrity sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==
dependencies:
loader-utils "^1.0.2"
schema-utils "^1.0.0"
@@ -4177,15 +4231,6 @@ find-babel-config@^1.1.0:
json5 "^0.5.1"
path-exists "^3.0.0"
-find-cache-dir@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
- integrity sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=
- dependencies:
- commondir "^1.0.1"
- make-dir "^1.0.0"
- pkg-dir "^2.0.0"
-
find-cache-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d"
@@ -4222,6 +4267,16 @@ find-up@^3.0.0:
dependencies:
locate-path "^3.0.0"
+findup-sync@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc"
+ integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=
+ dependencies:
+ detect-file "^1.0.0"
+ is-glob "^3.1.0"
+ micromatch "^3.0.4"
+ resolve-dir "^1.0.1"
+
flat-cache@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96"
@@ -4273,6 +4328,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
+format@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+ integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=
+
formdata-polyfill@^3.0.11:
version "3.0.11"
resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-3.0.11.tgz#c82b4b4bea3356c0a6752219e54ce1edb2a7fb5b"
@@ -4303,11 +4363,6 @@ from2@^2.1.0, from2@^2.1.1:
inherits "^2.0.1"
readable-stream "^2.0.0"
-from@~0:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
- integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=
-
fs-access@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a"
@@ -4476,10 +4531,30 @@ global-dirs@^0.1.0:
dependencies:
ini "^1.3.4"
-global-modules-path@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.1.0.tgz#923ec524e8726bb0c1a4ed4b8e21e1ff80c88bbb"
- integrity sha512-3DrmGj2TP+96cABk9TfMp6f3knH/Y46dqvWznTU3Tf6/bDGLDAn15tFluQ7BcloykOcdY16U0WGq0BQblYOxJQ==
+global-modules-path@^2.3.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.3.1.tgz#e541f4c800a1a8514a990477b267ac67525b9931"
+ integrity sha512-y+shkf4InI7mPRHSo2b/k6ix6+NLDtyccYv86whhxrSGX9wjPX1VMITmrDbE1eh7zkzhiWtW2sHklJYoQ62Cxg==
+
+global-modules@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
+ integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==
+ dependencies:
+ global-prefix "^1.0.1"
+ is-windows "^1.0.1"
+ resolve-dir "^1.0.0"
+
+global-prefix@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
+ integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=
+ dependencies:
+ expand-tilde "^2.0.2"
+ homedir-polyfill "^1.0.1"
+ ini "^1.3.4"
+ is-windows "^1.0.1"
+ which "^1.2.14"
globals@^11.1.0, globals@^11.7.0:
version "11.7.0"
@@ -4561,10 +4636,10 @@ got@^8.0.3:
url-parse-lax "^3.0.0"
url-to-options "^1.0.1"
-graceful-fs@^4.1.11, graceful-fs@^4.1.2:
- version "4.1.11"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
- integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.9:
+ version "4.1.15"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
+ integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
graphlibrary@^2.2.0:
version "2.2.0"
@@ -4605,10 +4680,10 @@ gzip-size@^5.0.0:
duplexer "^0.1.1"
pify "^3.0.0"
-handle-thing@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
- integrity sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=
+handle-thing@^2.0.0:
+ version "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.0.11, handlebars@^4.0.3:
version "4.0.12"
@@ -4756,7 +4831,7 @@ he@^1.1.0, he@^1.1.1:
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
-highlight.js@^9.13.1:
+highlight.js@^9.13.1, highlight.js@~9.13.0:
version "9.13.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
@@ -4778,6 +4853,13 @@ home-or-tmp@^2.0.0:
os-homedir "^1.0.0"
os-tmpdir "^1.0.1"
+homedir-polyfill@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
+ integrity sha1-TCu8inWJmP7r9e1oWA921GdotLw=
+ dependencies:
+ parse-passwd "^1.0.0"
+
hoopy@^0.1.2:
version "0.1.4"
resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
@@ -5021,7 +5103,7 @@ inquirer@3.0.6:
strip-ansi "^3.0.0"
through "^2.3.6"
-inquirer@^6.0.0, inquirer@^6.1.0:
+inquirer@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.0.tgz#51adcd776f661369dc1e894859c2560a224abdd8"
integrity sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==
@@ -5408,7 +5490,7 @@ is-utf8@^0.2.0:
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
-is-windows@^1.0.2:
+is-windows@^1.0.1, is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
@@ -6043,11 +6125,41 @@ js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.7.0:
argparse "^1.0.7"
esprima "^4.0.0"
+js2xmlparser@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-3.0.0.tgz#3fb60eaa089c5440f9319f51760ccd07e2499733"
+ integrity sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=
+ dependencies:
+ xmlcreate "^1.0.1"
+
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
+jsdoc-vue@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/jsdoc-vue/-/jsdoc-vue-1.0.0.tgz#ff3ac1ba6bc4a74079bb79058a7bf0066e346235"
+ integrity sha1-/zrBumvEp0B5u3kFinvwBm40YjU=
+
+jsdoc@^3.5.5:
+ version "3.5.5"
+ resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.5.5.tgz#484521b126e81904d632ff83ec9aaa096708fa4d"
+ integrity sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==
+ dependencies:
+ babylon "7.0.0-beta.19"
+ bluebird "~3.5.0"
+ catharsis "~0.8.9"
+ escape-string-regexp "~1.0.5"
+ js2xmlparser "~3.0.0"
+ klaw "~2.0.0"
+ marked "~0.3.6"
+ mkdirp "~0.5.1"
+ requizzle "~0.2.1"
+ strip-json-comments "~2.0.1"
+ taffydb "2.6.2"
+ underscore "~1.8.3"
+
jsdom@^11.5.1:
version "11.12.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8"
@@ -6135,6 +6247,13 @@ json5@^0.5.0, json5@^0.5.1:
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
+json5@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850"
+ integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==
+ dependencies:
+ minimist "^1.2.0"
+
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -6250,12 +6369,12 @@ karma@^3.0.0:
tmp "0.0.33"
useragent "2.2.1"
-katex@^0.9.0:
- version "0.9.0"
- resolved "https://registry.yarnpkg.com/katex/-/katex-0.9.0.tgz#26a7d082c21d53725422d2d71da9b2d8455fbd4a"
- integrity sha512-lp3x90LT1tDZBW2tjLheJ98wmRMRjUHwk4QpaswT9bhqoQZ+XA4cPcjcQBxgOQNwaOSt6ZeL/a6GKQ1of3LFxQ==
+katex@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/katex/-/katex-0.10.0.tgz#da562e5d0d5cc3aa602e27af8a9b8710bfbce765"
+ integrity sha512-/WRvx+L1eVBrLwX7QzKU1dQuaGnE7E8hDvx3VWfZh9HbMiCfsKWJNnYZ0S8ZMDAfAyDSofdyXIrH/hujF1fYXg==
dependencies:
- match-at "^0.1.1"
+ commander "^2.16.0"
keyv@3.0.0:
version "3.0.0"
@@ -6293,6 +6412,13 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
+klaw@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/klaw/-/klaw-2.0.0.tgz#59c128e0dc5ce410201151194eeb9cbf858650f6"
+ integrity sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=
+ dependencies:
+ graceful-fs "^4.1.9"
+
kleur@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
@@ -6351,6 +6477,18 @@ lie@~3.1.0:
dependencies:
immediate "~3.0.5"
+lightercollective@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/lightercollective/-/lightercollective-0.1.0.tgz#70df102c530dcb8d0ccabfe6175a8d00d5f61300"
+ integrity sha512-J9tg5uraYoQKaWbmrzDDexbG6hHnMcWS1qLYgJSWE+mpA3U5OCSeMUhb+K55otgZJ34oFdR0ECvdIb3xuO5JOQ==
+
+linkify-it@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db"
+ integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==
+ dependencies:
+ uc.micro "^1.0.1"
+
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -6412,7 +6550,7 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
-lodash.camelcase@4.3.0, lodash.camelcase@^4.3.0:
+lodash.camelcase@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
@@ -6512,12 +6650,20 @@ lowercase-keys@1.0.0, lowercase-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=
+lowlight@^1.11.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc"
+ integrity sha512-xrGGN6XLL7MbTMdPD6NfWPwY43SNkjf/d0mecSx/CW36fUZTjRHEq0/Cdug3TWKtRXLWi7iMl1eP0olYxj/a4A==
+ dependencies:
+ fault "^1.0.2"
+ highlight.js "~9.13.0"
+
lru-cache@2.2.x:
version "2.2.4"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
integrity sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=
-lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
+lru-cache@^4.0.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
integrity sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==
@@ -6525,6 +6671,13 @@ lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
pseudomap "^1.0.2"
yallist "^2.1.2"
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
@@ -6544,11 +6697,6 @@ makeerror@1.0.x:
dependencies:
tmpl "1.0.x"
-mamacro@^0.0.3:
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4"
- integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==
-
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"
@@ -6561,11 +6709,6 @@ map-cache@^0.2.2:
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
-map-stream@~0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194"
- integrity sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=
-
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
@@ -6573,15 +6716,21 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
-marked@^0.3.12:
- version "0.3.12"
- resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.12.tgz#7cf25ff2252632f3fe2406bde258e94eee927519"
- integrity sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==
+markdown-it@^8.4.2:
+ version "8.4.2"
+ resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54"
+ integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==
+ dependencies:
+ argparse "^1.0.7"
+ entities "~1.1.1"
+ linkify-it "^2.0.0"
+ mdurl "^1.0.1"
+ uc.micro "^1.0.5"
-match-at@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.1.tgz#25d040d291777704d5e6556bbb79230ec2de0540"
- integrity sha512-h4Yd392z9mST+dzc+yjuybOGFNOZjmXIPKWjxBd1Bb23r4SmDOsk2NYCU2BMUBGbSpZqwVsZYNq26QS3xfaT3Q==
+marked@^0.3.12, marked@~0.3.6:
+ version "0.3.19"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
+ integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
math-random@^1.0.1:
version "1.0.1"
@@ -6596,6 +6745,11 @@ md5.js@^1.3.4:
hash-base "^3.0.0"
inherits "^2.0.1"
+mdurl@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
+ integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -6692,7 +6846,7 @@ micromatch@^2.3.11:
parse-glob "^3.0.4"
regex-cache "^0.4.2"
-micromatch@^3.1.4, micromatch@^3.1.6, micromatch@^3.1.8, micromatch@^3.1.9:
+micromatch@^3.0.4, micromatch@^3.1.4, micromatch@^3.1.6, micromatch@^3.1.8, micromatch@^3.1.9:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
@@ -6793,22 +6947,6 @@ minizlib@^1.1.0:
dependencies:
minipass "^2.2.1"
-mississippi@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f"
- integrity sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==
- dependencies:
- concat-stream "^1.5.0"
- duplexify "^3.4.2"
- end-of-stream "^1.1.0"
- flush-write-stream "^1.0.0"
- from2 "^2.1.0"
- parallel-transform "^1.1.0"
- pump "^2.0.1"
- pumpify "^1.3.3"
- stream-each "^1.1.0"
- through2 "^2.0.0"
-
mississippi@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
@@ -6833,7 +6971,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
-mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
+mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@@ -6845,15 +6983,17 @@ moment@2.x, moment@^2.21.0:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=
-monaco-editor-webpack-plugin@^1.5.4:
- version "1.5.4"
- resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.5.4.tgz#6781a130e3e1379bb8f4cd190132f4af6dcd2c16"
- integrity sha512-9YmWYQdZoAoZ1RLy/uvoDbCcb0EKy5O2qoMQn+UIVQxk+VTCXfJDgANczDIWko+UOzg0MY0P+sA8bl4XI14RJg==
+monaco-editor-webpack-plugin@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.7.0.tgz#920cbeecca25f15d70d568a7e11b0ba4daf1ae83"
+ integrity sha512-oItymcnlL14Sjd7EF7q+CMhucfwR/2BxsqrXIBrWL6LQplFfAfV+grLEQRmVHeGSBZ/Gk9ptzfueXnWcoEcFuA==
+ dependencies:
+ "@types/webpack" "^4.4.19"
-monaco-editor@^0.14.3:
- version "0.14.3"
- resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.14.3.tgz#7cc4a4096a3821f52fea9b10489b527ef3034e22"
- integrity sha512-RhaO4xXmWn/p0WrkEOXe4PoZj6xOcvDYjoAh0e1kGUrQnP1IOpc0m86Ceuaa2CLEMDINqKijBSmqhvBQnsPLHQ==
+monaco-editor@^0.15.6:
+ version "0.15.6"
+ resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.15.6.tgz#d63b3b06f86f803464f003b252627c3eb4a09483"
+ integrity sha512-JoU9V9k6KqT9R9Tiw1RTU8ohZ+Xnf9DMg6Ktqqw5hILumwmq7xqa/KLXw513uTUsWbhtnHoSJYYR++u3pkyxJg==
mousetrap@^1.4.6:
version "1.4.6"
@@ -6942,10 +7082,10 @@ negotiator@0.6.1:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
-neo-async@^2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f"
- integrity sha512-nJmSswG4As/MkRq7QZFuH/sf/yuv8ODdMZrY4Bedjp77a5MK4A6s7YbBB64c9u79EBUOfXUXBvArmvzTD0X+6g==
+neo-async@^2.5.0, neo-async@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
+ integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
nice-try@^1.0.4:
version "1.0.4"
@@ -7033,28 +7173,28 @@ node-pre-gyp@^0.10.0:
semver "^5.3.0"
tar "^4"
-node-releases@^1.0.0-alpha.11:
- version "1.0.0-alpha.12"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.0-alpha.12.tgz#32e461b879ea76ac674e511d9832cf29da345268"
- integrity sha512-VPB4rTPqpVyWKBHbSa4YPFme3+8WHsOSpvbp0Mfj0bWsC8TEjt4HQrLl1hsBDELlp1nB4lflSgSuGTYiuyaP7Q==
+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"
-nodemon@^1.18.4:
- version "1.18.4"
- resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.4.tgz#873f65fdb53220eb166180cf106b1354ac5d714d"
- integrity sha512-hyK6vl65IPnky/ee+D3IWvVGgJa/m3No2/Xc/3wanS6Ce1MWjCzH6NnhPJ/vZM+6JFym16jtHx51lmCMB9HDtg==
+nodemon@^1.18.9:
+ version "1.18.9"
+ resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.9.tgz#90b467efd3b3c81b9453380aeb2a2cba535d0ead"
+ integrity sha512-oj/eEVTEI47pzYAjGkpcNw0xYwTl4XSTUQv2NPQI6PpN3b75PhpuYk3Vb3U80xHCyM2Jm+1j68ULHXl4OR3Afw==
dependencies:
- chokidar "^2.0.2"
+ chokidar "^2.0.4"
debug "^3.1.0"
ignore-by-default "^1.0.1"
minimatch "^3.0.4"
- pstree.remy "^1.1.0"
+ pstree.remy "^1.1.6"
semver "^5.5.0"
supports-color "^5.2.0"
touch "^3.1.0"
undefsafe "^2.0.2"
- update-notifier "^2.3.0"
+ update-notifier "^2.5.0"
nopt@3.x:
version "3.0.6"
@@ -7095,6 +7235,11 @@ normalize-path@^2.0.1, normalize-path@^2.1.1:
dependencies:
remove-trailing-separator "^1.0.1"
+normalize-path@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
normalize-url@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
@@ -7228,10 +7373,10 @@ object.pick@^1.3.0:
dependencies:
isobject "^3.0.1"
-obuf@^1.0.0, obuf@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e"
- integrity sha1-EEEktsYCxnlogaBCVB0220OlJk4=
+obuf@^1.0.0, obuf@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
+ integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
on-finished@~2.3.0:
version "2.3.0"
@@ -7318,6 +7463,11 @@ optionator@^0.8.1, optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
+orderedmap@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.0.0.tgz#d90fc2ba1ed085190907d601dec6e6a53f8d41ba"
+ integrity sha1-2Q/Cuh7QhRkJB9YB3sbmpT+NQbo=
+
original@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
@@ -7496,6 +7646,11 @@ parse-json@^4.0.0:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
+parse-passwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
+ integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
+
parse5@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
@@ -7600,13 +7755,6 @@ path-type@^3.0.0:
dependencies:
pify "^3.0.0"
-pause-stream@0.0.11:
- version "0.0.11"
- resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
- integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=
- dependencies:
- through "~2.3"
-
pbkdf2@^3.0.3:
version "3.0.14"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade"
@@ -7652,6 +7800,13 @@ pinkie@^2.0.0:
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
+pixelmatch@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854"
+ integrity sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=
+ dependencies:
+ pngjs "^3.0.0"
+
pkg-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
@@ -7683,6 +7838,11 @@ pn@^1.1.0:
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
+pngjs@^3.0.0:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"
+ integrity sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q==
+
pofile@^1:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954"
@@ -7708,9 +7868,9 @@ posix-character-classes@^0.1.0:
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
postcss-modules-extract-imports@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85"
- integrity sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a"
+ integrity sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==
dependencies:
postcss "^6.0.1"
@@ -7786,10 +7946,10 @@ prettier@1.13.7:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
-prettier@1.15.2:
- version "1.15.2"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.2.tgz#d31abe22afa4351efa14c7f8b94b58bb7452205e"
- integrity sha512-YgPLFFA0CdKL4Eg2IHtUSjzj/BWgszDHiNQAe0VAIBse34148whfdzLagRL+QiKS+YfK5ftB6X4v/MBw8yCoug==
+prettier@1.16.1:
+ version "1.16.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.1.tgz#534c2c9d7853f8845e5e078384e71973bd74089f"
+ integrity sha512-XXUITwIkGb3CPJ2hforHah/zTINRyie5006Jd2HKy2qz7snEJXl0KLfsJZW/wst9g6R2rFvqba3VpNYdu1hDcA==
pretty-format@^23.6.0:
version "23.6.0"
@@ -7844,6 +8004,122 @@ prompts@^0.1.9:
kleur "^2.0.1"
sisteransi "^0.1.1"
+prosemirror-commands@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.0.7.tgz#e5a2ba821e29ea7065c88277fe2c3d7f6b0b9d37"
+ integrity sha512-IR8yMSdw7XlKuF68tydAak1J9P/lLD5ohsrL7pzoLsJAJAQU7mVPDXtGbQrrm0mesddFjcc1zNo/cJQN3lRYnA==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-dropcursor@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.1.1.tgz#c60ed1ed6c58804a06a75db06a0d993b087b7622"
+ integrity sha512-GeUyMO/tOEf8MXrP7Xb7UIMrfK86OGh0fnyBrHfhav4VjY9cw65mNoqHy87CklE5711AhCP5Qzfp8RL/hVKusg==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.1.0"
+ prosemirror-view "^1.1.0"
+
+prosemirror-gapcursor@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.0.3.tgz#acc6537fc5a35e9b38966f91a199a382dfc715c4"
+ integrity sha512-X+hJhr42PcHWiSWL+lI5f/UeOhXCxlBFb8M6O8aG1hssmaRrW7sS2/Fjg5jFV+pTdS1REFkmm1occh01FMdDIQ==
+ dependencies:
+ prosemirror-keymap "^1.0.0"
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-view "^1.0.0"
+
+prosemirror-history@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.0.3.tgz#5fb8591adfc272afaaf0b41bec64ee7d9522a118"
+ integrity sha512-IfFGbhafSx+R3aq7nLJGkXeu2iaUiP8mkU3aRu2uQcIIjU8Fq7RJfuvhIOJ2RNUoSyqF/ANkdTjnZ74F5eHs1Q==
+ dependencies:
+ prosemirror-state "^1.2.2"
+ prosemirror-transform "^1.0.0"
+ rope-sequence "^1.2.0"
+
+prosemirror-inputrules@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.0.1.tgz#f63305fd966379f218e82ca76a2a9b328b66dc7b"
+ integrity sha512-UHy22NmwxS5WIMQYkzraDttQAF8mpP82FfbJsmKFfx6jwkR/SZa+ZhbkLY0zKQ5fBdJN7euj36JG/B5iAlrpxA==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.0.1.tgz#03ef32b828e3a859dfb570eb84928bf2e5330bc2"
+ integrity sha512-e79ApE7PXXZMFtPz7WbjycjAFd1NPjgY1MkecVz98tqwlBSggXWXYQnWFk6x7UkmnBYRHHbXHkR/RXmu2wyBJg==
+ dependencies:
+ prosemirror-state "^1.0.0"
+ w3c-keyname "^1.1.8"
+
+prosemirror-markdown@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.3.0.tgz#a100d14c27da7d8fb70818230d786898eeadb7fa"
+ integrity sha512-76l3yLB/suy6sA7LpzRJvRRWkHtKwOTpgWVNwmlIAIIZJeMypWSPldT/gFyIG604eyXEPZitnx+j80Y2DpbnUQ==
+ dependencies:
+ markdown-it "^8.4.2"
+ prosemirror-model "^1.0.0"
+
+prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.6.4:
+ version "1.6.4"
+ resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.6.4.tgz#2ac37a629448a7dbfd1635450e2fdd63c3450d7d"
+ integrity sha512-C2ALle8fZsAza+6stUF9Gv28jH9XtpNeczb33bowGlnb2cpNI4FZf1HHUyZjf6ou4cEvOlbt6fAYsT4NCKmlcQ==
+ dependencies:
+ orderedmap "^1.0.0"
+
+prosemirror-schema-list@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.0.1.tgz#f216e0cf4809b6074aa27912449ac89897f1ae94"
+ integrity sha512-AiLIX6qm6PEeDtMCKZLcSLi55WXo1ls7DnRK+4hSkoi0IIzNdxGsRlecCd3MzEu//DVz3nAEh+zEmslyW+uk8g==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-state@^1.0.0, prosemirror-state@^1.2.1, prosemirror-state@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.2.2.tgz#8df26d95fd6fd327c0f9984a760e84d863204154"
+ integrity sha512-j8aC/kf9BJSCQau485I/9pj39XQoce+TqH5xzekT7WWFARTsRYFLJtiXBcCKakv1VSeev+sC3bJP0pLfz7Ft8g==
+ dependencies:
+ prosemirror-model "^1.0.0"
+ prosemirror-transform "^1.0.0"
+
+prosemirror-tables@^0.7.10, prosemirror-tables@^0.7.9:
+ version "0.7.10"
+ resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-0.7.10.tgz#4b0f623422b4b8f84cdc9c559f8a87579846b3ba"
+ integrity sha512-VIu7UGS9keYEHs0Y6AEOTGbNE9QI2rL1OKng4vV6yoTshW/lYcb+s3hGXI12i+WLMjDVm7ujhfdWrpKpvFZOkQ==
+ dependencies:
+ prosemirror-keymap "^1.0.0"
+ prosemirror-model "^1.0.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.0.0"
+ prosemirror-view "^1.0.0"
+
+prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.1.3.tgz#28cfdf1f9ee514edc40466be7b7db39eed545fdf"
+ integrity sha512-1O6Di5lOL1mp4nuCnQNkHY7l2roIW5y8RH4ZG3hMYmkmDEWzTaFFnxxAAHsE5ipGLBSRcTlP7SsDhYBIdSuLpQ==
+ dependencies:
+ prosemirror-model "^1.0.0"
+
+prosemirror-utils@^0.7.5:
+ version "0.7.5"
+ resolved "https://registry.yarnpkg.com/prosemirror-utils/-/prosemirror-utils-0.7.5.tgz#11b477647b672ec8f10679ab298a5823dad6457a"
+ integrity sha512-F+63BUiBkUQb1S07c3rGHXjE4MDaZ5OjsNhmaO7eDdSh1lUNORTJJHrvlFEZKnLM7ChoDDXTIKhWNQwnCssQfA==
+
+prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.6.8:
+ version "1.6.8"
+ resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.6.8.tgz#33fc1a6e2731633e5d6dc1af1967378f15810b74"
+ integrity sha512-YWX3rfji77xsU5EErt4ZoecVytYW9/4oHBYhV1MUHGMYIcppe+QZEBgRlyPMBUuu0lxdZX4m3sq7fCsDvv/MlQ==
+ dependencies:
+ prosemirror-model "^1.1.0"
+ prosemirror-state "^1.0.0"
+ prosemirror-transform "^1.1.0"
+
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@@ -7862,13 +8138,6 @@ prr@~1.0.1:
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
-ps-tree@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014"
- integrity sha1-tCGyQUDWID8e08dplrRCewjowBQ=
- dependencies:
- event-stream "~3.3.0"
-
pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
@@ -7879,12 +8148,10 @@ psl@^1.1.24:
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==
-pstree.remy@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.0.tgz#f2af27265bd3e5b32bbfcc10e80bac55ba78688b"
- integrity sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q==
- dependencies:
- ps-tree "^1.1.0"
+pstree.remy@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.6.tgz#73a55aad9e2d95814927131fbf4dc1b62d259f47"
+ integrity sha512-NdF35+QsqD7EgNEI5mkI/X+UwaxVEbQaz9f4IooEmMUv6ZPmlTQYGjBPJGgrlzNdjSvIy4MWMg6Q6vCgBO2K+w==
public-encrypt@^4.0.0:
version "4.0.0"
@@ -7897,7 +8164,7 @@ public-encrypt@^4.0.0:
parse-asn1 "^5.0.0"
randombytes "^2.0.1"
-pump@^2.0.0, pump@^2.0.1:
+pump@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
@@ -8027,10 +8294,13 @@ raw-body@2.3.2:
iconv-lite "0.4.19"
unpipe "1.0.0"
-raw-loader@^0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
- integrity sha1-DD0L6u2KAclm2Xh793goElKpeao=
+raw-loader@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-1.0.0.tgz#3f9889e73dadbda9a424bce79809b4133ad46405"
+ integrity sha512-Uqy5AqELpytJTRxYT4fhltcKPj0TyaEpzJDcGz7DFJi+pQOOi3GjR/DOdxTkTsF+NzhnldIoG6TORaBlInUuqA==
+ dependencies:
+ loader-utils "^1.1.0"
+ schema-utils "^1.0.0"
rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
version "1.2.8"
@@ -8093,7 +8363,7 @@ read-pkg@^3.0.0:
normalize-package-data "^2.3.2"
path-type "^3.0.0"
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.6:
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
@@ -8188,6 +8458,15 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
+regexp-tree@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.0.tgz#a56ad7746097888ea16457479029ec9345b96ab0"
+ integrity sha512-rHQv+tzu+0l3KS/ERabas1yK49ahNVxuH40WcPg53CzP5p8TgmmyBgHELLyJcvjhTD0e5ahSY6C76LbEVtr7cg==
+ dependencies:
+ cli-table3 "^0.5.0"
+ colors "^1.1.2"
+ yargs "^10.0.3"
+
regexpp@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
@@ -8345,6 +8624,13 @@ requires-port@1.x.x, requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+requizzle@~0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.1.tgz#6943c3530c4d9a7e46f1cddd51c158fc670cdbde"
+ integrity sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=
+ dependencies:
+ underscore "~1.6.0"
+
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -8352,6 +8638,14 @@ resolve-cwd@^2.0.0:
dependencies:
resolve-from "^3.0.0"
+resolve-dir@^1.0.0, resolve-dir@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
+ integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=
+ dependencies:
+ expand-tilde "^2.0.0"
+ global-modules "^1.0.0"
+
resolve-from@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
@@ -8419,6 +8713,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^2.0.0"
inherits "^2.0.1"
+rope-sequence@^1.2.0:
+ version "1.2.2"
+ 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"
@@ -8511,7 +8810,7 @@ sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.4, schema-utils@^0.4.5:
+schema-utils@^0.4.0, schema-utils@^0.4.4:
version "0.4.5"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e"
integrity sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==
@@ -8737,10 +9036,10 @@ slugify@^1.3.1:
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.1.tgz#f572127e8535329fbc6c1edb74ab856b61ad7de2"
integrity sha512-6BwyhjF5tG5P8s+0DPNyJmBSBePG6iMyhjvIW5zGdA3tFik9PtK+yNkZgTeiroCRGZYgkHftFA62tGVK1EI9Kw==
-smooshpack@^0.0.53:
- version "0.0.53"
- resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.53.tgz#aa397ca43619912e8ac0aa32012846ff4feaa5e8"
- integrity sha512-FVXvKvZOz5+Tk/zUJ/wxM+ftu1yZtFEmeQl4chCqbzK/reU0L/OdDiYpx+/27Jt2VX09j08oIzwoyQ5fHH4+WQ==
+smooshpack@^0.0.54:
+ version "0.0.54"
+ resolved "https://registry.yarnpkg.com/smooshpack/-/smooshpack-0.0.54.tgz#9044358b85052d348b801f385678c8a0c76f2bb6"
+ integrity sha512-yIwEWb17hqoW5IaWyzO6O6nxY89I5UdRoGIZy5hihoqXP9OYcoMbBTxKwS57MeXSKdNA2rtk86rlCcOgAYIgrA==
dependencies:
codesandbox-api "^0.0.20"
codesandbox-import-utils "^1.2.3"
@@ -8877,7 +9176,7 @@ source-map-support@^0.4.15:
dependencies:
source-map "^0.5.6"
-source-map-support@^0.5.6:
+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==
@@ -8929,30 +9228,28 @@ spdx-license-ids@^1.0.2:
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
integrity sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=
-spdy-transport@^2.0.18:
- version "2.0.20"
- resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d"
- integrity sha1-c15yBUxIayNU/onnAiVgBKOazk0=
+spdy-transport@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31"
+ integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==
dependencies:
- debug "^2.6.8"
- detect-node "^2.0.3"
+ debug "^4.1.0"
+ detect-node "^2.0.4"
hpack.js "^2.1.6"
- obuf "^1.1.1"
- readable-stream "^2.2.9"
- safe-buffer "^5.0.1"
- wbuf "^1.7.2"
+ obuf "^1.1.2"
+ readable-stream "^3.0.6"
+ wbuf "^1.7.3"
-spdy@^3.4.1:
- version "3.4.7"
- resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc"
- integrity sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=
+spdy@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.0.tgz#81f222b5a743a329aa12cea6a390e60e9b613c52"
+ integrity sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==
dependencies:
- debug "^2.6.8"
- handle-thing "^1.2.5"
+ debug "^4.1.0"
+ handle-thing "^2.0.0"
http-deceiver "^1.2.7"
- safe-buffer "^5.0.1"
select-hose "^2.0.0"
- spdy-transport "^2.0.18"
+ spdy-transport "^3.0.0"
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
@@ -8961,13 +9258,6 @@ split-string@^3.0.1, split-string@^3.0.2:
dependencies:
extend-shallow "^3.0.0"
-split@0.3:
- version "0.3.3"
- resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f"
- integrity sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=
- dependencies:
- through "2"
-
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -9001,14 +9291,7 @@ sshpk@^1.7.0:
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
-ssri@^5.2.4:
- version "5.2.4"
- resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.2.4.tgz#9985e14041e65fc397af96542be35724ac11da52"
- integrity sha512-UnEAgMZa15973iH7cUi0AHjJn1ACDIkaMyZILoqwN6yzt+4P81I8tBc5Hl+qwi5auMplZtPQsHrPBR5vJLcQtQ==
- dependencies:
- safe-buffer "^5.1.1"
-
-ssri@^6.0.0:
+ssri@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
@@ -9056,13 +9339,6 @@ stream-browserify@^2.0.1:
inherits "~2.0.1"
readable-stream "^2.0.2"
-stream-combiner@~0.0.4:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14"
- integrity sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=
- dependencies:
- duplexer "~0.1.1"
-
stream-each@^1.1.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd"
@@ -9182,13 +9458,13 @@ strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
-style-loader@^0.23.0:
- version "0.23.0"
- resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.0.tgz#8377fefab68416a2e05f1cabd8c3a3acfcce74f1"
- integrity sha512-uCcN7XWHkqwGVt7skpInW6IGO1tG6ReyFQ1Cseh0VcN6VdcFQi62aG/2F3Y9ueA8x4IVlfaSUxpmQXQD9QrEuQ==
+style-loader@^0.23.1:
+ version "0.23.1"
+ resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925"
+ integrity sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==
dependencies:
loader-utils "^1.1.0"
- schema-utils "^0.4.5"
+ schema-utils "^1.0.0"
supports-color@^2.0.0:
version "2.0.0"
@@ -9202,7 +9478,7 @@ supports-color@^3.1.0, supports-color@^3.1.2:
dependencies:
has-flag "^1.0.0"
-supports-color@^5.1.0, supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0:
+supports-color@^5.1.0, supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@@ -9234,6 +9510,11 @@ table@^5.0.2:
slice-ansi "2.0.0"
string-width "^2.1.1"
+taffydb@2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
+ integrity sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=
+
tapable@^0.1.8:
version "0.1.10"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4"
@@ -9264,6 +9545,29 @@ term-size@^1.2.0:
dependencies:
execa "^0.7.0"
+terser-webpack-plugin@^1.1.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.1.tgz#7545da9ae5f4f9ae6a0ac961eb46f5e7c845cc26"
+ integrity sha512-GGSt+gbT0oKcMDmPx4SRSfJPE1XaN3kQRWG4ghxKQw9cn5G9x6aCKSsgYdvyM0na9NJ4Drv0RG6jbBByZ5CMjw==
+ dependencies:
+ cacache "^11.0.2"
+ find-cache-dir "^2.0.0"
+ schema-utils "^1.0.0"
+ serialize-javascript "^1.4.0"
+ source-map "^0.6.1"
+ terser "^3.8.1"
+ webpack-sources "^1.1.0"
+ worker-farm "^1.5.2"
+
+terser@^3.8.1:
+ version "3.14.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-3.14.1.tgz#cc4764014af570bc79c79742358bd46926018a32"
+ integrity sha512-NSo3E99QDbYSMeJaEk9YW2lTg3qS9V0aKGlb+PlOrei1X02r1wSBHCNX/O+yeTRFSWPKPIGj6MqvvdqV4rnVGw==
+ dependencies:
+ commander "~2.17.1"
+ source-map "~0.6.1"
+ source-map-support "~0.5.6"
+
test-exclude@^4.2.1:
version "4.2.3"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20"
@@ -9328,7 +9632,7 @@ through2@^2.0.0:
readable-stream "^2.1.5"
xtend "~4.0.1"
-through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
+through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -9362,6 +9666,57 @@ tiny-emitter@^2.0.0:
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c"
integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==
+tiptap-commands@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.4.0.tgz#0cfb3ac138ee3099de56114cb119abd841fbcbe7"
+ integrity sha512-ytO8jFXgufK5DziamTaVojzUTolWvL4m2xNXaLkAVJYy9CWXruMK7avqeLoFYPI4GZlhleMn5i4gzYTbD7e2jA==
+ dependencies:
+ prosemirror-commands "^1.0.7"
+ prosemirror-inputrules "^1.0.1"
+ prosemirror-schema-list "^1.0.1"
+ prosemirror-state "^1.2.2"
+ tiptap-utils "^1.1.1"
+
+tiptap-extensions@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/tiptap-extensions/-/tiptap-extensions-1.8.0.tgz#3067620a024f1a9e5fae4450790b143d7ebe4394"
+ integrity sha512-1JN9uk5QnA7DTID1j07gIBEqeOnRd6lwZ5rx/zqWXJLyreZu8VDPvP939tfP41GskO4oicGlhmsQ0aEnA5QYDw==
+ dependencies:
+ lowlight "^1.11.0"
+ prosemirror-history "^1.0.3"
+ prosemirror-state "^1.2.2"
+ prosemirror-tables "^0.7.10"
+ prosemirror-utils "^0.7.5"
+ prosemirror-view "^1.6.8"
+ tiptap "^1.8.0"
+ tiptap-commands "^1.4.0"
+
+tiptap-utils@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/tiptap-utils/-/tiptap-utils-1.1.1.tgz#e7aad3e84eb35f7abed704d15da0420029789d0d"
+ integrity sha512-yPIWwLFaL5a0GC7fcO7aoPlASnH3wOUQex0IlepNWbDCNycSL8shXhVx0HMN/tCnlp943zw1bwcYzpTW3wA4tw==
+ dependencies:
+ prosemirror-model "^1.6.4"
+ prosemirror-state "^1.2.2"
+ prosemirror-tables "^0.7.9"
+ prosemirror-utils "^0.7.5"
+
+tiptap@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/tiptap/-/tiptap-1.8.0.tgz#c671188075ffa5ee4f86470f95818fd9ce6f1040"
+ integrity sha512-zIcVY8U1Wgj4bg3R4pX5a2BCpZUw/dTCh259VZ9g5MtClnzdLW2XpKCcwqfa9iUBEs6MCPSnB3t8jGRtGciHJg==
+ dependencies:
+ prosemirror-commands "^1.0.7"
+ prosemirror-dropcursor "^1.1.1"
+ prosemirror-gapcursor "^1.0.3"
+ prosemirror-inputrules "^1.0.1"
+ prosemirror-keymap "^1.0.1"
+ prosemirror-model "^1.6.4"
+ prosemirror-state "^1.2.1"
+ prosemirror-view "^1.6.8"
+ tiptap-commands "^1.4.0"
+ tiptap-utils "^1.1.1"
+
tmp@0.0.33, tmp@0.0.x, tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -9513,13 +9868,10 @@ typescript@^2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
-uglify-es@^3.3.4:
- version "3.3.9"
- resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"
- integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==
- dependencies:
- commander "~2.13.0"
- source-map "~0.6.1"
+uc.micro@^1.0.1, uc.micro@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
+ integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==
uglify-js@^3.1.4:
version "3.4.9"
@@ -9529,20 +9881,6 @@ uglify-js@^3.1.4:
commander "~2.17.1"
source-map "~0.6.1"
-uglifyjs-webpack-plugin@^1.2.4:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.5.tgz#2ef8387c8f1a903ec5e44fa36f9f3cbdcea67641"
- integrity sha512-hIQJ1yxAPhEA2yW/i7Fr+SXZVMp+VEI3d42RTHBgQd2yhp/1UdBcR3QEWPV5ahBxlqQDMEMTuTEvDHSFINfwSw==
- dependencies:
- cacache "^10.0.4"
- find-cache-dir "^1.0.0"
- schema-utils "^0.4.5"
- serialize-javascript "^1.4.0"
- source-map "^0.6.1"
- uglify-es "^3.3.4"
- webpack-sources "^1.1.0"
- worker-farm "^1.5.2"
-
ultron@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
@@ -9555,11 +9893,28 @@ undefsafe@^2.0.2:
dependencies:
debug "^2.2.0"
+underscore-contrib@~0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/underscore-contrib/-/underscore-contrib-0.3.0.tgz#665b66c24783f8fa2b18c9f8cbb0e2c7d48c26c7"
+ integrity sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=
+ dependencies:
+ underscore "1.6.0"
+
+underscore@1.6.0, underscore@~1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
+ integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
+
underscore@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.0.tgz#31dbb314cfcc88f169cd3692d9149d81a00a73e4"
integrity sha512-4IV1DSSxC1QK48j9ONFK1MoIAKKkbE8i7u55w2R6IqBqbT7A/iG7aZBCR2Bi8piF0Uz+i/MG1aeqLwl/5vqF+A==
+underscore@~1.8.3:
+ version "1.8.3"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
+ integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -9598,10 +9953,10 @@ uniq@^1.0.1:
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
-unique-filename@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3"
- integrity sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=
+unique-filename@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
+ integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
dependencies:
unique-slug "^2.0.0"
@@ -9642,15 +9997,16 @@ upath@^1.0.5:
resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==
-update-notifier@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.3.0.tgz#4e8827a6bb915140ab093559d7014e3ebb837451"
- integrity sha1-TognpruRUUCrCTVZ1wFOPruDdFE=
+update-notifier@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6"
+ integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==
dependencies:
boxen "^1.2.1"
chalk "^2.0.1"
configstore "^3.0.0"
import-lazy "^2.1.0"
+ is-ci "^1.0.10"
is-installed-globally "^0.1.0"
is-npm "^1.0.0"
latest-version "^3.0.0"
@@ -9669,10 +10025,10 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
-url-loader@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.1.1.tgz#4d1f3b4f90dde89f02c008e662d604d7511167c1"
- integrity sha512-vugEeXjyYFBCUOpX+ZuaunbK3QXMKaQ3zUnRfIpRBlGkY7QizCnzyyn2ASfcxsvyU3ef+CJppVywnl3Kgf13Gg==
+url-loader@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.1.2.tgz#b971d191b83af693c5e3fea4064be9e1f2d7f8d8"
+ integrity sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==
dependencies:
loader-utils "^1.1.0"
mime "^2.0.3"
@@ -9765,10 +10121,10 @@ uuid@^3.0.1, uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
-v8-compile-cache@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.0.tgz#526492e35fc616864284700b7043e01baee09f0a"
- integrity sha512-qNdTUMaCjPs4eEnM3W9H94R3sU70YCuT+/ST7nUf+id1bVOrdjrpUaeZLqPBPRph3hsgn4a4BvwpxhHZx+oSDg==
+v8-compile-cache@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
+ integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==
validate-npm-package-license@^3.0.1:
version "3.0.1"
@@ -9839,10 +10195,10 @@ vue-hot-reload-api@^2.3.0:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926"
integrity sha512-2j/t+wIbyVMP5NvctQoSUvLkYKoWAAk2QlQiilrM2a6/ulzFgdcLUJfTvs4XQ/3eZhHiBmmEojbjmM4AzZj8JA==
-vue-jest@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.1.tgz#127cded1a57cdfcf01fa8a10ce29579e2cb3a04d"
- integrity sha512-otS+n341cTsp0pF7tuTu2x43b23x/+K0LZdAXV+ewKYIMZRqhuQaJTECWEt/cN/YZw2JC6hUM6xybdnOB4ZQ+g==
+vue-jest@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.2.tgz#c64bf5da9abd0d3ee16071217037696d14b0689c"
+ integrity sha512-5XIQ1xQFW0ZnWxHWM7adVA2IqbDsdw1vhgZfGFX4oWd75J38KIS3YT41PtiE7lpMLmNM6+VJ0uprT2mhHjUgkA==
dependencies:
babel-plugin-transform-es2015-modules-commonjs "^6.26.0"
chalk "^2.1.0"
@@ -9866,17 +10222,17 @@ vue-loader@^15.4.2:
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
-vue-resource@^1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-1.5.0.tgz#ba0c6ef7af2eeace03cf24a91f529471be974c72"
- integrity sha512-em+Ihe+duUWQv4uKO8aFTGK+e/lvNtk5EBEmWaBYcfQzBmHhKR4jJAeVIHcG6otugmsme/DmYrOEPfbss+2XfQ==
+vue-resource@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-1.5.1.tgz#0f3d685e3254d21800bebd966edcf56c34b3b6e4"
+ integrity sha512-o6V4wNgeqP+9v9b2bPXrr20CGNQPEXjpbUWdZWq9GJhqVeAGcYoeTtn/D4q059ZiyN0DIrDv/ADrQUmlUQcsmg==
dependencies:
got "^8.0.3"
-vue-router@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"
- integrity sha512-vLLoY452L+JBpALMP5UHum9+7nzR9PeIBCghU9ZtJ1eWm6ieUI8Zb/DI3MYxH32bxkjzYV1LRjNv4qr8d+uX/w==
+vue-router@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be"
+ integrity sha512-opKtsxjp9eOcFWdp6xLQPLmRGgfM932Tl56U9chYTnoWqKxQ8M20N7AkdEbM5beUh6wICoFGYugAX9vQjyJLFg==
vue-style-loader@^4.1.0:
version "4.1.0"
@@ -9886,10 +10242,10 @@ vue-style-loader@^4.1.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-vue-template-compiler@^2.5.0, vue-template-compiler@^2.5.17:
- version "2.5.17"
- resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.17.tgz#52a4a078c327deb937482a509ae85c06f346c3cb"
- integrity sha512-63uI4syCwtGR5IJvZM0LN5tVsahrelomHtCxvRkZPJ/Tf3ADm1U1wG6KWycK3qCfqR+ygM5vewUvmJ0REAYksg==
+vue-template-compiler@^2.5.0, vue-template-compiler@^2.5.21:
+ version "2.5.21"
+ resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
+ integrity sha512-Vmk5Cv7UcmI99B9nXJEkaK262IQNnHp5rJYo+EwYpe2epTAXqcVyExhV6pk8jTkxQK2vRc8v8KmZBAwdmUZvvw==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@@ -9904,11 +10260,6 @@ vue-virtual-scroll-list@^1.2.5:
resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.2.5.tgz#bcbd010f7cdb035eba8958ebf807c6214d9a167a"
integrity sha1-vL0BD3zbA166iVjr+AfGIU2aFno=
-vue@^2.5.17:
- version "2.5.17"
- resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.17.tgz#0f8789ad718be68ca1872629832ed533589c6ada"
- integrity sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ==
-
vue@^2.5.21:
version "2.5.21"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
@@ -9926,6 +10277,11 @@ w3c-hr-time@^1.0.1:
dependencies:
browser-process-hrtime "^0.1.2"
+w3c-keyname@^1.1.8:
+ version "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:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
@@ -9950,10 +10306,10 @@ watchpack@^1.5.0:
graceful-fs "^4.1.2"
neo-async "^2.5.0"
-wbuf@^1.1.0, wbuf@^1.7.2:
- version "1.7.2"
- resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe"
- integrity sha1-1pe5nx9ZUS3ydRvkJ2nBWAtYAf4=
+wbuf@^1.1.0, wbuf@^1.7.3:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df"
+ integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==
dependencies:
minimalistic-assert "^1.0.0"
@@ -9962,10 +10318,10 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
-webpack-bundle-analyzer@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.2.tgz#22f19ea6d1b5a15fd7a90baae0bc0f39bd1e4d48"
- integrity sha512-cZG4wSQtKrSpk5RJ33dxiaAyo8bP0V+JvycAyIDFEiDIhw4LHhhVKhn40YT1w6TR9E4scHA00LnIoBtTA13Mow==
+webpack-bundle-analyzer@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.3.tgz#dbc7fff8f52058b6714a20fddf309d0790e3e0a0"
+ integrity sha512-naLWiRfmtH4UJgtUktRTLw6FdoZJ2RvCR9ePbwM9aRMsS/KjFerkPZG9epEvXRAw5d5oPdrs9+3p+afNjxW8Xw==
dependencies:
acorn "^5.7.3"
bfj "^6.1.1"
@@ -9980,22 +10336,24 @@ webpack-bundle-analyzer@^3.0.2:
opener "^1.5.1"
ws "^6.0.0"
-webpack-cli@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.1.0.tgz#d71a83687dcfeb758fdceeb0fe042f96bcf62994"
- integrity sha512-p5NeKDtYwjZozUWq6kGNs9w+Gtw/CPvyuXjXn2HMdz8Tie+krjEg8oAtonvIyITZdvpF7XG9xDHwscLr2c+ugQ==
+webpack-cli@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.2.1.tgz#779c696c82482491f0803907508db2e276ed3b61"
+ integrity sha512-jeJveHwz/vwpJ3B8bxEL5a/rVKIpRNJDsKggfKnxuYeohNDW4Y/wB9N/XHJA093qZyS0r6mYL+/crLsIol4WKA==
dependencies:
chalk "^2.4.1"
cross-spawn "^6.0.5"
- enhanced-resolve "^4.0.0"
- global-modules-path "^2.1.0"
- import-local "^1.0.0"
- inquirer "^6.0.0"
+ enhanced-resolve "^4.1.0"
+ findup-sync "^2.0.0"
+ global-modules "^1.0.0"
+ global-modules-path "^2.3.0"
+ import-local "^2.0.0"
interpret "^1.1.0"
+ lightercollective "^0.1.0"
loader-utils "^1.1.0"
- supports-color "^5.4.0"
- v8-compile-cache "^2.0.0"
- yargs "^12.0.1"
+ supports-color "^5.5.0"
+ v8-compile-cache "^2.0.2"
+ yargs "^12.0.4"
webpack-dev-middleware@3.4.0, webpack-dev-middleware@^3.2.0:
version "3.4.0"
@@ -10007,10 +10365,10 @@ webpack-dev-middleware@3.4.0, webpack-dev-middleware@^3.2.0:
range-parser "^1.0.3"
webpack-log "^2.0.0"
-webpack-dev-server@^3.1.10:
- version "3.1.10"
- resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.10.tgz#507411bee727ee8d2fdffdc621b66a64ab3dea2b"
- integrity sha512-RqOAVjfqZJtQcB0LmrzJ5y4Jp78lv9CK0MZ1YJDTaTmedMZ9PU9FLMQNrMCfVu8hHzaVLVOJKBlGEHMN10z+ww==
+webpack-dev-server@^3.1.14:
+ version "3.1.14"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz#60fb229b997fc5a0a1fc6237421030180959d469"
+ integrity sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==
dependencies:
ansi-html "0.0.7"
bonjour "^3.5.0"
@@ -10031,12 +10389,14 @@ webpack-dev-server@^3.1.10:
portfinder "^1.0.9"
schema-utils "^1.0.0"
selfsigned "^1.9.1"
+ semver "^5.6.0"
serve-index "^1.7.2"
sockjs "0.3.19"
sockjs-client "1.3.0"
- spdy "^3.4.1"
+ spdy "^4.0.0"
strip-ansi "^3.0.0"
supports-color "^5.1.0"
+ url "^0.11.0"
webpack-dev-middleware "3.4.0"
webpack-log "^2.0.0"
yargs "12.0.2"
@@ -10049,7 +10409,7 @@ webpack-log@^2.0.0:
ansi-colors "^3.0.0"
uuid "^3.3.2"
-webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.2.0:
+webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85"
integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==
@@ -10062,17 +10422,17 @@ webpack-stats-plugin@^0.2.1:
resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.2.1.tgz#1f5bac13fc25d62cbb5fd0ff646757dc802b8595"
integrity sha512-OYMZLpZrK/qLA79NE4kC4DCt85h/5ipvWJcsefKe9MMw0qU4/ck/IJg+4OmWA+5EfrZZpHXDq92IptfYDWVfkw==
-webpack@^4.19.1:
- version "4.19.1"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.19.1.tgz#096674bc3b573f8756c762754366e5b333d6576f"
- integrity sha512-j7Q/5QqZRqIFXJvC0E59ipLV5Hf6lAnS3ezC3I4HMUybwEDikQBVad5d+IpPtmaQPQArvgUZLXIN6lWijHBn4g==
- dependencies:
- "@webassemblyjs/ast" "1.7.6"
- "@webassemblyjs/helper-module-context" "1.7.6"
- "@webassemblyjs/wasm-edit" "1.7.6"
- "@webassemblyjs/wasm-parser" "1.7.6"
- acorn "^5.6.2"
- acorn-dynamic-import "^3.0.0"
+webpack@^4.29.0:
+ version "4.29.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.29.0.tgz#f2cfef83f7ae404ba889ff5d43efd285ca26e750"
+ integrity sha512-pxdGG0keDBtamE1mNvT5zyBdx+7wkh6mh7uzMOo/uRQ/fhsdj5FXkh/j5mapzs060forql1oXqXN9HJGju+y7w==
+ dependencies:
+ "@webassemblyjs/ast" "1.7.11"
+ "@webassemblyjs/helper-module-context" "1.7.11"
+ "@webassemblyjs/wasm-edit" "1.7.11"
+ "@webassemblyjs/wasm-parser" "1.7.11"
+ acorn "^6.0.5"
+ acorn-dynamic-import "^4.0.0"
ajv "^6.1.0"
ajv-keywords "^3.1.0"
chrome-trace-event "^1.0.0"
@@ -10088,9 +10448,9 @@ webpack@^4.19.1:
node-libs-browser "^2.0.0"
schema-utils "^0.4.4"
tapable "^1.1.0"
- uglifyjs-webpack-plugin "^1.2.4"
+ terser-webpack-plugin "^1.1.0"
watchpack "^1.5.0"
- webpack-sources "^1.2.0"
+ webpack-sources "^1.3.0"
websocket-driver@>=0.5.1:
version "0.6.5"
@@ -10139,7 +10499,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.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.9, which@^1.3.0:
+which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -10258,6 +10618,11 @@ xmlbuilder@8.2.2:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773"
integrity sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=
+xmlcreate@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-1.0.2.tgz#fa6bf762a60a413fb3dd8f4b03c5b269238d308f"
+ integrity sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=
+
xmlhttprequest-ssl@~1.5.4:
version "1.5.5"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
@@ -10310,6 +10675,21 @@ yargs-parser@^10.1.0:
dependencies:
camelcase "^4.1.0"
+yargs-parser@^11.1.1:
+ version "11.1.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
+ integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==
+ dependencies:
+ camelcase "^5.0.0"
+ decamelize "^1.2.0"
+
+yargs-parser@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
+ integrity sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==
+ dependencies:
+ camelcase "^4.1.0"
+
yargs-parser@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
@@ -10317,7 +10697,7 @@ yargs-parser@^9.0.2:
dependencies:
camelcase "^4.1.0"
-yargs@12.0.2, yargs@^12.0.1:
+yargs@12.0.2:
version "12.0.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc"
integrity sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==
@@ -10335,6 +10715,24 @@ yargs@12.0.2, yargs@^12.0.1:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^10.1.0"
+yargs@^10.0.3:
+ version "10.1.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5"
+ integrity sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==
+ dependencies:
+ cliui "^4.0.0"
+ decamelize "^1.1.1"
+ find-up "^2.1.0"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^8.1.0"
+
yargs@^11.0.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"
@@ -10353,10 +10751,28 @@ yargs@^11.0.0:
y18n "^3.2.1"
yargs-parser "^9.0.2"
-yarn-deduplicate@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.0.5.tgz#e56016f1c29e77e323f401ea838f5e8c7cdbfd42"
- integrity sha512-4nds6N7dxuXcfUZAVaSUVSlI4TvwEdMaZg/DRBf/KM3iFezNBdkhcTYptcwKaecAYAfVxx3g0Ex21kssSr8YsA==
+yargs@^12.0.4:
+ version "12.0.5"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
+ integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
+ dependencies:
+ cliui "^4.0.0"
+ decamelize "^1.2.0"
+ find-up "^3.0.0"
+ get-caller-file "^1.0.1"
+ os-locale "^3.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1 || ^4.0.0"
+ yargs-parser "^11.1.1"
+
+yarn-deduplicate@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.0.tgz#bdfdcc5a2473556c0232996424dfe039293f2f44"
+ integrity sha512-YTZzmzzUgDK7IllsKxgnTQ7zAGbTVnj3bnH3nxoqZ2dE0IY7NpaFpFYXR+BuBeDtxIgMhwJJvH1LTWm3k3fWpg==
dependencies:
"@yarnpkg/lockfile" "^1.1.0"
commander "^2.10.0"