summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md723
-rw-r--r--app/assets/javascripts/api.js12
-rw-r--r--app/assets/javascripts/awards_handler.js55
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue15
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js10
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js15
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js6
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue45
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue6
-rw-r--r--app/assets/javascripts/clusters/constants.js15
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-rw-r--r--app/assets/javascripts/commons/polyfills/request_idle_callback.js17
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_gutter_content.vue28
-rw-r--r--app/assets/javascripts/diffs/components/diff_table_cell.vue7
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue11
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue7
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue22
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue51
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue12
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue39
-rw-r--r--app/assets/javascripts/diffs/store/getters.js41
-rw-r--r--app/assets/javascripts/diffs/store/utils.js21
-rw-r--r--app/assets/javascripts/gl_dropdown.js6
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue75
-rw-r--r--app/assets/javascripts/ide/components/branches/item.vue60
-rw-r--r--app/assets/javascripts/ide/components/branches/search_list.vue111
-rw-r--r--app/assets/javascripts/ide/components/ide_tree.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue5
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/dropdown.vue63
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/item.vue18
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue172
-rw-r--r--app/assets/javascripts/ide/components/nav_dropdown.vue59
-rw-r--r--app/assets/javascripts/ide/components/nav_dropdown_button.vue54
-rw-r--r--app/assets/javascripts/ide/components/nav_form.vue40
-rw-r--r--app/assets/javascripts/ide/components/shared/tokened_input.vue121
-rw-r--r--app/assets/javascripts/ide/ide_router.js6
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js3
-rw-r--r--app/assets/javascripts/ide/stores/index.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/actions.js39
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/index.js10
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/mutation_types.js5
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/mutations.js21
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/state.js4
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/actions.js41
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/getters.js4
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/index.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js18
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/state.js10
-rw-r--r--app/assets/javascripts/labels_select.js16
-rw-r--r--app/assets/javascripts/lazy_loader.js18
-rw-r--r--app/assets/javascripts/pages/profiles/show/emoji_menu.js18
-rw-r--r--app/assets/javascripts/pages/profiles/show/index.js49
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue2
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss10
-rw-r--r--app/assets/stylesheets/framework/avatar.scss1
-rw-r--r--app/assets/stylesheets/framework/common.scss3
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss3
-rw-r--r--app/assets/stylesheets/framework/images.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss62
-rw-r--r--app/assets/stylesheets/pages/groups.scss52
-rw-r--r--app/assets/stylesheets/pages/labels.scss3
-rw-r--r--app/assets/stylesheets/pages/notes.scss1
-rw-r--r--app/assets/stylesheets/pages/profile.scss20
-rw-r--r--app/assets/stylesheets/pages/projects.scss4
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/concerns/repository_settings_redirect.rb4
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/projects/avatars_controller.rb2
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb12
-rw-r--r--app/controllers/projects/deploy_tokens_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/projects/mirrors_controller.rb6
-rw-r--r--app/controllers/projects/protected_refs_controller.rb4
-rw-r--r--app/controllers/projects/runners_controller.rb6
-rw-r--r--app/controllers/projects/triggers_controller.rb10
-rw-r--r--app/controllers/projects_controller.rb12
-rw-r--r--app/helpers/avatars_helper.rb52
-rw-r--r--app/helpers/groups_helper.rb5
-rw-r--r--app/helpers/namespaces_helper.rb9
-rw-r--r--app/helpers/profiles_helper.rb4
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/concerns/access_requestable.rb2
-rw-r--r--app/models/concerns/artifact_migratable.rb2
-rw-r--r--app/models/concerns/atomic_internal_id.rb2
-rw-r--r--app/models/concerns/avatarable.rb7
-rw-r--r--app/models/concerns/awardable.rb2
-rw-r--r--app/models/concerns/batch_destroy_dependent_associations.rb2
-rw-r--r--app/models/concerns/blob_like.rb2
-rw-r--r--app/models/concerns/blocks_json_serialization.rb2
-rw-r--r--app/models/concerns/bulk_member_access_load.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb2
-rw-r--r--app/models/concerns/cacheable_attributes.rb2
-rw-r--r--app/models/concerns/case_sensitivity.rb2
-rw-r--r--app/models/concerns/chronic_duration_attribute.rb2
-rw-r--r--app/models/concerns/created_at_filterable.rb2
-rw-r--r--app/models/concerns/deployment_platform.rb2
-rw-r--r--app/models/concerns/diff_file.rb2
-rw-r--r--app/models/concerns/discussion_on_diff.rb2
-rw-r--r--app/models/concerns/each_batch.rb2
-rw-r--r--app/models/concerns/editable.rb2
-rw-r--r--app/models/concerns/enum_with_nil.rb2
-rw-r--r--app/models/concerns/expirable.rb2
-rw-r--r--app/models/concerns/fast_destroy_all.rb2
-rw-r--r--app/models/concerns/faster_cache_keys.rb2
-rw-r--r--app/models/concerns/feature_gate.rb2
-rw-r--r--app/models/concerns/ghost_user.rb2
-rw-r--r--app/models/concerns/group_descendant.rb2
-rw-r--r--app/models/concerns/has_status.rb2
-rw-r--r--app/models/concerns/has_variable.rb2
-rw-r--r--app/models/concerns/ignorable_column.rb2
-rw-r--r--app/models/concerns/iid_routes.rb2
-rw-r--r--app/models/concerns/importable.rb2
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--app/models/concerns/loaded_in_group_list.rb2
-rw-r--r--app/models/concerns/manual_inverse_association.rb2
-rw-r--r--app/models/concerns/mentionable.rb2
-rw-r--r--app/models/concerns/mentionable/reference_regexes.rb2
-rw-r--r--app/models/concerns/milestoneish.rb2
-rw-r--r--app/models/concerns/note_on_diff.rb2
-rw-r--r--app/models/concerns/noteable.rb2
-rw-r--r--app/models/concerns/participable.rb2
-rw-r--r--app/models/concerns/presentable.rb2
-rw-r--r--app/models/concerns/project_features_compatibility.rb2
-rw-r--r--app/models/concerns/prometheus_adapter.rb2
-rw-r--r--app/models/concerns/protected_branch_access.rb2
-rw-r--r--app/models/concerns/protected_ref.rb2
-rw-r--r--app/models/concerns/protected_ref_access.rb2
-rw-r--r--app/models/concerns/protected_tag_access.rb2
-rw-r--r--app/models/concerns/reactive_caching.rb30
-rw-r--r--app/models/concerns/reactive_service.rb2
-rw-r--r--app/models/concerns/redis_cacheable.rb2
-rw-r--r--app/models/concerns/referable.rb2
-rw-r--r--app/models/concerns/relative_positioning.rb2
-rw-r--r--app/models/concerns/resolvable_discussion.rb2
-rw-r--r--app/models/concerns/resolvable_note.rb2
-rw-r--r--app/models/concerns/routable.rb2
-rw-r--r--app/models/concerns/select_for_project_authorization.rb2
-rw-r--r--app/models/concerns/sha_attribute.rb2
-rw-r--r--app/models/concerns/sortable.rb3
-rw-r--r--app/models/concerns/spammable.rb2
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb2
-rw-r--r--app/models/concerns/storage/legacy_project_wiki.rb2
-rw-r--r--app/models/concerns/storage/legacy_repository.rb2
-rw-r--r--app/models/concerns/strip_attribute.rb2
-rw-r--r--app/models/concerns/subscribable.rb2
-rw-r--r--app/models/concerns/taskable.rb2
-rw-r--r--app/models/concerns/throttled_touch.rb2
-rw-r--r--app/models/concerns/time_trackable.rb2
-rw-r--r--app/models/concerns/token_authenticatable.rb2
-rw-r--r--app/models/concerns/triggerable_hooks.rb2
-rw-r--r--app/models/concerns/uniquify.rb2
-rw-r--r--app/models/concerns/updated_at_filterable.rb2
-rw-r--r--app/models/concerns/valid_attribute.rb2
-rw-r--r--app/models/concerns/with_uploads.rb2
-rw-r--r--app/models/conversational_development_index/card.rb2
-rw-r--r--app/models/conversational_development_index/idea_to_production_step.rb2
-rw-r--r--app/models/conversational_development_index/metric.rb2
-rw-r--r--app/models/diff_viewer/added.rb2
-rw-r--r--app/models/diff_viewer/base.rb2
-rw-r--r--app/models/diff_viewer/client_side.rb2
-rw-r--r--app/models/diff_viewer/deleted.rb2
-rw-r--r--app/models/diff_viewer/image.rb2
-rw-r--r--app/models/diff_viewer/mode_changed.rb2
-rw-r--r--app/models/diff_viewer/no_preview.rb2
-rw-r--r--app/models/diff_viewer/not_diffable.rb2
-rw-r--r--app/models/diff_viewer/renamed.rb2
-rw-r--r--app/models/diff_viewer/rich.rb2
-rw-r--r--app/models/diff_viewer/server_side.rb2
-rw-r--r--app/models/diff_viewer/simple.rb2
-rw-r--r--app/models/diff_viewer/static.rb2
-rw-r--r--app/models/diff_viewer/text.rb2
-rw-r--r--app/models/hooks/project_hook.rb2
-rw-r--r--app/models/hooks/service_hook.rb2
-rw-r--r--app/models/hooks/system_hook.rb2
-rw-r--r--app/models/hooks/web_hook.rb2
-rw-r--r--app/models/hooks/web_hook_log.rb2
-rw-r--r--app/models/issue/metrics.rb2
-rw-r--r--app/models/list.rb6
-rw-r--r--app/models/members/group_member.rb2
-rw-r--r--app/models/members/project_member.rb2
-rw-r--r--app/models/merge_request.rb19
-rw-r--r--app/models/merge_request/metrics.rb2
-rw-r--r--app/models/milestone.rb35
-rw-r--r--app/models/network/commit.rb2
-rw-r--r--app/models/network/graph.rb2
-rw-r--r--app/services/ci/compare_test_reports_service.rb43
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml2
-rw-r--r--app/views/groups/settings/_general.html.haml1
-rw-r--r--app/views/groups/settings/_permissions.html.haml1
-rw-r--r--app/views/profiles/show.html.haml42
-rw-r--r--app/views/projects/_new_project_fields.html.haml8
-rw-r--r--app/views/projects/deploy_keys/_index.html.haml2
-rw-r--r--app/views/projects/deploy_tokens/_form.html.haml2
-rw-r--r--app/views/projects/edit.html.haml3
-rw-r--r--app/views/projects/forks/_fork_button.html.haml4
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml1
-rw-r--r--app/views/projects/protected_branches/shared/_index.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_protected_branch.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml1
-rw-r--r--app/views/projects/protected_tags/shared/_protected_tag.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml2
-rw-r--r--app/views/projects/update.js.haml3
-rw-r--r--app/views/shared/boards/components/_board.html.haml3
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml3
-rw-r--r--changelogs/unreleased/#47282-Improving-Contributor-On-Boarding-Documentation.yml4
-rw-r--r--changelogs/unreleased/44127-board-label-edit-drop-down-is-showing-incorrect-selected-labels-summary.yml5
-rw-r--r--changelogs/unreleased/46165-web-ide-branch-picker.yml5
-rw-r--r--changelogs/unreleased/46535-orphaned-uploads.yml5
-rw-r--r--changelogs/unreleased/46703-group-dashboard-line-height-is-too-tall-for-group-names.yml5
-rw-r--r--changelogs/unreleased/add-homepage-link-to-status-pages.yml5
-rw-r--r--changelogs/unreleased/ce-5666-backport.yml5
-rw-r--r--changelogs/unreleased/ci-builds-status-index.yml5
-rw-r--r--changelogs/unreleased/feat-add-default-avatar-to-group.yml5
-rw-r--r--changelogs/unreleased/fix-prometheus-updated-status.yml5
-rw-r--r--changelogs/unreleased/ide-open-empty-merge-request.yml5
-rw-r--r--changelogs/unreleased/improve-junit-support-be.yml5
-rw-r--r--changelogs/unreleased/osw-fix-missing-and-duplicated-milestones-on-list.yml5
-rw-r--r--changelogs/unreleased/pl-json-gon.yml5
-rw-r--r--changelogs/unreleased/sh-fix-bitbucket-cloud-importer-replies.yml5
-rw-r--r--changelogs/unreleased/todos-visibility-migration.yml5
-rw-r--r--changelogs/unreleased/tz-mr-port-memory-fixes.yml5
-rw-r--r--changelogs/unreleased/winh-restyle-user-status.yml5
-rw-r--r--config/initializers/active_record_verbose_query_logs.rb54
-rw-r--r--db/migrate/20160317092222_add_moved_to_to_issue.rb2
-rw-r--r--db/migrate/20180717125853_remove_restricted_todos.rb31
-rw-r--r--db/migrate/20180807153545_remove_redundant_status_index_on_ci_builds.rb17
-rw-r--r--db/schema.rb3
-rw-r--r--doc/api/README.md21
-rw-r--r--doc/development/contributing/design.md70
-rw-r--r--doc/development/contributing/index.md246
-rw-r--r--doc/development/contributing/issue_workflow.md357
-rw-r--r--doc/development/contributing/merge_request_workflow.md191
-rw-r--r--doc/development/migration_style_guide.md28
-rw-r--r--doc/integration/bitbucket.md2
-rw-r--r--doc/integration/oauth_provider.md4
-rw-r--r--doc/raketasks/cleanup.md31
-rw-r--r--doc/user/project/bulk_editing.md22
-rw-r--r--doc/user/project/img/bulk-editing.pngbin0 -> 197686 bytes
-rw-r--r--doc/user/project/img/labels_project_list_search.pngbin0 -> 175669 bytes
-rw-r--r--doc/user/project/import/bitbucket.md27
-rw-r--r--doc/user/project/import/bitbucket_server.md75
-rw-r--r--doc/user/project/import/img/bitbucket_import_new_project.pngbin1316 -> 0 bytes
-rw-r--r--doc/user/project/import/img/bitbucket_server_import_credentials.pngbin0 -> 40566 bytes
-rw-r--r--doc/user/project/import/img/bitbucket_server_import_select_project.pngbin0 -> 56750 bytes
-rw-r--r--doc/user/project/import/img/import_projects_from_new_project_page.pngbin36821 -> 81639 bytes
-rw-r--r--doc/user/project/issue_board.md2
-rw-r--r--doc/user/project/labels.md10
-rw-r--r--doc/user/project/web_ide/index.md13
-rw-r--r--lib/api/boards.rb6
-rw-r--r--lib/api/boards_responses.rb16
-rw-r--r--lib/api/branches.rb1
-rw-r--r--lib/api/group_boards.rb6
-rw-r--r--lib/gitlab/background_migration/remove_restricted_todos.rb105
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb23
-rw-r--r--lib/gitlab/cleanup/remote_uploads.rb80
-rw-r--r--lib/gitlab/git/repository.rb2
-rw-r--r--lib/tasks/gitlab/cleanup.rake10
-rw-r--r--locale/gitlab.pot31
-rw-r--r--public/404.html4
-rw-r--r--public/422.html4
-rw-r--r--public/500.html4
-rw-r--r--public/502.html4
-rw-r--r--public/503.html4
-rw-r--r--qa/qa/page/project/new.rb2
-rw-r--r--rubocop/cop/migration/add_reference.rb49
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/controllers/application_controller_spec.rb51
-rw-r--r--spec/controllers/projects/deploy_keys_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb24
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb33
-rw-r--r--spec/controllers/projects/mirrors_controller_spec.rb4
-rw-r--r--spec/factories/milestones.rb5
-rw-r--r--spec/features/admin/admin_users_spec.rb15
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb226
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb75
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb4
-rw-r--r--spec/features/projects/tree/create_file_spec.rb2
-rw-r--r--spec/helpers/avatars_helper_spec.rb65
-rw-r--r--spec/helpers/groups_helper_spec.rb13
-rw-r--r--spec/javascripts/clusters/clusters_bundle_spec.js18
-rw-r--r--spec/javascripts/clusters/components/application_row_spec.js49
-rw-r--r--spec/javascripts/clusters/services/mock_data.js27
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_line_gutter_content_spec.js6
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js98
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js2
-rw-r--r--spec/javascripts/ide/components/branches/item_spec.js53
-rw-r--r--spec/javascripts/ide/components/branches/search_list_spec.js79
-rw-r--r--spec/javascripts/ide/components/merge_requests/dropdown_spec.js47
-rw-r--r--spec/javascripts/ide/components/merge_requests/item_spec.js15
-rw-r--r--spec/javascripts/ide/components/merge_requests/list_spec.js112
-rw-r--r--spec/javascripts/ide/components/nav_dropdown_button_spec.js63
-rw-r--r--spec/javascripts/ide/components/nav_dropdown_spec.js50
-rw-r--r--spec/javascripts/ide/components/shared/tokened_input_spec.js132
-rw-r--r--spec/javascripts/ide/helpers.js2
-rw-r--r--spec/javascripts/ide/mock_data.js30
-rw-r--r--spec/javascripts/ide/stores/modules/branches/actions_spec.js193
-rw-r--r--spec/javascripts/ide/stores/modules/branches/mutations_spec.js51
-rw-r--r--spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js77
-rw-r--r--spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js19
-rw-r--r--spec/javascripts/pages/profiles/show/emoji_menu_spec.js117
-rw-r--r--spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js15
-rw-r--r--spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb124
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb75
-rw-r--r--spec/lib/gitlab/cleanup/remote_uploads_spec.rb74
-rw-r--r--spec/models/ci/pipeline_spec.rb43
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb8
-rw-r--r--spec/models/merge_request_spec.rb13
-rw-r--r--spec/rubocop/cop/migration/add_reference_spec.rb54
-rw-r--r--spec/services/ci/compare_test_reports_service_spec.rb32
-rw-r--r--spec/services/milestones/update_service_spec.rb41
-rw-r--r--spec/services/notes/create_service_spec.rb4
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service.rb4
-rw-r--r--vendor/Dockerfile/Node-alpine.Dockerfile9
-rw-r--r--vendor/Dockerfile/Node.Dockerfile9
-rw-r--r--vendor/Dockerfile/Ruby-alpine.Dockerfile11
-rw-r--r--vendor/Dockerfile/Ruby.Dockerfile4
-rw-r--r--vendor/gitignore/Autotools.gitignore2
-rw-r--r--vendor/gitignore/Laravel.gitignore7
-rw-r--r--vendor/gitignore/VisualStudio.gitignore5
-rw-r--r--vendor/gitlab-ci-yml/Maven.gitlab-ci.yml2
-rw-r--r--vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml2
-rw-r--r--vendor/licenses.csv289
327 files changed, 5492 insertions, 1711 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0c483986270..e68e3b9cab0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,41 +21,47 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
<!-- 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)
- - [Bug Priority labels](#bug-priority-labels)
- - [Bug Severity labels](#bug-severity-labels)
- - [Severity impact guidance](#severity-impact-guidance)
- - [Label for community contributors](#label-for-community-contributors)
-- [Implement design & UI elements](#implement-design-ui-elements)
+ - [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)
+ - [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)
+ - [Merge request guidelines](#merge-request-guidelines)
+ - [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Definition of done](#definition-of-done)
- [Style guides](#style-guides)
-- [Code of conduct](#code-of-conduct)
-- [Contribution Flow](#contribution-flow)
<!-- 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.
+[view the new documentation](doc/development/contributing/index.md) to find the latest information.
+
## Contribute to GitLab
For a first-time step-by-step guide to the contribution process, see
@@ -84,6 +90,36 @@ Please report suspected security vulnerabilities in private to
Please do **NOT** create publicly viewable issues for suspected security
vulnerabilities.
+## Code of conduct
+
+As contributors and maintainers of this project, we pledge to respect all
+people who contribute through reporting issues, posting feature requests,
+updating documentation, submitting pull requests or patches, and other
+activities.
+
+We are committed to making participation in this project a harassment-free
+experience for everyone, regardless of level of experience, gender, gender
+identity and expression, sexual orientation, disability, personal appearance,
+body size, race, ethnicity, age, or religion.
+
+Examples of unacceptable behavior by participants include the use of sexual
+language or imagery, derogatory comments or personal attacks, trolling, public
+or private harassment, insults, or other unprofessional conduct.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct. Project maintainers who do not
+follow the Code of Conduct may be removed from the project team.
+
+This code of conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior can be
+reported by emailing `contact@gitlab.com`.
+
+This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
+available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
+
## Closing policy for issues and merge requests
GitLab is a popular open source project and the capacity to deal with issues
@@ -123,669 +159,164 @@ learn how to communicate with GitLab. If you're looking for a Gitter or Slack ch
please consider we favor
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
-## Workflow labels
-
-To allow for asynchronous issue handling, we use [milestones][milestones-page]
-and [labels][labels-page]. Leads and product managers handle most of the
-scheduling into milestones. Labelling is a task for everyone.
-
-Most issues will have labels for at least one of the following:
+## Contribution Flow
-- Type: ~"feature proposal", ~bug, ~customer, etc.
-- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
-- Team: ~"CI/CD", ~Plan, ~Manage, ~Quality, etc.
-- Release Scoping: ~Deliverable, ~Stretch, ~"Next Patch Release"
-- Priority: ~P1, ~P2, ~P3, ~P4
-- Severity: ~S1, ~S2, ~S3, ~S4
+When contributing to GitLab, your merge request is subject to review by merge request maintainers of a particular specialty.
-All labels, their meaning and priority are defined on the
-[labels page][labels-page].
+When you submit code to GitLab, we really want it to get merged, but there will be times when it will not be merged.
-If you come across an issue that has none of these, and you're allowed to set
-labels, you can _always_ add the team and type, and often also the subject.
+When maintainers are reading through a merge request they may request guidance from other maintainers. If merge request maintainers conclude that the code should not be merged, our reasons will be fully disclosed. If it has been decided that the code quality is not up to GitLab’s standards, the merge request maintainer will refer the author to our docs and code style guides, and provide some guidance.
-[milestones-page]: https://gitlab.com/gitlab-org/gitlab-ce/milestones
-[labels-page]: https://gitlab.com/gitlab-org/gitlab-ce/labels
+Sometimes style guides will be followed but the code will lack structural integrity, or the maintainer will have reservations about the code’s overall quality. When there is a reservation the maintainer will inform the author and provide some guidance. The author may then choose to update the merge request. Once the merge request has been updated and reassigned to the maintainer, they will review the code again. Once the code has been resubmitted any number of times, the maintainer may choose to close the merge request with a summary of why it will not be merged, as well as some guidance. If the merge request is closed the maintainer will be open to discussion as to how to improve the code so it can be approved in the future.
-### Type labels
+GitLab will do its best to review community contributions as quickly as possible. Specially appointed developers review community contributions daily. You may take a look at the [team page](https://about.gitlab.com/team/) for the merge request coach who specializes in the type of code you have written and mention them in the merge request. For example, if you have written some JavaScript in your code then you should mention the frontend merge request coach. If your code has multiple disciplines you may mention multiple merge request coaches.
-Type labels are very important. They define what kind of issue this is. Every
-issue should have one or more.
+GitLab receives a lot of community contributions, so if your code has not been reviewed within 4 days of its initial submission feel free to re-mention the appropriate merge request coach.
-Examples of type labels are ~"feature proposal", ~bug, ~customer, ~security,
-and ~"direction".
+When submitting code to GitLab, you may feel that your contribution requires the aid of an external library. If your code includes an external library please provide a link to the library, as well as reasons for including it.
-A number of type labels have a priority assigned to them, which automatically
-makes them float to the top, depending on their importance.
+When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
-Type labels are always lowercase, and can have any color, besides blue (which is
-already reserved for subject labels).
+[core team]: https://about.gitlab.com/core-team/
+[team]: https://about.gitlab.com/team/
+[getting-help]: https://about.gitlab.com/getting-help/
+[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
+[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?assignee_id=0&label_name[]=Accepting%20Merge%20Requests&sort=weight_asc
+[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
+[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
+[google-group]: https://groups.google.com/forum/#!forum/gitlabhq
+[stackoverflow]: https://stackoverflow.com/questions/tagged/gitlab
+[fpl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature+proposal
+[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests
+[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests
+[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
+[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
+[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
+[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
+[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
+[contributor-covenant]: http://contributor-covenant.org
+[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
+[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
+[changelog]: doc/development/changelog.md "Generate a changelog entry"
+[doc-guidelines]: doc/development/documentation/index.md "Documentation guidelines"
+[js-styleguide]: doc/development/fe_guide/style_guide_js.md "JavaScript styleguide"
+[scss-styleguide]: doc/development/fe_guide/style_guide_scss.md "SCSS styleguide"
+[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
+[UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/
+[license-finder-doc]: doc/development/licensing.md
+[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
+[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
+[testing]: doc/development/testing_guide/index.md
+[us-english]: https://en.wikipedia.org/wiki/American_English
-The descriptions on the [labels page][labels-page] explain what falls under each type label.
-### Subject labels
+## Workflow labels
-Subject labels are labels that define what area or feature of GitLab this issue
-hits. They are not always necessary, but very convenient.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-Examples of subject labels are ~wiki, ~ldap, ~api,
-~issues, ~"merge requests", ~labels, and ~"container registry".
-If you are an expert in a particular area, it makes it easier to find issues to
-work on. You can also subscribe to those labels to receive an email each time an
-issue is labeled with a subject label corresponding to your expertise.
+### Type labels
-Subject labels are always all-lowercase.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-### Team labels
-Team labels specify what team is responsible for this issue.
-Assigning a team label makes sure issues get the attention of the appropriate
-people.
+### Subject labels
-The current team labels are:
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-- ~Configuration
-- ~"CI/CD"
-- ~Create
-- ~Distribution
-- ~Documentation
-- ~Geo
-- ~Gitaly
-- ~Manage
-- ~Monitoring
-- ~Plan
-- ~Quality
-- ~Release
-- ~"Security Products"
-- ~UX
-The descriptions on the [labels page][labels-page] explain what falls under the
-responsibility of each team.
+### Team labels
-Within those team labels, we also have the ~backend and ~frontend labels to
-indicate if an issue needs backend work, frontend work, or both.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-Team labels are always capitalized so that they show up as the first label for
-any issue.
### Release Scoping labels
-Release Scoping labels help us clearly communicate expectations of the work for the
-release. There are three levels of Release Scoping labels:
-
-- ~Deliverable: Issues that are expected to be delivered in the current
- milestone.
-- ~Stretch: Issues that are a stretch goal for delivering in the current
- milestone. If these issues are not done in the current release, they will
- strongly be considered for the next release.
-- ~"Next Patch Release": Issues to put in the next patch release. Work on these
- first, and add the "Pick Into X" label to the merge request, along with the
- appropriate milestone.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-Each issue scheduled for the current milestone should be labeled ~Deliverable
-or ~"Stretch". Any open issue for a previous milestone should be labeled
-~"Next Patch Release", or otherwise rescheduled to a different milestone.
### Priority labels
-Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be.
-If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
-This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-| Label | Meaning | Estimate time to fix |
-|-------|-----------------|------------------------------------------------------------------|
-| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com |
-| ~P2 | High Priority | The next release |
-| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) |
-| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) |
### Severity labels
-Severity labels help us clearly communicate the impact of a ~bug on users.
-
-| Label | Meaning | Impact on Functionality | Example |
-|-------|-------------------|-------------------------------------------------------|---------|
-| ~S1 | Blocker | Outage, broken feature with no workaround | Unable to create an issue. Data corruption/loss. Security breach. |
-| ~S2 | Critical Severity | Broken Feature, workaround too complex & unacceptable | Can push commits, but only via the command line. |
-| ~S3 | Major Severity | Broken Feature, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue. |
-| ~S4 | Low Severity | Functionality inconvenience or cosmetic issue | Label colors are incorrect / not being displayed. |
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
#### Severity impact guidance
-Severity levels can be applied further depending on the facet of the impact; e.g. Affected customers, GitLab.com availability, performance and etc. The below is a guideline.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-| Severity | Affected Customers/Users | GitLab.com Availability | Performance Degradation |
-|----------|---------------------------------------------------------------------|----------------------------------------------------|------------------------------|
-| ~S1 | >50% users affected (possible company extinction level event) | Significant impact on all of GitLab.com | |
-| ~S2 | Many users or multiple paid customers affected (but not apocalyptic)| Significant impact on large portions of GitLab.com | Degradation is guaranteed to occur in the near future |
-| ~S3 | A few users or a single paid customer affected | Limited impact on important portions of GitLab.com | Degradation is likely to occur in the near future |
-| ~S4 | No paid users/customer affected, or expected to in the near future | Minor impact on on GitLab.com | Degradation _may_ occur but it's not likely |
### Label for community contributors
-Issues that are beneficial to our users, 'nice to haves', that we currently do
-not have the capacity for or want to give the priority to, are labeled as
-~"Accepting Merge Requests", so the community can make a contribution.
-
-Community contributors can submit merge requests for any issue they want, but
-the ~"Accepting Merge Requests" label has a special meaning. It points to
-changes that:
-
-1. We already agreed on,
-1. Are well-defined,
-1. Are likely to get accepted by a maintainer.
-
-We want to avoid a situation when a contributor picks an
-~"Accepting Merge Requests" issue and then their merge request gets closed,
-because we realize that it does not fit our vision, or we want to solve it in a
-different way.
-
-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 ~"technical debt" issues
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-After adding the ~"Accepting Merge Requests" label, we try to estimate the
-[weight](#issue-weight) of the issue. We use issue weight to let contributors
-know how difficult the issue is. Additionally:
-
-- We advertise ["Accepting Merge Requests" issues with weight < 5][up-for-grabs]
- as suitable for people that have never contributed to GitLab before on the
- [Up For Grabs campaign](http://up-for-grabs.net)
-- We encourage people that have never contributed to any open source project to
- look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers]
-
-If you've decided that you would like to work on an issue, please @-mention
-the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
-as soon as possible. The product manager will then pull in appropriate GitLab team
-members to further discuss scope, design, and technical considerations. This will
-ensure that that your contribution is aligned with the GitLab product and minimize
-any rework and delay in getting it merged into master.
-
-GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
-should update the issue description with a responsible product manager, inviting
-any potential community contributor to @-mention per above.
-
-[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
-[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
## Implement design & UI elements
-For guidance on UX implementation at GitLab, please refer to our [Design System](https://design.gitlab.com/).
-
-The UX team uses labels to manage their workflow.
-
-The ~"UX" label on an issue is a signal to the UX team that it will need UX attention.
-To better understand the priority by which UX tackles issues, see the [UX section](https://about.gitlab.com/handbook/engineering/ux) of the handbook.
-
-Once an issue has been worked on and is ready for development, a UXer removes the ~"UX" label and applies the ~"UX ready" label to that issue.
+This [documentation](doc/development/contributing/design.md) has been moved.
-The UX team has a special type label called ~"design artifact". This label indicates that the final output
-for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone.
-Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is
-needed until the solution has been decided.
-
-~"design artifact" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone.
-
-To prevent the misunderstanding that a feature will be be delivered in the
-assigned milestone, when only UX design is planned for that milestone, the
-Product Manager should create a separate issue for the ~"design artifact",
-assign the ~UX, ~"design artifact" and ~"Deliverable" labels, add a milestone
-and use a title that makes it clear that the scheduled issue is design only
-(e.g. `Design exploration for XYZ`).
-
-When the ~"design artifact" issue has been completed, the UXer removes the ~UX
-label, adds the ~"UX ready" label and closes the issue. This indicates the
-design artifact is complete. The UXer will also copy the designs to related
-issues for implementation in an upcoming milestone.
## Issue tracker
-To get support for your particular problem please use the
-[getting help channels](https://about.gitlab.com/getting-help/).
-
-The [GitLab CE issue tracker on GitLab.com][ce-tracker] is for bugs concerning
-the latest GitLab release and [feature proposals](#feature-proposals).
-
-When submitting an issue please conform to the issue submission guidelines
-listed below. Not all issues will be addressed and your issue is more likely to
-be addressed if you submit a merge request which partially or fully solves
-the issue.
-
-If you're unsure where to post, post to the [mailing list][google-group] or
-[Stack Overflow][stackoverflow] first. There are a lot of helpful GitLab users
-there who may be able to help you quickly. If your particular issue turns out
-to be a bug, it will find its way from there.
-
-If it happens that you know the solution to an existing bug, please first
-open the issue in order to keep track of it and then open the relevant merge
-request that potentially fixes it.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
### Issue triaging
-Our issue triage policies are [described in our handbook]. You are very welcome
-to help the GitLab team triage issues. We also organize [issue bash events] once
-every quarter.
-
-The most important thing is making sure valid issues receive feedback from the
-development team. Therefore the priority is mentioning developers that can help
-on those issues. Please select someone with relevant experience from the
-[GitLab team][team]. If there is nobody mentioned with that expertise look in
-the commit history for the affected files to find someone.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-We also use [GitLab Triage] to automate some triaging policies. This is
-currently setup as a [scheduled pipeline] running on [quality/triage-ops]
-project.
-
-[described in our handbook]: https://about.gitlab.com/handbook/engineering/issue-triage/
-[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
-[GitLab Triage]: https://gitlab.com/gitlab-org/gitlab-triage
-[scheduled pipeline]: https://gitlab.com/gitlab-org/quality/triage-ops/pipeline_schedules/10512/edit
-[quality/triage-ops]: https://gitlab.com/gitlab-org/quality/triage-ops
### Feature proposals
-To create a feature proposal for CE, open an issue on the
-[issue tracker of CE][ce-tracker].
-
-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
-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"`.
-
-Please keep feature proposals as small and simple as possible, complex ones
-might be edited to make them small and simple.
-
-Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
-
-For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should
-be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may
-need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself.
-
-If you want to create something yourself, consider opening an issue first to
-discuss whether it is interesting to include this in GitLab.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
### Issue tracker guidelines
-**[Search the issue tracker][ce-tracker]** for similar entries before
-submitting your own, there's a good chance somebody else had the same issue or
-feature proposal. Show your support with an award emoji and/or join the
-discussion.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-Please submit bugs using the ['Bug' issue template](.gitlab/issue_templates/Bug.md) provided on the issue tracker.
-The text in the parenthesis is there to help you with what to include. Omit it
-when submitting the actual issue. You can copy-paste it and then edit as you
-see fit.
### Issue weight
-Issue weight allows us to get an idea of the amount of work required to solve
-one or multiple issues. This makes it possible to schedule work more accurately.
-
-You are encouraged to set the weight of any issue. Following the guidelines
-below will make it easy to manage this, without unnecessary overhead.
-
-1. Set weight for any issue at the earliest possible convenience
-1. If you don't agree with a set weight, discuss with other developers until
-consensus is reached about the weight
-1. Issue weights are an abstract measurement of complexity of the issue. Do not
-relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring)
-and something you want to avoid.
-1. Something that has a weight of 1 (or no weight) is really small and simple.
-Something that is 9 is rewriting a large fundamental part of GitLab,
-which might lead to many hard problems to solve. Changing some text in GitLab
-is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
-1. If something is very large, it should probably be split up in multiple
-issues or chunks. You can simply not set the weight of a parent issue and set
-weights to children issues.
-
-### Regression issues
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-Every monthly release has a corresponding issue on the CE issue tracker to keep
-track of functionality broken by that release and any fixes that need to be
-included in a patch release (see [8.3 Regressions] as an example).
-As outlined in the issue description, the intended workflow is to post one note
-with a reference to an issue describing the regression, and then to update that
-note with a reference to the merge request that fixes it as it becomes available.
-
-If you're a contributor who doesn't have the required permissions to update
-other users' notes, please post a new note with a reference to both the issue
-and the merge request.
+### Regression issues
-The release manager will [update the notes] in the regression issue as fixes are
-addressed.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
-[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
### Technical and UX debt
-In order to track things that can be improved in GitLab's codebase,
-we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
-For user experience improvements, we use the ~"UX debt" label.
-
-These labels should be added to issues that describe things that can be improved,
-shortcuts that have been taken, features that need additional attention, and all
-other things that have been left behind due to high velocity of development.
-For example, code that needs refactoring should use the ~"technical debt" label,
-user experience refinements should use the ~"UX debt" label.
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-Everyone can create an issue, though you may need to ask for adding a specific
-label, if you do not have permissions to do it by yourself. Additional labels
-can be combined with these labels, to make it easier to schedule
-the improvements for a release.
-
-Issues tagged with these labels have the same priority like issues
-that describe a new feature to be introduced in GitLab, and should be scheduled
-for a release by the appropriate person.
-
-Make sure to mention the merge request that the ~"technical debt" issue or
-~"UX debt" issue is associated with in the description of the issue.
### Stewardship
-For issues related to the open source stewardship of GitLab,
-there is the ~"stewardship" label.
-
-This label is to be used for issues in which the stewardship of GitLab
-is a topic of discussion. For instance if GitLab Inc. is planning to add
-features from GitLab EE to GitLab CE, related issues would be labelled with
-~"stewardship".
-
-A recent example of this was the issue for
-[bringing the time tracking API to GitLab CE][time-tracking-issue].
+This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-[time-tracking-issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/25517#note_20019084
## Merge requests
-We welcome merge requests with fixes and improvements to GitLab code, tests,
-and/or documentation. The issues that are specifically suitable for
-community contributions are listed with the label
-[`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
-and [EE][accepting-mrs-ee], but you are free to contribute to any other issue
-you want.
-
-Please note that if an issue is marked for the current milestone either before
-or while you are working on it, a team member may take over the merge request
-in order to ensure the work is finished before the release date.
-
-If you want to add a new feature that is not labeled it is best to first create
-a feedback issue (if there isn't one already) and leave a comment asking for it
-to be marked as `Accepting Merge Requests`. Please include screenshots or
-wireframes if the feature will also change the UI.
-
-Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
+This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
-If you are new to GitLab development (or web development in general), see the
-[I want to contribute!](#i-want-to-contribute) section to get you started with
-some potentially easy issues.
-
-To start with GitLab development download the [GitLab Development Kit][gdk] and
-see the [Development section](doc/development/README.md) for some guidelines.
### Merge request guidelines
-If you can, please submit a merge request with the fix or improvements
-including tests. If you don't know how to fix the issue but can write a test
-that exposes the issue we will accept that as well. In general bug fixes that
-include a regression test are merged quickly while new features without proper
-tests are least likely to receive timely feedback. The workflow to make a merge
-request is as follows:
-
-1. Fork the project into your personal space on GitLab.com
-1. Create a feature branch, branch away from `master`
-1. Write [tests](https://docs.gitlab.com/ee/development/rake_tasks.html#run-tests) and code
-1. [Generate a changelog entry with `bin/changelog`][changelog]
-1. If you are writing documentation, make sure to follow the
- [documentation guidelines][doc-guidelines]
-1. If you have multiple commits please combine them into a few logically
- organized commits by [squashing them][git-squash]
-1. Push the commit(s) to your fork
-1. Submit a merge request (MR) to the `master` branch
- 1. Your merge request needs at least 1 approval but feel free to require more.
- For instance if you're touching backend and frontend code, it's a good idea
- to require 2 approvals: 1 from a backend maintainer and 1 from a frontend
- maintainer
- 1. You don't have to select any approvers, but you can if you really want
- specific people to approve your merge request
-1. The MR title should describe the change you want to make
-1. The MR description should give a motive for your change and the method you
- used to achieve it.
- 1. If you are contributing code, fill in the template already provided in the
- "Description" field.
- 1. If you are contributing documentation, choose `Documentation` from the
- "Choose a template" menu and fill in the template.
- 1. Mention the issue(s) your merge request solves, using the `Solves #XXX` or
- `Closes #XXX` syntax to auto-close the issue(s) once the merge request will
- be merged.
-1. If you're allowed to, set a relevant milestone and labels
-1. If the MR changes the UI it should include *Before* and *After* screenshots
-1. If the MR changes CSS classes please include the list of affected pages,
- `grep css-class ./app -R`
-1. Be prepared to answer questions and incorporate feedback even if requests
- for this arrive weeks or months after your MR submission
- 1. If a discussion has been addressed, select the "Resolve discussion" button
- beneath it to mark it resolved.
-1. If your MR touches code that executes shell commands, reads or opens files or
- handles paths to files on disk, make sure it adheres to the
- [shell command guidelines](doc/development/shell_commands.md)
-1. If your code creates new files on disk please read the
- [shared files guidelines](doc/development/shared_files.md).
-1. When writing commit messages please follow
- [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
- [guidelines](http://chris.beams.io/posts/git-commit/).
-1. If your merge request adds one or more migrations, make sure to execute all
- migrations on a fresh database before the MR is reviewed. If the review leads
- to large changes in the MR, do this again once the review is complete.
-1. For more complex migrations, write tests.
-1. Merge requests **must** adhere to the [merge request performance
- guidelines](doc/development/merge_request_performance_guidelines.md).
-1. For tests that use Capybara or PhantomJS, see this [article on how
- to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
-
-Please keep the change in a single MR **as small as possible**. If you want to
-contribute a large feature think very hard what the minimum viable change is.
-Can you split the functionality? Can you only submit the backend/API code? Can
-you start with a very simple UI? Can you do part of the refactor? The increased
-reviewability of small MRs that leads to higher code quality is more important
-to us than having a minimal commit log. The smaller an MR is the more likely it
-is it will be merged (quickly). After that you can send more MRs to enhance it.
-The ['How to get faster PR reviews' document of Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) also has some great points regarding this.
-
-For examples of feedback on merge requests please look at already
-[closed merge requests][closed-merge-requests]. If you would like quick feedback
-on your merge request feel free to mention someone from the [core team] or one
-of the [Merge request coaches][team].
-Please ensure that your merge request meets the contribution acceptance criteria.
-
-When having your code reviewed and when reviewing merge requests please take the
-[code review guidelines](doc/development/code_review.md) into account.
-
-### Contribution acceptance criteria
-
-1. The change is as small as possible
-1. Include proper tests and make all tests pass (unless it contains a test
- exposing a bug in existing code). Every new class should have corresponding
- unit tests, even if the class is exercised at a higher level, such as a feature test.
-1. If you suspect a failing CI build is unrelated to your contribution, you may
- try and restart the failing CI job or ask a developer to fix the
- aforementioned failing test
-1. Your MR initially contains a single commit (please use `git rebase -i` to
- squash commits)
-1. Your changes can merge without problems (if not please rebase if you're the
- only one working on your feature branch, otherwise, merge `master`)
-1. Does not break any existing functionality
-1. Fixes one specific issue or implements one specific feature (do not combine
- things, send separate merge requests if needed)
-1. Migrations should do only one thing (e.g., either create a table, move data
- to a new table or remove an old table) to aid retrying on failure
-1. Keeps the GitLab code base clean and well structured
-1. Contains functionality we think other users will benefit from too
-1. Doesn't add configuration options or settings options since they complicate
- making and testing future changes
-1. Changes do not adversely degrade performance.
- - Avoid repeated polling of endpoints that require a significant amount of overhead
- - Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- - Avoid repeated access of filesystem
-1. If you need polling to support real-time features, please use
- [polling with ETag caching][polling-etag].
-1. Changes after submitting the merge request should be in separate commits
- (no squashing).
-1. It conforms to the [style guides](#style-guides) and the following:
- - If your change touches a line that does not follow the style, modify the
- entire line to follow it. This prevents linting tools from generating warnings.
- - Don't touch neighbouring lines. As an exception, automatic mass
- refactoring modifications may leave style non-compliant.
-1. If the merge request adds any new libraries (gems, JavaScript libraries,
- etc.), they should conform to our [Licensing guidelines][license-finder-doc].
- See the instructions in that document for help if your MR fails the
- "license-finder" test with a "Dependencies that need approval" error.
-1. The merge request meets the [definition of done](#definition-of-done).
-
-## Definition of done
+This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
-If you contribute to GitLab please know that changes involve more than just
-code. We have the following [definition of done][definition-of-done]. Please ensure you support
-the feature you contribute through all of these steps.
-
-1. Description explaining the relevancy (see following item)
-1. Working and clean code that is commented where needed
-1. [Unit, integration, and system tests][testing] that pass on the CI server
-1. Performance/scalability implications have been considered, addressed, and tested
-1. [Documented][doc-guidelines] in the `/doc` directory
-1. [Changelog entry added][changelog], if necessary
-1. Reviewed and any concerns are addressed
-1. Merged by a project maintainer
-1. Added to the release blog article, if relevant
-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.)
-
-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. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
-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
-## Style guides
-
-1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
- Important sections include [Source Code Layout][rss-source] and
- [Naming][rss-naming]. Use:
- - multi-line method chaining style **Option A**: dot `.` on the second line
- - string literal quoting style **Option A**: single quoted by default
-1. [Rails](https://github.com/bbatsov/rails-style-guide)
-1. [Newlines styleguide][newlines-styleguide]
-1. [Testing][testing]
-1. [JavaScript styleguide][js-styleguide]
-1. [SCSS styleguide][scss-styleguide]
-1. [Shell commands](doc/development/shell_commands.md) created by GitLab
- contributors to enhance security
-1. [Database Migrations](doc/development/migration_style_guide.md)
-1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
-1. [Documentation styleguide](https://docs.gitlab.com/ee/development/documentation/styleguide.html)
-1. Interface text should be written subjectively instead of objectively. It
- should be the GitLab core team addressing a person. It should be written in
- present time and never use past tense (has been/was). For example instead
- of _prohibited this user from being saved due to the following errors:_ the
- text should be _sorry, we could not create your account because:_
-1. Code should be written in [US English][us-english]
-
-This is also the style used by linting tools such as
-[RuboCop](https://github.com/bbatsov/rubocop),
-[PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
-
-## Code of conduct
-
-As contributors and maintainers of this project, we pledge to respect all
-people who contribute through reporting issues, posting feature requests,
-updating documentation, submitting pull requests or patches, and other
-activities.
-
-We are committed to making participation in this project a harassment-free
-experience for everyone, regardless of level of experience, gender, gender
-identity and expression, sexual orientation, disability, personal appearance,
-body size, race, ethnicity, age, or religion.
-
-Examples of unacceptable behavior by participants include the use of sexual
-language or imagery, derogatory comments or personal attacks, trolling, public
-or private harassment, insults, or other unprofessional conduct.
-
-Project maintainers have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct. Project maintainers who do not
-follow the Code of Conduct may be removed from the project team.
-
-This code of conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community.
-
-Instances of abusive, harassing, or otherwise unacceptable behavior can be
-reported by emailing `contact@gitlab.com`.
-
-This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
-available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
-
-## Contribution Flow
-
-When contributing to GitLab, your merge request is subject to review by merge request maintainers of a particular specialty.
-
-When you submit code to GitLab, we really want it to get merged, but there will be times when it will not be merged.
+### Contribution acceptance criteria
-When maintainers are reading through a merge request they may request guidance from other maintainers. If merge request maintainers conclude that the code should not be merged, our reasons will be fully disclosed. If it has been decided that the code quality is not up to GitLab’s standards, the merge request maintainer will refer the author to our docs and code style guides, and provide some guidance.
+This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
-Sometimes style guides will be followed but the code will lack structural integrity, or the maintainer will have reservations about the code’s overall quality. When there is a reservation the maintainer will inform the author and provide some guidance. The author may then choose to update the merge request. Once the merge request has been updated and reassigned to the maintainer, they will review the code again. Once the code has been resubmitted any number of times, the maintainer may choose to close the merge request with a summary of why it will not be merged, as well as some guidance. If the merge request is closed the maintainer will be open to discussion as to how to improve the code so it can be approved in the future.
-GitLab will do its best to review community contributions as quickly as possible. Specially appointed developers review community contributions daily. You may take a look at the [team page](https://about.gitlab.com/team/) for the merge request coach who specializes in the type of code you have written and mention them in the merge request. For example, if you have written some JavaScript in your code then you should mention the frontend merge request coach. If your code has multiple disciplines you may mention multiple merge request coaches.
+## Definition of done
-GitLab receives a lot of community contributions, so if your code has not been reviewed within 4 days of its initial submission feel free to re-mention the appropriate merge request coach.
+This [documentation](doc/development/contributing/merge_request_workflow.md)) has been moved.
-When submitting code to GitLab, you may feel that your contribution requires the aid of an external library. If your code includes an external library please provide a link to the library, as well as reasons for including it.
-When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
+## Style guides
-[core team]: https://about.gitlab.com/core-team/
-[team]: https://about.gitlab.com/team/
-[getting-help]: https://about.gitlab.com/getting-help/
-[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
-[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?assignee_id=0&label_name[]=Accepting%20Merge%20Requests&sort=weight_asc
-[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
-[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
-[google-group]: https://groups.google.com/forum/#!forum/gitlabhq
-[stackoverflow]: https://stackoverflow.com/questions/tagged/gitlab
-[fpl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature+proposal
-[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests
-[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests
-[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
-[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
-[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
-[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
-[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
-[contributor-covenant]: http://contributor-covenant.org
-[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
-[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
-[changelog]: doc/development/changelog.md "Generate a changelog entry"
-[doc-guidelines]: doc/development/documentation/index.md "Documentation guidelines"
-[js-styleguide]: doc/development/fe_guide/style_guide_js.md "JavaScript styleguide"
-[scss-styleguide]: doc/development/fe_guide/style_guide_scss.md "SCSS styleguide"
-[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
-[UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/
-[license-finder-doc]: doc/development/licensing.md
-[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
-[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
-[testing]: doc/development/testing_guide/index.md
-[us-english]: https://en.wikipedia.org/wiki/American_English
+This [documentation](doc/development/contributing/design.md) has been moved.
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 422becb7db8..25fe2ae553e 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -244,6 +244,18 @@ const Api = {
});
},
+ branches(id, query = '', options = {}) {
+ const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id));
+
+ return axios.get(url, {
+ params: {
+ search: query,
+ per_page: 20,
+ ...options,
+ },
+ });
+ },
+
createBranch(id, { ref, branch }) {
const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id));
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 70f20c5c7cf..e34db893989 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -33,19 +33,24 @@ const categoryLabelMap = {
const IS_VISIBLE = 'is-visible';
const IS_RENDERED = 'is-rendered';
-class AwardsHandler {
+export class AwardsHandler {
constructor(emoji) {
this.emoji = emoji;
this.eventListeners = [];
+ this.toggleButtonSelector = '.js-add-award';
+ this.menuClass = 'js-award-emoji-menu';
+ }
+
+ bindEvents() {
// If the user shows intent let's pre-build the menu
this.registerEventListener(
'one',
$(document),
'mouseenter focus',
- '.js-add-award',
+ this.toggleButtonSelector,
'mouseenter focus',
() => {
- const $menu = $('.emoji-menu');
+ const $menu = $(`.${this.menuClass}`);
if ($menu.length === 0) {
requestAnimationFrame(() => {
this.createEmojiMenu();
@@ -53,7 +58,7 @@ class AwardsHandler {
}
},
);
- this.registerEventListener('on', $(document), 'click', '.js-add-award', e => {
+ this.registerEventListener('on', $(document), 'click', this.toggleButtonSelector, e => {
e.stopPropagation();
e.preventDefault();
this.showEmojiMenu($(e.currentTarget));
@@ -61,15 +66,17 @@ class AwardsHandler {
this.registerEventListener('on', $('html'), 'click', e => {
const $target = $(e.target);
- if (!$target.closest('.emoji-menu').length) {
+ if (!$target.closest(`.${this.menuClass}`).length) {
$('.js-awards-block.current').removeClass('current');
- if ($('.emoji-menu').is(':visible')) {
- $('.js-add-award.is-active').removeClass('is-active');
- this.hideMenuElement($('.emoji-menu'));
+ if ($(`.${this.menuClass}`).is(':visible')) {
+ $(`${this.toggleButtonSelector}.is-active`).removeClass('is-active');
+ this.hideMenuElement($(`.${this.menuClass}`));
}
}
});
- this.registerEventListener('on', $(document), 'click', '.js-emoji-btn', e => {
+
+ const emojiButtonSelector = `.js-awards-block .js-emoji-btn, .${this.menuClass} .js-emoji-btn`;
+ this.registerEventListener('on', $(document), 'click', emojiButtonSelector, e => {
e.preventDefault();
const $target = $(e.currentTarget);
const $glEmojiElement = $target.find('gl-emoji');
@@ -101,7 +108,7 @@ class AwardsHandler {
$addBtn.closest('.js-awards-block').addClass('current');
}
- const $menu = $('.emoji-menu');
+ const $menu = $(`.${this.menuClass}`);
const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
const $userAuthored = this.isUserAuthored($addBtn);
if ($menu.length) {
@@ -118,7 +125,7 @@ class AwardsHandler {
} else {
$addBtn.addClass('is-loading is-active');
this.createEmojiMenu(() => {
- const $createdMenu = $('.emoji-menu');
+ const $createdMenu = $(`.${this.menuClass}`);
$addBtn.removeClass('is-loading');
this.positionMenu($createdMenu, $addBtn);
return setTimeout(() => {
@@ -156,7 +163,7 @@ class AwardsHandler {
}
const emojiMenuMarkup = `
- <div class="emoji-menu">
+ <div class="emoji-menu ${this.menuClass}">
<input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" />
<div class="emoji-menu-content">
@@ -185,7 +192,7 @@ class AwardsHandler {
// Avoid the jank and render the remaining categories separately
// This will take more time, but makes UI more responsive
- const menu = document.querySelector('.emoji-menu');
+ const menu = document.querySelector(`.${this.menuClass}`);
const emojiContentElement = menu.querySelector('.emoji-menu-content');
const remainingCategories = Object.keys(categoryMap).slice(1);
const allCategoriesAddedPromise = remainingCategories.reduce(
@@ -270,9 +277,9 @@ class AwardsHandler {
if (isInVueNoteablePage() && !isMainAwardsBlock) {
const id = votesBlock.attr('id').replace('note_', '');
- this.hideMenuElement($('.emoji-menu'));
+ this.hideMenuElement($(`.${this.menuClass}`));
- $('.js-add-award.is-active').removeClass('is-active');
+ $(`${this.toggleButtonSelector}.is-active`).removeClass('is-active');
const toggleAwardEvent = new CustomEvent('toggleAward', {
detail: {
awardName: emoji,
@@ -291,9 +298,9 @@ class AwardsHandler {
return typeof callback === 'function' ? callback() : undefined;
});
- this.hideMenuElement($('.emoji-menu'));
+ this.hideMenuElement($(`.${this.menuClass}`));
- return $('.js-add-award.is-active').removeClass('is-active');
+ return $(`${this.toggleButtonSelector}.is-active`).removeClass('is-active');
}
addAwardToEmojiBar(votesBlock, emoji, checkForMutuality) {
@@ -321,7 +328,7 @@ class AwardsHandler {
getVotesBlock() {
if (isInVueNoteablePage()) {
- const $el = $('.js-add-award.is-active').closest('.note.timeline-entry');
+ const $el = $(`${this.toggleButtonSelector}.is-active`).closest('.note.timeline-entry');
if ($el.length) {
return $el;
@@ -458,7 +465,7 @@ class AwardsHandler {
}
createEmoji(votesBlock, emoji) {
- if ($('.emoji-menu').length) {
+ if ($(`.${this.menuClass}`).length) {
this.createAwardButtonForVotesBlock(votesBlock, emoji);
}
this.createEmojiMenu(() => {
@@ -538,7 +545,7 @@ class AwardsHandler {
this.searchEmojis(term);
});
- const $menu = $('.emoji-menu');
+ const $menu = $(`.${this.menuClass}`);
this.registerEventListener('on', $menu, transitionEndEventString, e => {
if (e.target === e.currentTarget) {
// Clear the search
@@ -608,7 +615,7 @@ class AwardsHandler {
this.eventListeners.forEach(entry => {
entry.element.off.call(entry.element, ...entry.args);
});
- $('.emoji-menu').remove();
+ $(`.${this.menuClass}`).remove();
}
}
@@ -616,7 +623,11 @@ let awardsHandlerPromise = null;
export default function loadAwardsHandler(reload = false) {
if (!awardsHandlerPromise || reload) {
awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(
- Emoji => new AwardsHandler(Emoji),
+ Emoji => {
+ const awardsHandler = new AwardsHandler(Emoji);
+ awardsHandler.bindEvents();
+ return awardsHandler;
+ },
);
}
return awardsHandlerPromise;
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 5c7565234d8..3e610a4088c 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -112,12 +112,20 @@ export default {
if (e.target) {
const containerEl = e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list');
const toBoardType = containerEl.dataset.boardType;
+ const cloneActions = {
+ label: ['milestone', 'assignee'],
+ assignee: ['milestone', 'label'],
+ milestone: ['label', 'assignee'],
+ };
if (toBoardType) {
const fromBoardType = this.list.type;
+ // For each list we check if the destination list is
+ // a the list were we should clone the issue
+ const shouldClone = Object.entries(cloneActions).some(entry => (
+ fromBoardType === entry[0] && entry[1].includes(toBoardType)));
- if ((fromBoardType === 'assignee' && toBoardType === 'label') ||
- (fromBoardType === 'label' && toBoardType === 'assignee')) {
+ if (shouldClone) {
return 'clone';
}
}
@@ -145,7 +153,8 @@ export default {
});
},
onUpdate: (e) => {
- const sortedArray = this.sortable.toArray().filter(id => id !== '-1');
+ const sortedArray = this.sortable.toArray()
+ .filter(id => id !== '-1');
gl.issueBoards.BoardsStore
.moveIssueInList(this.list, Store.moving.issue, e.oldIndex, e.newIndex, sortedArray);
},
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 371be109229..a9102743bf9 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -51,6 +51,16 @@ gl.issueBoards.BoardSidebar = Vue.extend({
canRemove() {
return !this.list.preset;
},
+ hasLabels() {
+ return this.issue.labels && this.issue.labels.length;
+ },
+ labelDropdownTitle() {
+ return this.hasLabels ?
+ `${this.issue.labels[0].title} ${this.issue.labels.length - 1}+ more` : 'Label';
+ },
+ selectedLabels() {
+ return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
+ }
},
watch: {
detail: {
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 76467564608..957114cf420 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -108,6 +108,16 @@ gl.issueBoards.BoardsStore = {
issue.findAssignee(listTo.assignee)) {
const targetIssue = listTo.findIssue(issue.id);
targetIssue.removeAssignee(listFrom.assignee);
+ } else if (listTo.type === 'milestone') {
+ const currentMilestone = issue.milestone;
+ const currentLists = this.state.lists
+ .filter(list => (list.type === 'milestone' && list.id !== listTo.id))
+ .filter(list => list.issues.some(listIssue => issue.id === listIssue.id));
+
+ issue.removeMilestone(currentMilestone);
+ issue.addMilestone(listTo.milestone);
+ currentLists.forEach(currentList => currentList.removeIssue(issue));
+ listTo.addIssue(issue, listFrom, newIndex);
} else {
// Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex);
@@ -125,6 +135,9 @@ gl.issueBoards.BoardsStore = {
} else if (listTo.type === 'backlog' && listFrom.type === 'assignee') {
issue.removeAssignee(listFrom.assignee);
listFrom.removeIssue(issue);
+ } else if (listTo.type === 'backlog' && listFrom.type === 'milestone') {
+ issue.removeMilestone(listFrom.milestone);
+ listFrom.removeIssue(issue);
} else if (this.shouldRemoveIssue(listFrom, listTo)) {
listFrom.removeIssue(issue);
}
@@ -144,7 +157,7 @@ gl.issueBoards.BoardsStore = {
},
findList (key, val, type = 'label') {
const filteredList = this.state.lists.filter((list) => {
- const byType = type ? (list.type === type) || (list.type === 'assignee') : true;
+ const byType = type ? (list.type === type) || (list.type === 'assignee') || (list.type === 'milestone') : true;
return list[key] === val && byType;
});
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index e565af800d0..0fdf0c7a389 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -6,7 +6,7 @@ import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub';
import {
- APPLICATION_INSTALLED,
+ APPLICATION_STATUS,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
@@ -177,8 +177,8 @@ export default class Clusters {
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
const appTitles = Object.keys(newApplicationMap)
- .filter(appId => newApplicationMap[appId].status === APPLICATION_INSTALLED &&
- prevApplicationMap[appId].status !== APPLICATION_INSTALLED &&
+ .filter(appId => newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
+ prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
prevApplicationMap[appId].status !== null)
.map(appId => newApplicationMap[appId].title);
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index ec52fdfdf32..651f3b50236 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -4,12 +4,7 @@
import eventHub from '../event_hub';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import {
- APPLICATION_NOT_INSTALLABLE,
- APPLICATION_SCHEDULED,
- APPLICATION_INSTALLABLE,
- APPLICATION_INSTALLING,
- APPLICATION_INSTALLED,
- APPLICATION_ERROR,
+ APPLICATION_STATUS,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
@@ -59,49 +54,57 @@
},
},
computed: {
+ isUnknownStatus() {
+ return !this.isKnownStatus && this.status !== null;
+ },
+ isKnownStatus() {
+ return Object.values(APPLICATION_STATUS).includes(this.status);
+ },
rowJsClass() {
return `js-cluster-application-row-${this.id}`;
},
installButtonLoading() {
return !this.status ||
- this.status === APPLICATION_SCHEDULED ||
- this.status === APPLICATION_INSTALLING ||
+ this.status === APPLICATION_STATUS.SCHEDULED ||
+ this.status === APPLICATION_STATUS.INSTALLING ||
this.requestStatus === REQUEST_LOADING;
},
installButtonDisabled() {
- // Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but
+ // Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but
// we already made a request to install and are just waiting for the real-time
// to sync up.
- return (this.status !== APPLICATION_INSTALLABLE
- && this.status !== APPLICATION_ERROR) ||
+ return ((this.status !== APPLICATION_STATUS.INSTALLABLE
+ && this.status !== APPLICATION_STATUS.ERROR) ||
this.requestStatus === REQUEST_LOADING ||
- this.requestStatus === REQUEST_SUCCESS;
+ this.requestStatus === REQUEST_SUCCESS) && this.isKnownStatus;
},
installButtonLabel() {
let label;
if (
- this.status === APPLICATION_NOT_INSTALLABLE ||
- this.status === APPLICATION_INSTALLABLE ||
- this.status === APPLICATION_ERROR
+ this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
+ this.status === APPLICATION_STATUS.INSTALLABLE ||
+ this.status === APPLICATION_STATUS.ERROR ||
+ this.isUnknownStatus
) {
label = s__('ClusterIntegration|Install');
- } else if (this.status === APPLICATION_SCHEDULED ||
- this.status === APPLICATION_INSTALLING) {
+ } else if (this.status === APPLICATION_STATUS.SCHEDULED ||
+ this.status === APPLICATION_STATUS.INSTALLING) {
label = s__('ClusterIntegration|Installing');
- } else if (this.status === APPLICATION_INSTALLED) {
+ } else if (this.status === APPLICATION_STATUS.INSTALLED ||
+ this.status === APPLICATION_STATUS.UPDATED) {
label = s__('ClusterIntegration|Installed');
}
return label;
},
showManageButton() {
- return this.manageLink && this.status === APPLICATION_INSTALLED;
+ return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
},
manageButtonLabel() {
return s__('ClusterIntegration|Manage');
},
hasError() {
- return this.status === APPLICATION_ERROR ||
+ return this.status === APPLICATION_STATUS.ERROR ||
this.requestStatus === REQUEST_FAILURE;
},
generalErrorDescription() {
@@ -182,7 +185,7 @@
</div>
</div>
<div
- v-if="hasError"
+ v-if="hasError || isUnknownStatus"
class="gl-responsive-table-row-layout"
role="row"
>
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 8ee7279e544..d708a9e595a 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -3,7 +3,7 @@ import _ from 'underscore';
import { s__, sprintf } from '../../locale';
import applicationRow from './application_row.vue';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
-import { APPLICATION_INSTALLED, INGRESS } from '../constants';
+import { APPLICATION_STATUS, INGRESS } from '../constants';
export default {
components: {
@@ -58,7 +58,7 @@ export default {
return INGRESS;
},
ingressInstalled() {
- return this.applications.ingress.status === APPLICATION_INSTALLED;
+ return this.applications.ingress.status === APPLICATION_STATUS.INSTALLED;
},
ingressExternalIp() {
return this.applications.ingress.externalIp;
@@ -122,7 +122,7 @@ export default {
);
},
jupyterInstalled() {
- return this.applications.jupyter.status === APPLICATION_INSTALLED;
+ return this.applications.jupyter.status === APPLICATION_STATUS.INSTALLED;
},
jupyterHostname() {
return this.applications.jupyter.hostname;
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index 371f71fde44..72fc9355d82 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -1,10 +1,13 @@
// These need to match what is returned from the server
-export const APPLICATION_NOT_INSTALLABLE = 'not_installable';
-export const APPLICATION_INSTALLABLE = 'installable';
-export const APPLICATION_SCHEDULED = 'scheduled';
-export const APPLICATION_INSTALLING = 'installing';
-export const APPLICATION_INSTALLED = 'installed';
-export const APPLICATION_ERROR = 'errored';
+export const APPLICATION_STATUS = {
+ NOT_INSTALLABLE: 'not_installable',
+ INSTALLABLE: 'installable',
+ SCHEDULED: 'scheduled',
+ INSTALLING: 'installing',
+ INSTALLED: 'installed',
+ UPDATED: 'updated',
+ ERROR: 'errored',
+};
// These are only used client-side
export const REQUEST_LOADING = 'request-loading';
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index f595f3c3187..589eeee9695 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -19,3 +19,4 @@ import './polyfills/custom_event';
import './polyfills/element';
import './polyfills/event';
import './polyfills/nodelist';
+import './polyfills/request_idle_callback';
diff --git a/app/assets/javascripts/commons/polyfills/request_idle_callback.js b/app/assets/javascripts/commons/polyfills/request_idle_callback.js
new file mode 100644
index 00000000000..2356569d06e
--- /dev/null
+++ b/app/assets/javascripts/commons/polyfills/request_idle_callback.js
@@ -0,0 +1,17 @@
+window.requestIdleCallback =
+ window.requestIdleCallback ||
+ function requestShim(cb) {
+ const start = Date.now();
+ return setTimeout(() => {
+ cb({
+ didTimeout: false,
+ timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
+ });
+ }, 1);
+ };
+
+window.cancelIdleCallback =
+ window.cancelIdleCallback ||
+ function cancelShim(id) {
+ clearTimeout(id);
+ };
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 a73f898e10b..8ad1ea34245 100644
--- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
@@ -71,13 +71,23 @@ export default {
required: false,
default: false,
},
+ isHover: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ discussions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
computed: {
...mapState({
diffViewType: state => state.diffs.diffViewType,
diffFiles: state => state.diffs.diffFiles,
}),
- ...mapGetters(['isLoggedIn', 'discussionsByLineCode']),
+ ...mapGetters(['isLoggedIn']),
lineHref() {
return this.lineCode ? `#${this.lineCode}` : '#';
},
@@ -85,26 +95,22 @@ export default {
return (
this.isLoggedIn &&
this.showCommentButton &&
+ this.isHover &&
!this.isMatchLine &&
!this.isContextLine &&
- !this.hasDiscussions &&
- !this.isMetaLine
+ !this.isMetaLine &&
+ !this.hasDiscussions
);
},
- discussions() {
- return this.discussionsByLineCode[this.lineCode] || [];
- },
hasDiscussions() {
return this.discussions.length > 0;
},
shouldShowAvatarsOnGutter() {
- let render = this.hasDiscussions && this.showCommentButton;
-
if (!this.lineType && this.linePosition === LINE_POSITION_RIGHT) {
- render = false;
+ return false;
}
- return render;
+ return this.showCommentButton && this.hasDiscussions;
},
},
methods: {
@@ -176,7 +182,7 @@ export default {
v-else
>
<button
- v-show="shouldShowCommentButton"
+ v-if="shouldShowCommentButton"
type="button"
class="add-diff-note js-add-diff-note-button"
title="Add a comment to this line"
diff --git a/app/assets/javascripts/diffs/components/diff_table_cell.vue b/app/assets/javascripts/diffs/components/diff_table_cell.vue
index 5962f30d9bb..33bc8d9971e 100644
--- a/app/assets/javascripts/diffs/components/diff_table_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_table_cell.vue
@@ -67,6 +67,11 @@ export default {
required: false,
default: false,
},
+ discussions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
computed: {
...mapGetters(['isLoggedIn']),
@@ -132,10 +137,12 @@ export default {
:line-number="lineNumber"
:meta-data="normalizedLine.metaData"
:show-comment-button="showCommentButton"
+ :is-hover="isHover"
:is-bottom="isBottom"
:is-match-line="isMatchLine"
:is-context-line="isContentLine"
:is-meta-line="isMetaLine"
+ :discussions="discussions"
/>
</td>
</template>
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 ca265dd892c..caf84dc9573 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
@@ -1,5 +1,5 @@
<script>
-import { mapState, mapGetters } from 'vuex';
+import { mapState } from 'vuex';
import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue';
@@ -21,15 +21,16 @@ export default {
type: Number,
required: true,
},
+ discussions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
computed: {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
- ...mapGetters(['discussionsByLineCode']),
- discussions() {
- return this.discussionsByLineCode[this.line.lineCode] || [];
- },
className() {
return this.discussions.length ? '' : 'js-temp-notes-holder';
},
diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
index 0e306f39a9f..32d65ff994f 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
@@ -33,6 +33,11 @@ export default {
required: false,
default: false,
},
+ discussions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
return {
@@ -89,6 +94,7 @@ export default {
:is-bottom="isBottom"
:is-hover="isHover"
:show-comment-button="true"
+ :discussions="discussions"
class="diff-line-num old_line"
/>
<diff-table-cell
@@ -98,6 +104,7 @@ export default {
:line-type="newLineType"
:is-bottom="isBottom"
:is-hover="isHover"
+ :discussions="discussions"
class="diff-line-num new_line"
/>
<td
diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue
index 9fd19b74cd7..e7d789734c3 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -20,8 +20,11 @@ export default {
},
},
computed: {
- ...mapGetters('diffs', ['commitId']),
- ...mapGetters(['discussionsByLineCode']),
+ ...mapGetters('diffs', [
+ 'commitId',
+ 'shouldRenderInlineCommentRow',
+ 'singleDiscussionByLineCode',
+ ]),
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
@@ -36,15 +39,8 @@ export default {
},
},
methods: {
- shouldRenderCommentRow(line) {
- if (this.diffLineCommentForms[line.lineCode]) return true;
-
- const lineDiscussions = this.discussionsByLineCode[line.lineCode];
- if (lineDiscussions === undefined) {
- return false;
- }
-
- return lineDiscussions.every(discussion => discussion.expanded);
+ discussionsList(line) {
+ return line.lineCode !== undefined ? this.singleDiscussionByLineCode(line.lineCode) : [];
},
},
};
@@ -65,13 +61,15 @@ export default {
:line="line"
:is-bottom="index + 1 === diffLinesLength"
:key="line.lineCode"
+ :discussions="discussionsList(line)"
/>
<inline-diff-comment-row
- v-if="shouldRenderCommentRow(line)"
+ v-if="shouldRenderInlineCommentRow(line)"
:diff-file-hash="diffFile.fileHash"
:line="line"
:line-index="index"
:key="index"
+ :discussions="discussionsList(line)"
/>
</template>
</tbody>
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 cc5248c25d9..48b8feeb0b4 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -1,5 +1,5 @@
<script>
-import { mapState, mapGetters } from 'vuex';
+import { mapState } from 'vuex';
import diffDiscussions from './diff_discussions.vue';
import diffLineNoteForm from './diff_line_note_form.vue';
@@ -21,30 +21,34 @@ export default {
type: Number,
required: true,
},
+ leftDiscussions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ rightDiscussions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
computed: {
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
- ...mapGetters(['discussionsByLineCode']),
leftLineCode() {
return this.line.left.lineCode;
},
rightLineCode() {
return this.line.right.lineCode;
},
- hasDiscussion() {
- const discussions = this.discussionsByLineCode;
-
- return discussions[this.leftLineCode] || discussions[this.rightLineCode];
- },
hasExpandedDiscussionOnLeft() {
- const discussions = this.discussionsByLineCode[this.leftLineCode];
+ const discussions = this.leftDiscussions;
return discussions ? discussions.every(discussion => discussion.expanded) : false;
},
hasExpandedDiscussionOnRight() {
- const discussions = this.discussionsByLineCode[this.rightLineCode];
+ const discussions = this.rightDiscussions;
return discussions ? discussions.every(discussion => discussion.expanded) : false;
},
@@ -52,17 +56,18 @@ export default {
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
},
shouldRenderDiscussionsOnLeft() {
- return this.discussionsByLineCode[this.leftLineCode] && this.hasExpandedDiscussionOnLeft;
+ return this.leftDiscussions && this.hasExpandedDiscussionOnLeft;
},
shouldRenderDiscussionsOnRight() {
- return (
- this.discussionsByLineCode[this.rightLineCode] &&
- this.hasExpandedDiscussionOnRight &&
- this.line.right.type
- );
+ return this.rightDiscussions && this.hasExpandedDiscussionOnRight && this.line.right.type;
+ },
+ showRightSideCommentForm() {
+ return this.line.right.type && this.diffLineCommentForms[this.rightLineCode];
},
className() {
- return this.hasDiscussion ? '' : 'js-temp-notes-holder';
+ return this.leftDiscussions.length > 0 || this.rightDiscussions.length > 0
+ ? ''
+ : 'js-temp-notes-holder';
},
},
};
@@ -80,13 +85,12 @@ export default {
class="content"
>
<diff-discussions
- v-if="discussionsByLineCode[leftLineCode].length"
- :discussions="discussionsByLineCode[leftLineCode]"
+ v-if="leftDiscussions.length"
+ :discussions="leftDiscussions"
/>
</div>
<diff-line-note-form
- v-if="diffLineCommentForms[leftLineCode] &&
- diffLineCommentForms[leftLineCode]"
+ v-if="diffLineCommentForms[leftLineCode]"
:diff-file-hash="diffFileHash"
:line="line.left"
:note-target-line="line.left"
@@ -100,13 +104,12 @@ export default {
class="content"
>
<diff-discussions
- v-if="discussionsByLineCode[rightLineCode].length"
- :discussions="discussionsByLineCode[rightLineCode]"
+ v-if="rightDiscussions.length"
+ :discussions="rightDiscussions"
/>
</div>
<diff-line-note-form
- v-if="diffLineCommentForms[rightLineCode] &&
- diffLineCommentForms[rightLineCode] && line.right.type"
+ v-if="showRightSideCommentForm"
:diff-file-hash="diffFileHash"
:line="line.right"
:note-target-line="line.right"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
index 0031cedc68f..d4e54c2bd00 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
@@ -36,6 +36,16 @@ export default {
required: false,
default: false,
},
+ leftDiscussions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ rightDiscussions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
return {
@@ -116,6 +126,7 @@ export default {
:is-hover="isLeftHover"
:show-comment-button="true"
:diff-view-type="parallelDiffViewType"
+ :discussions="leftDiscussions"
class="diff-line-num old_line"
/>
<td
@@ -136,6 +147,7 @@ export default {
:is-hover="isRightHover"
:show-comment-button="true"
:diff-view-type="parallelDiffViewType"
+ :discussions="rightDiscussions"
class="diff-line-num new_line"
/>
<td
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index 32528c9e7ab..24ceb52a04a 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -21,8 +21,11 @@ export default {
},
},
computed: {
- ...mapGetters('diffs', ['commitId']),
- ...mapGetters(['discussionsByLineCode']),
+ ...mapGetters('diffs', [
+ 'commitId',
+ 'singleDiscussionByLineCode',
+ 'shouldRenderParallelCommentRow',
+ ]),
...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}),
@@ -53,29 +56,9 @@ export default {
},
},
methods: {
- shouldRenderCommentRow(line) {
- const leftLineCode = line.left.lineCode;
- const rightLineCode = line.right.lineCode;
- const discussions = this.discussionsByLineCode;
- const leftDiscussions = discussions[leftLineCode];
- const rightDiscussions = discussions[rightLineCode];
- const hasDiscussion = leftDiscussions || rightDiscussions;
-
- const hasExpandedDiscussionOnLeft = leftDiscussions
- ? leftDiscussions.every(discussion => discussion.expanded)
- : false;
- const hasExpandedDiscussionOnRight = rightDiscussions
- ? rightDiscussions.every(discussion => discussion.expanded)
- : false;
-
- if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
- return true;
- }
-
- const hasCommentFormOnLeft = this.diffLineCommentForms[leftLineCode];
- const hasCommentFormOnRight = this.diffLineCommentForms[rightLineCode];
-
- return hasCommentFormOnLeft || hasCommentFormOnRight;
+ discussionsByLine(line, leftOrRight) {
+ return line[leftOrRight] && line[leftOrRight].lineCode !== undefined ?
+ this.singleDiscussionByLineCode(line[leftOrRight].lineCode) : [];
},
},
};
@@ -98,13 +81,17 @@ export default {
:line="line"
:is-bottom="index + 1 === diffLinesLength"
:key="index"
+ :left-discussions="discussionsByLine(line, 'left')"
+ :right-discussions="discussionsByLine(line, 'right')"
/>
<parallel-diff-comment-row
- v-if="shouldRenderCommentRow(line)"
+ v-if="shouldRenderParallelCommentRow(line)"
:key="`dcr-${index}`"
:line="line"
:diff-file-hash="diffFile.fileHash"
:line-index="index"
+ :left-discussions="discussionsByLine(line, 'left')"
+ :right-discussions="discussionsByLine(line, 'right')"
/>
</template>
</tbody>
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index 9aec117c236..4a47646d7fa 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -64,6 +64,47 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash),
) || [];
+export const singleDiscussionByLineCode = (state, getters, rootState, rootGetters) => lineCode => {
+ if (!lineCode || lineCode === undefined) return [];
+ const discussions = rootGetters.discussionsByLineCode;
+ return discussions[lineCode] || [];
+};
+
+export const shouldRenderParallelCommentRow = (state, getters) => line => {
+ const leftLineCode = line.left.lineCode;
+ const rightLineCode = line.right.lineCode;
+ const leftDiscussions = getters.singleDiscussionByLineCode(leftLineCode);
+ const rightDiscussions = getters.singleDiscussionByLineCode(rightLineCode);
+ const hasDiscussion = leftDiscussions.length || rightDiscussions.length;
+
+ const hasExpandedDiscussionOnLeft = leftDiscussions.length
+ ? leftDiscussions.every(discussion => discussion.expanded)
+ : false;
+ const hasExpandedDiscussionOnRight = rightDiscussions.length
+ ? rightDiscussions.every(discussion => discussion.expanded)
+ : false;
+
+ if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
+ return true;
+ }
+
+ const hasCommentFormOnLeft = state.diffLineCommentForms[leftLineCode];
+ const hasCommentFormOnRight = state.diffLineCommentForms[rightLineCode];
+
+ return hasCommentFormOnLeft || hasCommentFormOnRight;
+};
+
+export const shouldRenderInlineCommentRow = (state, getters) => line => {
+ if (state.diffLineCommentForms[line.lineCode]) return true;
+
+ const lineDiscussions = getters.singleDiscussionByLineCode(line.lineCode);
+ if (lineDiscussions.length === 0) {
+ return false;
+ }
+
+ return lineDiscussions.every(discussion => discussion.expanded);
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
export const getDiffFileByHash = state => fileHash =>
state.diffFiles.find(file => file.fileHash === fileHash);
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index d9589baa76e..82082ac508a 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -173,3 +173,24 @@ export function trimFirstCharOfLineContent(line = {}) {
return parsedLine;
}
+
+export function getDiffRefsByLineCode(diffFiles) {
+ return diffFiles.reduce((acc, diffFile) => {
+ const { baseSha, headSha, startSha } = diffFile.diffRefs;
+ const { newPath, oldPath } = diffFile;
+
+ // We can only use highlightedDiffLines to create the map of diff lines because
+ // highlightedDiffLines will also include every parallel diff line in it.
+ if (diffFile.highlightedDiffLines) {
+ diffFile.highlightedDiffLines.forEach(line => {
+ const { lineCode, oldLine, newLine } = line;
+
+ if (lineCode) {
+ acc[lineCode] = { baseSha, headSha, startSha, newPath, oldPath, oldLine, newLine };
+ }
+ });
+ }
+
+ return acc;
+ }, {});
+}
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index cbc05b229cb..c3959ef3e9e 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -616,7 +616,11 @@ GitLabDropdown = (function() {
}
if (this.options.opened) {
- this.options.opened.call(this, e);
+ if (this.options.preserveContext) {
+ this.options.opened(e);
+ } else {
+ this.options.opened.call(this, e);
+ }
}
return this.dropdown.trigger('shown.gl.dropdown');
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index efbf2e3a295..2b9e2a929fc 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -78,17 +78,10 @@ export default {
>
<div
:class="{ 'project-row-contents': !isGroup }"
- class="group-row-contents">
- <item-actions
- v-if="isGroup"
- :group="group"
- :parent-group="parentGroup"
- />
- <item-stats
- :item="group"
- />
+ class="group-row-contents d-flex justify-content-end align-items-center"
+ >
<div
- class="folder-toggle-wrap"
+ class="folder-toggle-wrap append-right-4 d-flex align-items-center"
>
<item-caret
:is-group-open="group.isOpen"
@@ -100,7 +93,7 @@ export default {
</div>
<div
:class="{ 'content-loading': group.isChildrenLoading }"
- class="avatar-container prepend-top-8 prepend-left-5 s24 d-none d-sm-block"
+ class="avatar-container s24 d-none d-sm-block"
>
<a
:href="group.relativePath"
@@ -120,32 +113,46 @@ export default {
</a>
</div>
<div
- class="title namespace-title"
+ class="group-text flex-grow"
>
- <a
- v-tooltip
- :href="group.relativePath"
- :title="group.fullName"
- class="no-expand"
- data-placement="bottom"
- >{{
- // ending bracket must be by closing tag to prevent
- // link hover text-decoration from over-extending
- group.name
- }}</a>
- <span
- v-if="group.permission"
- class="user-access-role"
+ <div
+ class="title namespace-title append-right-8"
>
- {{ group.permission }}
- </span>
- </div>
- <div
- v-if="group.description"
- class="description">
- <span v-html="group.description">
- </span>
+ <a
+ v-tooltip
+ :href="group.relativePath"
+ :title="group.fullName"
+ class="no-expand"
+ data-placement="bottom"
+ >{{
+ // ending bracket must be by closing tag to prevent
+ // link hover text-decoration from over-extending
+ group.name
+ }}</a>
+ <span
+ v-if="group.permission"
+ class="user-access-role"
+ >
+ {{ group.permission }}
+ </span>
+ </div>
+ <div
+ v-if="group.description"
+ class="description"
+ >
+ <span v-html="group.description">
+ </span>
+ </div>
</div>
+ <item-stats
+ :item="group"
+ class="group-stats prepend-top-2"
+ />
+ <item-actions
+ v-if="isGroup"
+ :group="group"
+ :parent-group="parentGroup"
+ />
</div>
<group-folder
v-if="group.isOpen && hasChildren"
diff --git a/app/assets/javascripts/ide/components/branches/item.vue b/app/assets/javascripts/ide/components/branches/item.vue
new file mode 100644
index 00000000000..cc3e84e3f77
--- /dev/null
+++ b/app/assets/javascripts/ide/components/branches/item.vue
@@ -0,0 +1,60 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
+import router from '../../ide_router';
+
+export default {
+ components: {
+ Icon,
+ Timeago,
+ },
+ props: {
+ item: {
+ type: Object,
+ required: true,
+ },
+ projectId: {
+ type: String,
+ required: true,
+ },
+ isActive: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ branchHref() {
+ return router.resolve(`/project/${this.projectId}/edit/${this.item.name}`).href;
+ },
+ },
+};
+</script>
+
+<template>
+ <a
+ :href="branchHref"
+ class="btn-link d-flex align-items-center"
+ >
+ <span class="d-flex append-right-default ide-search-list-current-icon">
+ <icon
+ v-if="isActive"
+ :size="18"
+ name="mobile-issue-close"
+ />
+ </span>
+ <span>
+ <strong>
+ {{ item.name }}
+ </strong>
+ <span
+ class="ide-merge-request-project-path d-block mt-1"
+ >
+ Updated
+ <timeago
+ :time="item.committedDate || ''"
+ />
+ </span>
+ </span>
+ </a>
+</template>
diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue
new file mode 100644
index 00000000000..6db7b9d6b0e
--- /dev/null
+++ b/app/assets/javascripts/ide/components/branches/search_list.vue
@@ -0,0 +1,111 @@
+<script>
+import { mapActions, mapState } from 'vuex';
+import _ from 'underscore';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import Item from './item.vue';
+
+export default {
+ components: {
+ LoadingIcon,
+ Item,
+ Icon,
+ },
+ data() {
+ return {
+ search: '',
+ };
+ },
+ computed: {
+ ...mapState('branches', ['branches', 'isLoading']),
+ ...mapState(['currentBranchId', 'currentProjectId']),
+ hasBranches() {
+ return this.branches.length !== 0;
+ },
+ hasNoSearchResults() {
+ return this.search !== '' && !this.hasBranches;
+ },
+ },
+ watch: {
+ isLoading: {
+ handler: 'focusSearch',
+ },
+ },
+ mounted() {
+ this.loadBranches();
+ },
+ methods: {
+ ...mapActions('branches', ['fetchBranches']),
+ loadBranches() {
+ this.fetchBranches({ search: this.search });
+ },
+ searchBranches: _.debounce(function debounceSearch() {
+ this.loadBranches();
+ }, 250),
+ focusSearch() {
+ if (!this.isLoading) {
+ this.$nextTick(() => {
+ this.$refs.searchInput.focus();
+ });
+ }
+ },
+ isActiveBranch(item) {
+ return item.name === this.currentBranchId;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="dropdown-input mt-3 pb-3 mb-0 border-bottom">
+ <div class="position-relative">
+ <input
+ ref="searchInput"
+ :placeholder="__('Search branches')"
+ v-model="search"
+ type="search"
+ class="form-control dropdown-input-field"
+ @input="searchBranches"
+ />
+ <icon
+ :size="18"
+ name="search"
+ class="input-icon"
+ />
+ </div>
+ </div>
+ <div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
+ <loading-icon
+ v-if="isLoading"
+ class="mt-3 mb-3 align-self-center ml-auto mr-auto"
+ size="2"
+ />
+ <ul
+ v-else
+ class="mb-3 w-100"
+ >
+ <template v-if="hasBranches">
+ <li
+ v-for="item in branches"
+ :key="item.name"
+ >
+ <item
+ :item="item"
+ :project-id="currentProjectId"
+ :is-active="isActiveBranch(item)"
+ />
+ </li>
+ </template>
+ <li
+ v-else
+ class="ide-search-list-empty d-flex align-items-center justify-content-center"
+ >
+ <template v-if="hasNoSearchResults">
+ {{ __('No branches found') }}
+ </template>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue
index 33f1179a234..39d46a91731 100644
--- a/app/assets/javascripts/ide/components/ide_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_tree.vue
@@ -41,7 +41,7 @@ export default {
slot="header"
>
{{ __('Edit') }}
- <div class="ml-auto d-flex">
+ <div class="ide-tree-actions ml-auto d-flex">
<new-entry-button
:label="__('New file')"
:show-label="false"
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index e303ff6ea8f..5611b37be7c 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -3,14 +3,14 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import RepoFile from './repo_file.vue';
-import NewDropdown from './new_dropdown/index.vue';
+import NavDropdown from './nav_dropdown.vue';
export default {
components: {
Icon,
RepoFile,
SkeletonLoadingContainer,
- NewDropdown,
+ NavDropdown,
},
props: {
viewerType: {
@@ -57,6 +57,7 @@ export default {
:class="headerClass"
class="ide-tree-header"
>
+ <nav-dropdown />
<slot name="header"></slot>
</header>
<div
diff --git a/app/assets/javascripts/ide/components/merge_requests/dropdown.vue b/app/assets/javascripts/ide/components/merge_requests/dropdown.vue
deleted file mode 100644
index 4b9824bf04b..00000000000
--- a/app/assets/javascripts/ide/components/merge_requests/dropdown.vue
+++ /dev/null
@@ -1,63 +0,0 @@
-<script>
-import { mapGetters } from 'vuex';
-import Tabs from '../../../vue_shared/components/tabs/tabs';
-import Tab from '../../../vue_shared/components/tabs/tab.vue';
-import List from './list.vue';
-
-export default {
- components: {
- Tabs,
- Tab,
- List,
- },
- props: {
- show: {
- type: Boolean,
- required: true,
- },
- },
- computed: {
- ...mapGetters('mergeRequests', ['assignedData', 'createdData']),
- createdMergeRequestLength() {
- return this.createdData.mergeRequests.length;
- },
- assignedMergeRequestLength() {
- return this.assignedData.mergeRequests.length;
- },
- },
-};
-</script>
-
-<template>
- <div class="dropdown-menu ide-merge-requests-dropdown p-0">
- <tabs
- v-if="show"
- stop-propagation
- >
- <tab active>
- <template slot="title">
- {{ __('Created by me') }}
- <span class="badge badge-pill">
- {{ createdMergeRequestLength }}
- </span>
- </template>
- <list
- :empty-text="__('You have not created any merge requests')"
- type="created"
- />
- </tab>
- <tab>
- <template slot="title">
- {{ __('Assigned to me') }}
- <span class="badge badge-pill">
- {{ assignedMergeRequestLength }}
- </span>
- </template>
- <list
- :empty-text="__('You do not have any assigned merge requests')"
- type="assigned"
- />
- </tab>
- </tabs>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/merge_requests/item.vue b/app/assets/javascripts/ide/components/merge_requests/item.vue
index 4e18376bd48..0c4ea80ba08 100644
--- a/app/assets/javascripts/ide/components/merge_requests/item.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/item.vue
@@ -1,5 +1,6 @@
<script>
import Icon from '../../../vue_shared/components/icon.vue';
+import router from '../../ide_router';
export default {
components: {
@@ -29,22 +30,21 @@ export default {
pathWithID() {
return `${this.item.projectPathWithNamespace}!${this.item.iid}`;
},
- },
- methods: {
- clickItem() {
- this.$emit('click', this.item);
+ mergeRequestHref() {
+ const path = `/project/${this.item.projectPathWithNamespace}/merge_requests/${this.item.iid}`;
+
+ return router.resolve(path).href;
},
},
};
</script>
<template>
- <button
- type="button"
+ <a
+ :href="mergeRequestHref"
class="btn-link d-flex align-items-center"
- @click="clickItem"
>
- <span class="d-flex append-right-default ide-merge-request-current-icon">
+ <span class="d-flex append-right-default ide-search-list-current-icon">
<icon
v-if="isActive"
:size="18"
@@ -59,5 +59,5 @@ export default {
{{ pathWithID }}
</span>
</span>
- </button>
+ </a>
</template>
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index 19d3e48ee10..fc612956688 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -1,96 +1,101 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
+import { mapActions, mapState } from 'vuex';
import _ from 'underscore';
-import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
+import { __ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Item from './item.vue';
+import TokenedInput from '../shared/tokened_input.vue';
+
+const SEARCH_TYPES = [
+ { type: 'created', label: __('Created by me') },
+ { type: 'assigned', label: __('Assigned to me') },
+];
export default {
components: {
LoadingIcon,
+ TokenedInput,
Item,
- },
- props: {
- type: {
- type: String,
- required: true,
- },
- emptyText: {
- type: String,
- required: true,
- },
+ Icon,
},
data() {
return {
search: '',
+ currentSearchType: null,
+ hasSearchFocus: false,
};
},
computed: {
- ...mapGetters('mergeRequests', ['getData']),
+ ...mapState('mergeRequests', ['mergeRequests', 'isLoading']),
...mapState(['currentMergeRequestId', 'currentProjectId']),
- data() {
- return this.getData(this.type);
- },
- isLoading() {
- return this.data.isLoading;
- },
- mergeRequests() {
- return this.data.mergeRequests;
- },
hasMergeRequests() {
return this.mergeRequests.length !== 0;
},
hasNoSearchResults() {
return this.search !== '' && !this.hasMergeRequests;
},
+ showSearchTypes() {
+ return this.hasSearchFocus && !this.search && !this.currentSearchType;
+ },
+ type() {
+ return this.currentSearchType
+ ? this.currentSearchType.type
+ : '';
+ },
+ searchTokens() {
+ return this.currentSearchType
+ ? [this.currentSearchType]
+ : [];
+ },
},
watch: {
- isLoading: {
- handler: 'focusSearch',
+ search() {
+ // When the search is updated, let's turn off this flag to hide the search types
+ this.hasSearchFocus = false;
},
},
mounted() {
this.loadMergeRequests();
},
methods: {
- ...mapActions('mergeRequests', ['fetchMergeRequests', 'openMergeRequest']),
+ ...mapActions('mergeRequests', ['fetchMergeRequests']),
loadMergeRequests() {
this.fetchMergeRequests({ type: this.type, search: this.search });
},
- viewMergeRequest(item) {
- this.openMergeRequest({
- projectPath: item.projectPathWithNamespace,
- id: item.iid,
- });
- },
searchMergeRequests: _.debounce(function debounceSearch() {
this.loadMergeRequests();
}, 250),
- focusSearch() {
- if (!this.isLoading) {
- this.$nextTick(() => {
- this.$refs.searchInput.focus();
- });
- }
+ onSearchFocus() {
+ this.hasSearchFocus = true;
+ },
+ setSearchType(searchType) {
+ this.currentSearchType = searchType;
+ this.loadMergeRequests();
},
},
+ searchTypes: SEARCH_TYPES,
};
</script>
<template>
<div>
<div class="dropdown-input mt-3 pb-3 mb-0 border-bottom">
- <input
- ref="searchInput"
- :placeholder="__('Search merge requests')"
- v-model="search"
- type="search"
- class="dropdown-input-field"
- @input="searchMergeRequests"
- />
- <i
- aria-hidden="true"
- class="fa fa-search dropdown-input-search"
- ></i>
+ <div class="position-relative">
+ <tokened-input
+ v-model="search"
+ :tokens="searchTokens"
+ :placeholder="__('Search merge requests')"
+ @focus="onSearchFocus"
+ @input="searchMergeRequests"
+ @removeToken="setSearchType(null)"
+ />
+ <icon
+ :size="18"
+ name="search"
+ class="input-icon"
+ />
+ </div>
</div>
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
<loading-icon
@@ -98,35 +103,52 @@ export default {
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
size="2"
/>
- <ul
- v-else
- class="mb-3 w-100"
- >
- <template v-if="hasMergeRequests">
- <li
- v-for="item in mergeRequests"
- :key="item.id"
- >
- <item
- :item="item"
- :current-id="currentMergeRequestId"
- :current-project-id="currentProjectId"
- @click="viewMergeRequest"
- />
- </li>
- </template>
- <li
- v-else
- class="ide-merge-requests-empty d-flex align-items-center justify-content-center"
+ <template v-else>
+ <ul
+ class="mb-3 w-100"
>
- <template v-if="hasNoSearchResults">
- {{ __('No merge requests found') }}
+ <template v-if="showSearchTypes">
+ <li
+ v-for="searchType in $options.searchTypes"
+ :key="searchType.type"
+ >
+ <button
+ type="button"
+ class="btn-link d-flex align-items-center"
+ @click.stop="setSearchType(searchType)"
+ >
+ <span class="d-flex append-right-default ide-search-list-current-icon">
+ <icon
+ :size="18"
+ name="search"
+ />
+ </span>
+ <span>
+ {{ searchType.label }}
+ </span>
+ </button>
+ </li>
</template>
- <template v-else>
- {{ emptyText }}
+ <template v-else-if="hasMergeRequests">
+ <li
+ v-for="item in mergeRequests"
+ :key="item.id"
+ >
+ <item
+ :item="item"
+ :current-id="currentMergeRequestId"
+ :current-project-id="currentProjectId"
+ />
+ </li>
</template>
- </li>
- </ul>
+ <li
+ v-else
+ class="ide-search-list-empty d-flex align-items-center justify-content-center"
+ >
+ {{ __('No merge requests found') }}
+ </li>
+ </ul>
+ </template>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/nav_dropdown.vue b/app/assets/javascripts/ide/components/nav_dropdown.vue
new file mode 100644
index 00000000000..db36779c395
--- /dev/null
+++ b/app/assets/javascripts/ide/components/nav_dropdown.vue
@@ -0,0 +1,59 @@
+<script>
+import $ from 'jquery';
+import Icon from '~/vue_shared/components/icon.vue';
+import NavForm from './nav_form.vue';
+import NavDropdownButton from './nav_dropdown_button.vue';
+
+export default {
+ components: {
+ Icon,
+ NavDropdownButton,
+ NavForm,
+ },
+ data() {
+ return {
+ isVisibleDropdown: false,
+ };
+ },
+ mounted() {
+ this.addDropdownListeners();
+ },
+ beforeDestroy() {
+ this.removeDropdownListeners();
+ },
+ methods: {
+ addDropdownListeners() {
+ $(this.$refs.dropdown)
+ .on('show.bs.dropdown', () => this.showDropdown())
+ .on('hide.bs.dropdown', () => this.hideDropdown());
+ },
+ removeDropdownListeners() {
+ $(this.$refs.dropdown)
+ .off('show.bs.dropdown')
+ .off('hide.bs.dropdown');
+ },
+ showDropdown() {
+ this.isVisibleDropdown = true;
+ },
+ hideDropdown() {
+ this.isVisibleDropdown = false;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ ref="dropdown"
+ class="btn-group ide-nav-dropdown dropdown"
+ >
+ <nav-dropdown-button />
+ <div
+ class="dropdown-menu dropdown-menu-left p-0"
+ >
+ <nav-form
+ v-if="isVisibleDropdown"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/nav_dropdown_button.vue b/app/assets/javascripts/ide/components/nav_dropdown_button.vue
new file mode 100644
index 00000000000..7f98769d484
--- /dev/null
+++ b/app/assets/javascripts/ide/components/nav_dropdown_button.vue
@@ -0,0 +1,54 @@
+<script>
+import { mapState } from 'vuex';
+import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+const EMPTY_LABEL = '-';
+
+export default {
+ components: {
+ Icon,
+ DropdownButton,
+ },
+ computed: {
+ ...mapState(['currentBranchId', 'currentMergeRequestId']),
+ mergeRequestLabel() {
+ return this.currentMergeRequestId
+ ? `!${this.currentMergeRequestId}`
+ : EMPTY_LABEL;
+ },
+ branchLabel() {
+ return this.currentBranchId || EMPTY_LABEL;
+ },
+ },
+};
+</script>
+
+<template>
+ <dropdown-button>
+ <span
+ class="row"
+ >
+ <span
+ class="col-7 text-truncate"
+ >
+ <icon
+ :size="16"
+ :aria-label="__('Current Branch')"
+ name="branch"
+ />
+ {{ branchLabel }}
+ </span>
+ <span
+ class="col-5 pl-0 text-truncate"
+ >
+ <icon
+ :size="16"
+ :aria-label="__('Merge Request')"
+ name="merge-request"
+ />
+ {{ mergeRequestLabel }}
+ </span>
+ </span>
+ </dropdown-button>
+</template>
diff --git a/app/assets/javascripts/ide/components/nav_form.vue b/app/assets/javascripts/ide/components/nav_form.vue
new file mode 100644
index 00000000000..718b836e11c
--- /dev/null
+++ b/app/assets/javascripts/ide/components/nav_form.vue
@@ -0,0 +1,40 @@
+<script>
+import Tabs from '~/vue_shared/components/tabs/tabs';
+import Tab from '~/vue_shared/components/tabs/tab.vue';
+import BranchesSearchList from './branches/search_list.vue';
+import MergeRequestSearchList from './merge_requests/list.vue';
+
+export default {
+ components: {
+ Tabs,
+ Tab,
+ BranchesSearchList,
+ MergeRequestSearchList,
+ },
+};
+</script>
+
+<template>
+ <div
+ class="ide-nav-form p-0"
+ >
+ <tabs
+ stop-propagation
+ >
+ <tab
+ active
+ >
+ <template slot="title">
+ {{ __('Merge Requests') }}
+ </template>
+ <merge-request-search-list />
+ </tab>
+ <tab>
+ <template slot="title">
+ {{ __('Branches') }}
+ </template>
+ <branches-search-list />
+ </tab>
+ </tabs>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/shared/tokened_input.vue b/app/assets/javascripts/ide/components/shared/tokened_input.vue
new file mode 100644
index 00000000000..a7a12f6785d
--- /dev/null
+++ b/app/assets/javascripts/ide/components/shared/tokened_input.vue
@@ -0,0 +1,121 @@
+<script>
+import { __ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ placeholder: {
+ type: String,
+ required: false,
+ default: __('Search'),
+ },
+ tokens: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ value: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ backspaceCount: 0,
+ };
+ },
+ computed: {
+ placeholderText() {
+ return this.tokens.length
+ ? ''
+ : this.placeholder;
+ },
+ },
+ watch: {
+ tokens() {
+ this.$refs.input.focus();
+ },
+ },
+ methods: {
+ onFocus() {
+ this.$emit('focus');
+ },
+ onBlur() {
+ this.$emit('blur');
+ },
+ onInput(evt) {
+ this.$emit('input', evt.target.value);
+ },
+ onBackspace() {
+ if (!this.value && this.tokens.length) {
+ this.backspaceCount += 1;
+ } else {
+ this.backspaceCount = 0;
+ return;
+ }
+
+ if (this.backspaceCount > 1) {
+ this.removeToken(this.tokens[this.tokens.length - 1]);
+ this.backspaceCount = 0;
+ }
+ },
+ removeToken(token) {
+ this.$emit('removeToken', token);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="filtered-search-wrapper">
+ <div class="filtered-search-box">
+ <div class="tokens-container list-unstyled">
+ <div
+ v-for="token in tokens"
+ :key="token.label"
+ class="filtered-search-token"
+ >
+ <button
+ class="selectable btn-blank"
+ type="button"
+ @click.stop="removeToken(token)"
+ @keyup.delete="removeToken(token)"
+ >
+ <div
+ class="value-container rounded"
+ >
+ <div
+ class="value"
+ >{{ token.label }}</div>
+ <div
+ class="remove-token inverted"
+ >
+ <icon
+ :size="10"
+ name="close"
+ />
+ </div>
+ </div>
+ </button>
+ </div>
+ <div class="input-token">
+ <input
+ ref="input"
+ :placeholder="placeholderText"
+ :value="value"
+ type="search"
+ class="form-control filtered-search"
+ @input="onInput"
+ @focus="onFocus"
+ @blur="onBlur"
+ @keyup.delete="onBackspace"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js
index c6d7d218e81..82f6f981e7a 100644
--- a/app/assets/javascripts/ide/ide_router.js
+++ b/app/assets/javascripts/ide/ide_router.js
@@ -117,7 +117,7 @@ router.beforeEach((to, from, next) => {
mergeRequestId: to.params.mrid,
})
.then(mr => {
- store.dispatch('updateActivityBarView', activityBarViews.review);
+ store.dispatch('setCurrentBranchId', mr.source_branch);
store.dispatch('getBranchData', {
projectId: fullProjectId,
@@ -144,6 +144,10 @@ router.beforeEach((to, from, next) => {
}),
)
.then(mrChanges => {
+ if (mrChanges.changes.length) {
+ store.dispatch('updateActivityBarView', activityBarViews.review);
+ }
+
mrChanges.changes.forEach((change, ind) => {
const changeTreeEntry = store.state.entries[change.new_path];
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 9e3f5da4676..c9795750d65 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -54,9 +54,6 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
commit(types.SET_FILE_ACTIVE, { path, active: true });
dispatch('scrollToTab');
-
- commit(types.SET_CURRENT_PROJECT, file.projectId);
- commit(types.SET_CURRENT_BRANCH, file.branchId);
};
export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js
index f8ce8a67ec0..a601dc8f5a0 100644
--- a/app/assets/javascripts/ide/stores/index.js
+++ b/app/assets/javascripts/ide/stores/index.js
@@ -7,6 +7,7 @@ import mutations from './mutations';
import commitModule from './modules/commit';
import pipelines from './modules/pipelines';
import mergeRequests from './modules/merge_requests';
+import branches from './modules/branches';
Vue.use(Vuex);
@@ -20,6 +21,7 @@ export const createStore = () =>
commit: commitModule,
pipelines,
mergeRequests,
+ branches,
},
});
diff --git a/app/assets/javascripts/ide/stores/modules/branches/actions.js b/app/assets/javascripts/ide/stores/modules/branches/actions.js
new file mode 100644
index 00000000000..74aa98ef9f9
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/branches/actions.js
@@ -0,0 +1,39 @@
+import { __ } from '~/locale';
+import Api from '~/api';
+import * as types from './mutation_types';
+
+export const requestBranches = ({ commit }) => commit(types.REQUEST_BRANCHES);
+export const receiveBranchesError = ({ commit, dispatch }, { search }) => {
+ dispatch(
+ 'setErrorMessage',
+ {
+ text: __('Error loading branches.'),
+ action: payload =>
+ dispatch('fetchBranches', payload).then(() =>
+ dispatch('setErrorMessage', null, { root: true }),
+ ),
+ actionText: __('Please try again'),
+ actionPayload: { search },
+ },
+ { root: true },
+ );
+ commit(types.RECEIVE_BRANCHES_ERROR);
+};
+export const receiveBranchesSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_BRANCHES_SUCCESS, data);
+
+export const fetchBranches = ({ dispatch, rootGetters }, { search = '' }) => {
+ dispatch('requestBranches');
+ dispatch('resetBranches');
+
+ return Api.branches(rootGetters.currentProject.id, search, { sort: 'updated_desc' })
+ .then(({ data }) => dispatch('receiveBranchesSuccess', data))
+ .catch(() => dispatch('receiveBranchesError', { search }));
+};
+
+export const resetBranches = ({ commit }) => commit(types.RESET_BRANCHES);
+
+export const openBranch = ({ rootState, dispatch }, id) =>
+ dispatch('goToRoute', `/project/${rootState.currentProjectId}/edit/${id}`, { root: true });
+
+export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/branches/index.js b/app/assets/javascripts/ide/stores/modules/branches/index.js
new file mode 100644
index 00000000000..04e7e0f08f1
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/branches/index.js
@@ -0,0 +1,10 @@
+import state from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+export default {
+ namespaced: true,
+ state: state(),
+ actions,
+ mutations,
+};
diff --git a/app/assets/javascripts/ide/stores/modules/branches/mutation_types.js b/app/assets/javascripts/ide/stores/modules/branches/mutation_types.js
new file mode 100644
index 00000000000..2272f7b9531
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/branches/mutation_types.js
@@ -0,0 +1,5 @@
+export const REQUEST_BRANCHES = 'REQUEST_BRANCHES';
+export const RECEIVE_BRANCHES_ERROR = 'RECEIVE_BRANCHES_ERROR';
+export const RECEIVE_BRANCHES_SUCCESS = 'RECEIVE_BRANCHES_SUCCESS';
+
+export const RESET_BRANCHES = 'RESET_BRANCHES';
diff --git a/app/assets/javascripts/ide/stores/modules/branches/mutations.js b/app/assets/javascripts/ide/stores/modules/branches/mutations.js
new file mode 100644
index 00000000000..081ec2d4c28
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/branches/mutations.js
@@ -0,0 +1,21 @@
+/* eslint-disable no-param-reassign */
+import * as types from './mutation_types';
+
+export default {
+ [types.REQUEST_BRANCHES](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_BRANCHES_ERROR](state) {
+ state.isLoading = false;
+ },
+ [types.RECEIVE_BRANCHES_SUCCESS](state, data) {
+ state.isLoading = false;
+ state.branches = data.map(branch => ({
+ name: branch.name,
+ committedDate: branch.commit.committed_date,
+ }));
+ },
+ [types.RESET_BRANCHES](state) {
+ state.branches = [];
+ },
+};
diff --git a/app/assets/javascripts/ide/stores/modules/branches/state.js b/app/assets/javascripts/ide/stores/modules/branches/state.js
new file mode 100644
index 00000000000..89bf220c45f
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/branches/state.js
@@ -0,0 +1,4 @@
+export default () => ({
+ isLoading: false,
+ branches: [],
+});
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
index 6ef938b0ae2..baa2497ec5b 100644
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
@@ -1,12 +1,10 @@
import { __ } from '../../../../locale';
import Api from '../../../../api';
-import router from '../../../ide_router';
import { scopes } from './constants';
import * as types from './mutation_types';
-import * as rootTypes from '../../mutation_types';
-export const requestMergeRequests = ({ commit }, type) =>
- commit(types.REQUEST_MERGE_REQUESTS, type);
+export const requestMergeRequests = ({ commit }) =>
+ commit(types.REQUEST_MERGE_REQUESTS);
export const receiveMergeRequestsError = ({ commit, dispatch }, { type, search }) => {
dispatch(
'setErrorMessage',
@@ -21,39 +19,22 @@ export const receiveMergeRequestsError = ({ commit, dispatch }, { type, search }
},
{ root: true },
);
- commit(types.RECEIVE_MERGE_REQUESTS_ERROR, type);
+ commit(types.RECEIVE_MERGE_REQUESTS_ERROR);
};
-export const receiveMergeRequestsSuccess = ({ commit }, { type, data }) =>
- commit(types.RECEIVE_MERGE_REQUESTS_SUCCESS, { type, data });
+export const receiveMergeRequestsSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_MERGE_REQUESTS_SUCCESS, data);
export const fetchMergeRequests = ({ dispatch, state: { state } }, { type, search = '' }) => {
- const scope = scopes[type];
- dispatch('requestMergeRequests', type);
- dispatch('resetMergeRequests', type);
+ dispatch('requestMergeRequests');
+ dispatch('resetMergeRequests');
+
+ const scope = type ? scopes[type] : 'all';
return Api.mergeRequests({ scope, state, search })
- .then(({ data }) => dispatch('receiveMergeRequestsSuccess', { type, data }))
+ .then(({ data }) => dispatch('receiveMergeRequestsSuccess', data))
.catch(() => dispatch('receiveMergeRequestsError', { type, search }));
};
-export const resetMergeRequests = ({ commit }, type) => commit(types.RESET_MERGE_REQUESTS, type);
-
-export const openMergeRequest = ({ commit, dispatch }, { projectPath, id }) => {
- commit(rootTypes.CLEAR_PROJECTS, null, { root: true });
- commit(rootTypes.SET_CURRENT_MERGE_REQUEST, `${id}`, { root: true });
- commit(rootTypes.RESET_OPEN_FILES, null, { root: true });
- dispatch('setCurrentBranchId', '', { root: true });
- dispatch('pipelines/stopPipelinePolling', null, { root: true })
- .then(() => {
- dispatch('pipelines/resetLatestPipeline', null, { root: true });
- dispatch('pipelines/clearEtagPoll', null, { root: true });
- })
- .catch(e => {
- throw e;
- });
- dispatch('setRightPane', null, { root: true });
-
- router.push(`/project/${projectPath}/merge_requests/${id}`);
-};
+export const resetMergeRequests = ({ commit }) => commit(types.RESET_MERGE_REQUESTS);
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/getters.js b/app/assets/javascripts/ide/stores/modules/merge_requests/getters.js
deleted file mode 100644
index 8e2b234be8d..00000000000
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/getters.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export const getData = state => type => state[type];
-
-export const assignedData = state => state.assigned;
-export const createdData = state => state.created;
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/index.js b/app/assets/javascripts/ide/stores/modules/merge_requests/index.js
index 2e6dfb420f4..04e7e0f08f1 100644
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/index.js
+++ b/app/assets/javascripts/ide/stores/modules/merge_requests/index.js
@@ -1,6 +1,5 @@
import state from './state';
import * as actions from './actions';
-import * as getters from './getters';
import mutations from './mutations';
export default {
@@ -8,5 +7,4 @@ export default {
state: state(),
actions,
mutations,
- getters,
};
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js b/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js
index 971da0806bd..98102a68e08 100644
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/merge_requests/mutations.js
@@ -2,15 +2,15 @@
import * as types from './mutation_types';
export default {
- [types.REQUEST_MERGE_REQUESTS](state, type) {
- state[type].isLoading = true;
+ [types.REQUEST_MERGE_REQUESTS](state) {
+ state.isLoading = true;
},
- [types.RECEIVE_MERGE_REQUESTS_ERROR](state, type) {
- state[type].isLoading = false;
+ [types.RECEIVE_MERGE_REQUESTS_ERROR](state) {
+ state.isLoading = false;
},
- [types.RECEIVE_MERGE_REQUESTS_SUCCESS](state, { type, data }) {
- state[type].isLoading = false;
- state[type].mergeRequests = data.map(mergeRequest => ({
+ [types.RECEIVE_MERGE_REQUESTS_SUCCESS](state, data) {
+ state.isLoading = false;
+ state.mergeRequests = data.map(mergeRequest => ({
id: mergeRequest.id,
iid: mergeRequest.iid,
title: mergeRequest.title,
@@ -20,7 +20,7 @@ export default {
.replace(`/merge_requests/${mergeRequest.iid}`, ''),
}));
},
- [types.RESET_MERGE_REQUESTS](state, type) {
- state[type].mergeRequests = [];
+ [types.RESET_MERGE_REQUESTS](state) {
+ state.mergeRequests = [];
},
};
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/state.js b/app/assets/javascripts/ide/stores/modules/merge_requests/state.js
index 57eb6b04283..4748ccfa2e6 100644
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/state.js
+++ b/app/assets/javascripts/ide/stores/modules/merge_requests/state.js
@@ -1,13 +1,7 @@
import { states } from './constants';
export default () => ({
- created: {
- isLoading: false,
- mergeRequests: [],
- },
- assigned: {
- isLoading: false,
- mergeRequests: [],
- },
+ isLoading: false,
+ mergeRequests: [],
state: states.opened,
});
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 37a45d1d1a2..cb851ff6745 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -39,7 +39,7 @@ export default class LabelsSelect {
showNo = $dropdown.data('showNo');
showAny = $dropdown.data('showAny');
showMenuAbove = $dropdown.data('showMenuAbove');
- defaultLabel = $dropdown.data('defaultLabel');
+ defaultLabel = $dropdown.data('defaultLabel') || 'Label';
abilityName = $dropdown.data('abilityName');
$selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block');
@@ -244,21 +244,21 @@ export default class LabelsSelect {
var $dropdownInputField = $dropdownParent.find('.dropdown-input-field');
var isSelected = el !== null ? el.hasClass('is-active') : false;
- var { title } = selected;
+ var title = selected ? selected.title : null;
var selectedLabels = this.selected;
if ($dropdownInputField.length && $dropdownInputField.val().length) {
$dropdownParent.find('.dropdown-input-clear').trigger('click');
}
- if (selected.id === 0) {
+ if (selected && selected.id === 0) {
this.selected = [];
return 'No Label';
}
else if (isSelected) {
this.selected.push(title);
}
- else {
+ else if (!isSelected && title) {
var index = this.selected.indexOf(title);
this.selected.splice(index, 1);
}
@@ -409,6 +409,14 @@ export default class LabelsSelect {
}
}
},
+ opened: function(e) {
+ if ($dropdown.hasClass('js-issue-board-sidebar')) {
+ const previousSelection = $dropdown.attr('data-selected');
+ this.selected = previousSelection ? previousSelection.split(',') : [];
+ $dropdown.data('glDropdown').updateLabel();
+ }
+ },
+ preserveContext: true,
});
// Set dropdown data
diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js
index 9bba341e3a3..bd2212edec7 100644
--- a/app/assets/javascripts/lazy_loader.js
+++ b/app/assets/javascripts/lazy_loader.js
@@ -19,11 +19,17 @@ export default class LazyLoader {
scrollContainer.addEventListener('load', () => this.loadCheck());
}
searchLazyImages() {
- this.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
+ const that = this;
+ requestIdleCallback(
+ () => {
+ that.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
- if (this.lazyImages.length) {
- this.checkElementsInView();
- }
+ if (that.lazyImages.length) {
+ that.checkElementsInView();
+ }
+ },
+ { timeout: 500 },
+ );
}
startContentObserver() {
const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
@@ -56,7 +62,9 @@ export default class LazyLoader {
const imgBound = imgTop + imgBoundRect.height;
if (scrollTop < imgBound && visHeight > imgTop) {
- LazyLoader.loadImage(selectedImage);
+ requestAnimationFrame(() => {
+ LazyLoader.loadImage(selectedImage);
+ });
return false;
}
diff --git a/app/assets/javascripts/pages/profiles/show/emoji_menu.js b/app/assets/javascripts/pages/profiles/show/emoji_menu.js
new file mode 100644
index 00000000000..094837b40e0
--- /dev/null
+++ b/app/assets/javascripts/pages/profiles/show/emoji_menu.js
@@ -0,0 +1,18 @@
+import { AwardsHandler } from '~/awards_handler';
+
+class EmojiMenu extends AwardsHandler {
+ constructor(emoji, toggleButtonSelector, menuClass, selectEmojiCallback) {
+ super(emoji);
+
+ this.selectEmojiCallback = selectEmojiCallback;
+ this.toggleButtonSelector = toggleButtonSelector;
+ this.menuClass = menuClass;
+ }
+
+ postEmoji($emojiButton, awardUrl, selectedEmoji, callback) {
+ this.selectEmojiCallback(selectedEmoji, this.emoji.glEmojiTag(selectedEmoji));
+ callback();
+ }
+}
+
+export default EmojiMenu;
diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js
new file mode 100644
index 00000000000..949219a0837
--- /dev/null
+++ b/app/assets/javascripts/pages/profiles/show/index.js
@@ -0,0 +1,49 @@
+import $ from 'jquery';
+import createFlash from '~/flash';
+import GfmAutoComplete from '~/gfm_auto_complete';
+import EmojiMenu from './emoji_menu';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const toggleEmojiMenuButtonSelector = '.js-toggle-emoji-menu';
+ const toggleEmojiMenuButton = document.querySelector(toggleEmojiMenuButtonSelector);
+ const statusEmojiField = document.getElementById('js-status-emoji-field');
+ const statusMessageField = document.getElementById('js-status-message-field');
+ const findNoEmojiPlaceholder = () => document.getElementById('js-no-emoji-placeholder');
+
+ const removeStatusEmoji = () => {
+ const statusEmoji = toggleEmojiMenuButton.querySelector('gl-emoji');
+ if (statusEmoji) {
+ statusEmoji.remove();
+ }
+ };
+
+ const selectEmojiCallback = (emoji, emojiTag) => {
+ statusEmojiField.value = emoji;
+ findNoEmojiPlaceholder().classList.add('hidden');
+ removeStatusEmoji();
+ toggleEmojiMenuButton.innerHTML += emojiTag;
+ };
+
+ const clearEmojiButton = document.getElementById('js-clear-user-status-button');
+ clearEmojiButton.addEventListener('click', () => {
+ statusEmojiField.value = '';
+ statusMessageField.value = '';
+ removeStatusEmoji();
+ findNoEmojiPlaceholder().classList.remove('hidden');
+ });
+
+ const emojiAutocomplete = new GfmAutoComplete();
+ emojiAutocomplete.setup($(statusMessageField), { emojis: true });
+
+ import(/* webpackChunkName: 'emoji' */ '~/emoji')
+ .then(Emoji => {
+ const emojiMenu = new EmojiMenu(
+ Emoji,
+ toggleEmojiMenuButtonSelector,
+ 'js-status-emoji-menu',
+ selectEmojiCallback,
+ );
+ emojiMenu.bindEvents();
+ })
+ .catch(() => createFlash('Failed to load emoji list!'));
+});
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
index 133bdbb54f7..8163947cd0c 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
@@ -42,6 +42,9 @@ export default {
},
methods: {
onImgLoad() {
+ requestIdleCallback(this.calculateImgSize, { timeout: 1000 });
+ },
+ calculateImgSize() {
const { contentImg } = this.$refs;
if (contentImg) {
diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
index 3cba0c5e633..af5ebcdc40a 100644
--- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
+++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue
@@ -38,9 +38,17 @@ export default {
v-show="isLoading"
:inline="true"
/>
- <span class="dropdown-toggle-text">
- {{ toggleText }}
- </span>
+ <template>
+ <slot
+ v-if="$slots.default"
+ ></slot>
+ <span
+ v-else
+ class="dropdown-toggle-text"
+ >
+ {{ toggleText }}
+ </span>
+ </template>
<span
v-show="!isLoading"
class="dropdown-toggle-icon"
diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue
index 3cf90b45a97..5e0e7315e99 100644
--- a/app/assets/javascripts/vue_shared/components/icon.vue
+++ b/app/assets/javascripts/vue_shared/components/icon.vue
@@ -1,7 +1,7 @@
<script>
// only allow classes in images.scss e.g. s12
-const validSizes = [8, 12, 16, 18, 24, 32, 48, 72];
+const validSizes = [8, 10, 12, 16, 18, 24, 32, 48, 72];
let iconValidator = () => true;
/*
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index d28ad407734..c20738a20c3 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -339,3 +339,13 @@ input[type=color].form-control {
vertical-align: unset;
}
}
+
+// Bootstrap 3 compatibility because bootstrap_form Gem is not updated yet
+.input-group-btn:first-child {
+ @extend .input-group-prepend;
+}
+
+// Bootstrap 3 compatibility because bootstrap_form Gem is not updated yet
+.input-group-btn:last-child {
+ @extend .input-group-append;
+}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 369556dc24e..4c7c399a3ca 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -103,6 +103,7 @@
display: flex;
a {
+ width: 100%;
display: flex;
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index c9865610b78..af17210f341 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -454,6 +454,7 @@ img.emoji {
.prepend-left-10 { margin-left: 10px; }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }
+.append-right-4 { margin-right: 4px; }
.append-right-5 { margin-right: 5px; }
.append-right-8 { margin-right: 8px; }
.append-right-10 { margin-right: 10px; }
@@ -470,3 +471,5 @@ img.emoji {
.center { text-align: center; }
.vertical-align-middle { vertical-align: middle; }
.flex-align-self-center { align-self: center; }
+.flex-grow { flex-grow: 1; }
+.flex-no-shrink { flex-shrink: 0; }
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index ec4a0f378d0..eebce8b9011 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -571,7 +571,8 @@
margin-bottom: 10px;
padding: 0 10px;
- .fa {
+ .fa,
+ .input-icon {
position: absolute;
top: 10px;
right: 20px;
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index ab3cceceae9..f878ec1ca91 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -39,7 +39,7 @@
svg {
fill: currentColor;
- $svg-sizes: 8 12 16 18 24 32 48 72;
+ $svg-sizes: 8 10 12 16 18 24 32 48 72;
@each $svg-size in $svg-sizes {
&.s#{$svg-size} {
@include svg-size(#{$svg-size}px);
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 37ad6a717d9..2b8163b8c68 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -1,6 +1,7 @@
@import 'framework/variables';
@import 'framework/mixins';
+$search-list-icon-width: 18px;
$ide-activity-bar-width: 60px;
$ide-context-header-padding: 10px;
$ide-project-avatar-end: $ide-context-header-padding + 48px;
@@ -49,7 +50,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
display: flex;
flex-direction: column;
flex: 1;
- overflow: hidden;
+ min-height: 0;
.file {
height: 32px;
@@ -532,7 +533,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
position: relative;
display: flex;
flex-direction: column;
- height: 100%;
+ min-height: 100%;
min-width: 0;
width: 100%;
}
@@ -541,11 +542,11 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
display: flex;
flex: 1;
flex-direction: column;
- overflow: hidden;
background-color: $white-light;
border-left: 1px solid $white-dark;
border-top: 1px solid $white-dark;
border-top-left-radius: $border-radius-small;
+ min-height: 0;
}
}
@@ -1057,6 +1058,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
flex: 0 0 auto;
display: flex;
align-items: center;
+ flex-wrap: wrap;
padding: 12px 0;
margin-left: $ide-tree-padding;
margin-right: $ide-tree-padding;
@@ -1066,6 +1068,32 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
margin-left: auto;
}
+ .ide-nav-dropdown {
+ width: 100%;
+ margin-bottom: 12px;
+
+ .dropdown-menu {
+ width: 385px;
+ max-height: initial;
+ }
+
+ .dropdown-menu-toggle {
+ svg {
+ vertical-align: middle;
+ }
+
+ &:hover {
+ background-color: $white-normal;
+ }
+ }
+
+ &.show {
+ .dropdown-menu-toggle {
+ background-color: $white-dark;
+ }
+ }
+ }
+
button {
color: $gl-text-color;
}
@@ -1181,7 +1209,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.ide-context-body {
- overflow: hidden;
+ min-height: 0;
}
.ide-sidebar-project-title {
@@ -1331,7 +1359,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
min-height: 60px;
}
-.ide-merge-requests-dropdown {
+.ide-nav-form {
.nav-links li {
width: 50%;
padding-left: 0;
@@ -1350,22 +1378,36 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
padding-left: $gl-padding;
padding-right: $gl-padding;
- .fa {
- right: 26px;
+ .input-icon {
+ right: auto;
+ left: 10px;
+ top: 50%;
+ transform: translateY(-50%);
}
}
+ .dropdown-input-field {
+ padding-left: $search-list-icon-width + $gl-padding;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ }
+
+ .tokens-container {
+ padding-left: $search-list-icon-width + $gl-padding;
+ overflow-x: hidden;
+ }
+
.btn-link {
padding-top: $gl-padding;
padding-bottom: $gl-padding;
}
}
-.ide-merge-request-current-icon {
- min-width: 18px;
+.ide-search-list-current-icon {
+ min-width: $search-list-icon-width;
}
-.ide-merge-requests-empty {
+.ide-search-list-empty {
height: 230px;
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 05bf5596fb3..1587aebfe1d 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -290,9 +290,8 @@
}
.folder-toggle-wrap {
- float: left;
- line-height: $list-text-height;
font-size: 0;
+ flex-shrink: 0;
span {
font-size: $gl-font-size;
@@ -308,7 +307,7 @@
width: 15px;
svg {
- margin-bottom: 2px;
+ margin-bottom: 1px;
}
}
@@ -391,9 +390,17 @@
cursor: pointer;
}
- .avatar-container > a {
- width: 100%;
- text-decoration: none;
+ .group-text {
+ min-width: 0; // allows for truncated text within flex children
+ }
+
+ .avatar-container {
+ flex-shrink: 0;
+
+ > a {
+ width: 100%;
+ text-decoration: none;
+ }
}
&.has-more-items {
@@ -401,9 +408,18 @@
padding: 20px 10px;
}
+ .description {
+ p {
+ @include str-truncated;
+
+ max-width: none;
+ }
+ }
+
.stats {
position: relative;
- line-height: 46px;
+ line-height: normal;
+ flex-shrink: 0;
> span {
display: inline-flex;
@@ -422,14 +438,20 @@
}
.controls {
- margin-left: 5px;
+ flex-shrink: 0;
> .btn {
- margin-right: $btn-margin-5;
+ margin: 0 0 0 $btn-margin-5;
}
}
}
+ @include media-breakpoint-down(xs) {
+ .group-stats {
+ display: none;
+ }
+ }
+
.project-row-contents .stats {
line-height: inherit;
@@ -451,18 +473,6 @@
}
}
-ul.group-list-tree {
- li.group-row {
- > .group-row-contents .title {
- line-height: $list-text-height;
- }
-
- &.has-description > .group-row-contents .title {
- line-height: inherit;
- }
- }
-}
-
.js-groups-list-holder {
.groups-list-loading {
font-size: 34px;
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 2b40404971c..b25dc4f419a 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -253,7 +253,7 @@
text-align: right;
padding: 0;
position: relative;
- top: -3px;
+ margin: 0;
}
.label-badge {
@@ -274,6 +274,7 @@
.label-links {
list-style: none;
+ margin: 0;
padding: 0;
white-space: nowrap;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 7fc2936c5e6..c369d89d63c 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -546,6 +546,7 @@ ul.notes {
svg {
@include btn-svg;
+ margin: 0;
}
.award-control-icon-positive,
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 5d0d59e12f2..b45e305897c 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -418,3 +418,23 @@ table.u2f-registrations {
}
}
}
+
+.edit-user {
+ .clear-user-status {
+ svg {
+ fill: $gl-text-color-secondary;
+ }
+ }
+
+ .emoji-menu-toggle-button {
+ @extend .note-action-button;
+
+ .no-emoji-placeholder {
+ position: relative;
+
+ svg {
+ fill: $gl-text-color-secondary;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 944421604fe..6eaa0523387 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -823,10 +823,6 @@ pre.light-well {
.avatar-container {
align-self: flex-start;
-
- > a {
- width: 100%;
- }
}
.project-details {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 7228a2f1715..05ed3669a41 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -20,13 +20,13 @@ class ApplicationController < ActionController::Base
before_action :ldap_security_check
before_action :sentry_context
before_action :default_headers
- before_action :add_gon_variables, unless: :peek_request?
+ before_action :add_gon_variables, unless: [:peek_request?, :json_request?]
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
around_action :set_locale
- after_action :set_page_title_header, if: -> { request.format == :json }
+ after_action :set_page_title_header, if: :json_request?
protect_from_forgery with: :exception, prepend: true
@@ -424,6 +424,10 @@ class ApplicationController < ActionController::Base
request.path.start_with?('/-/peek')
end
+ def json_request?
+ request.format.json?
+ end
+
def should_enforce_terms?
return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
diff --git a/app/controllers/concerns/repository_settings_redirect.rb b/app/controllers/concerns/repository_settings_redirect.rb
index 0576f0e6e70..f3db3cd563b 100644
--- a/app/controllers/concerns/repository_settings_redirect.rb
+++ b/app/controllers/concerns/repository_settings_redirect.rb
@@ -1,7 +1,7 @@
module RepositorySettingsRedirect
extend ActiveSupport::Concern
- def redirect_to_repository_settings(project)
- redirect_to project_settings_repository_path(project)
+ def redirect_to_repository_settings(project, anchor: nil)
+ redirect_to project_settings_repository_path(project, anchor: anchor)
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 79fa5818359..83169636ccf 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -85,7 +85,7 @@ class GroupsController < Groups::ApplicationController
def update
if Groups::UpdateService.new(@group, current_user, group_params).execute
- redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
+ redirect_to edit_group_path(@group, anchor: params[:update_section]), notice: "Group '#{@group.name}' was successfully updated."
else
@group.restore_path!
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index a13d552dbd8..53fdc5843b5 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -21,6 +21,6 @@ class Projects::AvatarsController < Projects::ApplicationController
@project.save
- redirect_to edit_project_path(@project), status: :found
+ redirect_to edit_project_path(@project, anchor: 'js-general-project-settings'), status: :found
end
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 06739d8fd4a..28fea322334 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -10,7 +10,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def index
respond_to do |format|
- format.html { redirect_to_repository_settings(@project) }
+ format.html { redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings') }
format.json do
render json: Projects::Settings::DeployKeysPresenter.new(@project, current_user: current_user).as_json
end
@@ -18,7 +18,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def new
- redirect_to_repository_settings(@project)
+ redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings')
end
def create
@@ -28,7 +28,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
flash[:alert] = @key.errors.full_messages.join(', ').html_safe
end
- redirect_to_repository_settings(@project)
+ redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings')
end
def edit
@@ -37,7 +37,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def update
if deploy_key.update(update_params)
flash[:notice] = 'Deploy key was successfully updated.'
- redirect_to_repository_settings(@project)
+ redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings')
else
render 'edit'
end
@@ -47,7 +47,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
Projects::EnableDeployKeyService.new(@project, current_user, params).execute
respond_to do |format|
- format.html { redirect_to_repository_settings(@project) }
+ format.html { redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings') }
format.json { head :ok }
end
end
@@ -59,7 +59,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
deploy_key_project.destroy!
respond_to do |format|
- format.html { redirect_to_repository_settings(@project) }
+ format.html { redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings') }
format.json { head :ok }
end
end
diff --git a/app/controllers/projects/deploy_tokens_controller.rb b/app/controllers/projects/deploy_tokens_controller.rb
index 2f91b8f36de..83abda64fe0 100644
--- a/app/controllers/projects/deploy_tokens_controller.rb
+++ b/app/controllers/projects/deploy_tokens_controller.rb
@@ -5,6 +5,6 @@ class Projects::DeployTokensController < Projects::ApplicationController
@token = @project.deploy_tokens.find(params[:id])
@token.revoke!
- redirect_to project_settings_repository_path(project)
+ redirect_to project_settings_repository_path(project, anchor: 'js-deploy-tokens')
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index eaf4434f913..1b069fe507b 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -102,10 +102,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def test_reports
result = @merge_request.compare_test_reports
- Gitlab::PollingInterval.set_header(response, interval: 10_000)
-
case result[:status]
when :parsing
+ Gitlab::PollingInterval.set_header(response, interval: 3000)
+
render json: '', status: :no_content
when :parsed
render json: result[:data].to_json, status: :ok
diff --git a/app/controllers/projects/mirrors_controller.rb b/app/controllers/projects/mirrors_controller.rb
index da209c3ba61..3739608e4c0 100644
--- a/app/controllers/projects/mirrors_controller.rb
+++ b/app/controllers/projects/mirrors_controller.rb
@@ -9,7 +9,7 @@ class Projects::MirrorsController < Projects::ApplicationController
layout "project_settings"
def show
- redirect_to_repository_settings(project)
+ redirect_to_repository_settings(project, anchor: 'js-push-remote-settings')
end
def update
@@ -22,7 +22,7 @@ class Projects::MirrorsController < Projects::ApplicationController
end
respond_to do |format|
- format.html { redirect_to_repository_settings(project) }
+ format.html { redirect_to_repository_settings(project, anchor: 'js-push-remote-settings') }
format.json do
if project.errors.present?
render json: project.errors, status: :unprocessable_entity
@@ -39,7 +39,7 @@ class Projects::MirrorsController < Projects::ApplicationController
flash[:notice] = "The remote repository is being updated..."
end
- redirect_to_repository_settings(project)
+ redirect_to_repository_settings(project, anchor: 'js-push-remote-settings')
end
private
diff --git a/app/controllers/projects/protected_refs_controller.rb b/app/controllers/projects/protected_refs_controller.rb
index 9e757a8d25f..cc62ce2f11b 100644
--- a/app/controllers/projects/protected_refs_controller.rb
+++ b/app/controllers/projects/protected_refs_controller.rb
@@ -19,7 +19,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
flash[:alert] = protected_ref.errors.full_messages.join(', ').html_safe
end
- redirect_to_repository_settings(@project)
+ redirect_to_repository_settings(@project, anchor: params[:update_section])
end
def show
@@ -40,7 +40,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
destroy_service_class.new(@project, current_user).execute(@protected_ref)
respond_to do |format|
- format.html { redirect_to_repository_settings(@project) }
+ format.html { redirect_to_repository_settings(@project, anchor: params[:update_section]) }
format.js { head :ok }
end
end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index cc7cce887bf..d118cec977c 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
- redirect_to project_settings_ci_cd_path(@project)
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
end
def edit
@@ -50,13 +50,13 @@ class Projects::RunnersController < Projects::ApplicationController
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
- redirect_to project_settings_ci_cd_path(@project)
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
end
def toggle_group_runners
project.toggle_ci_cd_settings!(:group_runners_enabled)
- redirect_to project_settings_ci_cd_path(@project)
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-runners-settings')
end
protected
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index 6f3de43f85a..cb12b707087 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController
layout 'project_settings'
def index
- redirect_to project_settings_ci_cd_path(@project)
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end
def create
@@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not create a new trigger.'
end
- redirect_to project_settings_ci_cd_path(@project)
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end
def take_ownership
@@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not take ownership of trigger.'
end
- redirect_to project_settings_ci_cd_path(@project)
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end
def edit
@@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController
def update
if trigger.update(trigger_params)
- redirect_to project_settings_ci_cd_path(@project), notice: 'Trigger was successfully updated.'
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: 'Trigger was successfully updated.'
else
render action: "edit"
end
@@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = "Could not remove the trigger."
end
- redirect_to project_settings_ci_cd_path(@project), status: :found
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), status: :found
end
private
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 4d7e33d7b11..e9ae8c13142 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -61,7 +61,7 @@ class ProjectsController < Projects::ApplicationController
flash[:notice] = _("Project '%{project_name}' was successfully updated.") % { project_name: @project.name }
format.html do
- redirect_to(edit_project_path(@project))
+ redirect_to(edit_project_path(@project, anchor: 'js-general-project-settings'))
end
else
flash.now[:alert] = result[:message]
@@ -174,7 +174,7 @@ class ProjectsController < Projects::ApplicationController
)
rescue ::Projects::HousekeepingService::LeaseTaken => ex
redirect_to(
- edit_project_path(@project),
+ edit_project_path(@project, anchor: 'js-project-advanced-settings'),
alert: ex.to_s
)
end
@@ -183,7 +183,7 @@ class ProjectsController < Projects::ApplicationController
@project.add_export_job(current_user: current_user)
redirect_to(
- edit_project_path(@project),
+ edit_project_path(@project, anchor: 'js-export-project'),
notice: _("Project export started. A download link will be sent by email.")
)
end
@@ -195,7 +195,7 @@ class ProjectsController < Projects::ApplicationController
send_file export_project_path, disposition: 'attachment'
else
redirect_to(
- edit_project_path(@project),
+ edit_project_path(@project, anchor: 'js-export-project'),
alert: _("Project export link has expired. Please generate a new export from your project settings.")
)
end
@@ -208,7 +208,7 @@ class ProjectsController < Projects::ApplicationController
flash[:alert] = _("Project export could not be deleted.")
end
- redirect_to(edit_project_path(@project))
+ redirect_to(edit_project_path(@project, anchor: 'js-export-project'))
end
def generate_new_export
@@ -216,7 +216,7 @@ class ProjectsController < Projects::ApplicationController
export
else
redirect_to(
- edit_project_path(@project),
+ edit_project_path(@project, anchor: 'js-export-project'),
alert: _("Project export could not be deleted.")
)
end
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index d48dae8f06d..494f785e305 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -1,28 +1,10 @@
module AvatarsHelper
def project_icon(project_id, options = {})
- project =
- if project_id.respond_to?(:avatar_url)
- project_id
- else
- Project.find_by_full_path(project_id)
- end
-
- if project.avatar_url
- image_tag project.avatar_url, options
- else # generated icon
- project_identicon(project, options)
- end
+ source_icon(Project, project_id, options)
end
- def project_identicon(project, options = {})
- bg_key = (project.id % 7) + 1
- options[:class] ||= ''
- options[:class] << ' identicon'
- options[:class] << " bg#{bg_key}"
-
- content_tag(:div, class: options[:class]) do
- project.name[0, 1].upcase
- end
+ def group_icon(group_id, options = {})
+ source_icon(Group, group_id, options)
end
# Takes both user and email and returns the avatar_icon by
@@ -123,4 +105,32 @@ module AvatarsHelper
mail_to(options[:user_email], avatar)
end
end
+
+ private
+
+ def source_icon(klass, source_id, options = {})
+ source =
+ if source_id.respond_to?(:avatar_url)
+ source_id
+ else
+ klass.find_by_full_path(source_id)
+ end
+
+ if source.avatar_url
+ image_tag source.avatar_url, options
+ else
+ source_identicon(source, options)
+ end
+ end
+
+ def source_identicon(source, options = {})
+ bg_key = (source.id % 7) + 1
+ options[:class] ||= ''
+ options[:class] << ' identicon'
+ options[:class] << " bg#{bg_key}"
+
+ content_tag(:div, class: options[:class].strip) do
+ source.name[0, 1].upcase
+ end
+ end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 3c5c8bbd71b..5b51d2f2425 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -33,11 +33,6 @@ module GroupsHelper
.count
end
- def group_icon(group, options = {})
- img_path = group_icon_url(group, options)
- image_tag img_path, options
- end
-
def group_icon_url(group, options = {})
if group.is_a?(String)
group = Group.find_by_full_path(group)
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 66aaf055cf2..30585cb403d 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -3,11 +3,10 @@ module NamespacesHelper
params.dig(:project, :namespace_id) || params[:namespace_id]
end
- def namespaces_options(selected = :current_user, display_path: false, extra_group: nil, groups_only: false)
- groups = current_user.manageable_groups
- .joins(:route)
- .includes(:route)
- .order('routes.path')
+ def namespaces_options(selected = :current_user, display_path: false, groups: nil, extra_group: nil, groups_only: false)
+ groups ||= current_user.manageable_groups
+ .eager_load(:route)
+ .order('routes.path')
users = [current_user.namespace]
selected_id = selected
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
index a6a57db3002..e7aa92e6e5c 100644
--- a/app/helpers/profiles_helper.rb
+++ b/app/helpers/profiles_helper.rb
@@ -9,8 +9,4 @@ module ProfilesHelper
end
end
end
-
- def show_user_status_field?
- Feature.enabled?(:user_status_form) || cookies[:feature_user_status_form] == 'true'
- end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 75dfa00d12e..e4aed76f611 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -606,12 +606,12 @@ module Ci
end
def has_test_reports?
- complete? && builds.with_test_reports.any?
+ complete? && builds.latest.with_test_reports.any?
end
def test_reports
Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
- builds.with_test_reports.each do |build|
+ builds.latest.with_test_reports.each do |build|
build.collect_test_reports!(test_reports)
end
end
diff --git a/app/models/concerns/access_requestable.rb b/app/models/concerns/access_requestable.rb
index d502e7e54c6..31ee3a4da73 100644
--- a/app/models/concerns/access_requestable.rb
+++ b/app/models/concerns/access_requestable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == AccessRequestable concern
#
# Contains functionality related to objects that can receive request for access.
diff --git a/app/models/concerns/artifact_migratable.rb b/app/models/concerns/artifact_migratable.rb
index ff52ca64459..cbd63ba8876 100644
--- a/app/models/concerns/artifact_migratable.rb
+++ b/app/models/concerns/artifact_migratable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Adapter class to unify the interface between mounted uploaders and the
# Ci::Artifact model
# Meant to be prepended so the interface can stay the same
diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb
index 5e39676b24b..7f6d48d972c 100644
--- a/app/models/concerns/atomic_internal_id.rb
+++ b/app/models/concerns/atomic_internal_id.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Include atomic internal id generation scheme for a model
#
# This allows us to atomically generate internal ids that are
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index a6d604a580d..c0233661a9b 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Avatarable
extend ActiveSupport::Concern
@@ -57,7 +59,8 @@ module Avatarable
only_path = false
end
- url_base = ""
+ url_base = []
+
if use_asset_host
url_base << asset_host unless only_path
else
@@ -65,7 +68,7 @@ module Avatarable
url_base << gitlab_config.relative_url_root
end
- url_base + avatar.local_url + query_params
+ url_base.join + avatar.local_url + query_params
end
# Path that is persisted in the tracking Upload model. Used to fetch the
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index fce37e7f78e..dd07f389fa5 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Awardable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/batch_destroy_dependent_associations.rb b/app/models/concerns/batch_destroy_dependent_associations.rb
index 353ee2e73d0..45fbc88fbba 100644
--- a/app/models/concerns/batch_destroy_dependent_associations.rb
+++ b/app/models/concerns/batch_destroy_dependent_associations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Provides a way to work around Rails issue where dependent objects are all
# loaded into memory before destroyed: https://github.com/rails/rails/issues/22510.
#
diff --git a/app/models/concerns/blob_like.rb b/app/models/concerns/blob_like.rb
index adb81561000..e96fefe81c4 100644
--- a/app/models/concerns/blob_like.rb
+++ b/app/models/concerns/blob_like.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BlobLike
extend ActiveSupport::Concern
include Linguist::BlobHelper
diff --git a/app/models/concerns/blocks_json_serialization.rb b/app/models/concerns/blocks_json_serialization.rb
index 8019e6adc1c..d346da1ba4b 100644
--- a/app/models/concerns/blocks_json_serialization.rb
+++ b/app/models/concerns/blocks_json_serialization.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Overrides `as_json` and `to_json` to raise an exception when called in order
# to prevent accidentally exposing attributes
#
diff --git a/app/models/concerns/bulk_member_access_load.rb b/app/models/concerns/bulk_member_access_load.rb
index 984c4f53bf7..c4346d5dd17 100644
--- a/app/models/concerns/bulk_member_access_load.rb
+++ b/app/models/concerns/bulk_member_access_load.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Returns and caches in thread max member access for a resource
#
module BulkMemberAccessLoad
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index b05bf909058..6e2adc76ec6 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This module takes care of updating cache columns for Markdown-containing
# fields. Use like this in the body of your class:
#
diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb
index 606549b947f..62b78c3611c 100644
--- a/app/models/concerns/cacheable_attributes.rb
+++ b/app/models/concerns/cacheable_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CacheableAttributes
extend ActiveSupport::Concern
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
index 034e9f40ff0..0ba542b75ab 100644
--- a/app/models/concerns/case_sensitivity.rb
+++ b/app/models/concerns/case_sensitivity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Concern for querying columns with specific case sensitivity handling.
module CaseSensitivity
extend ActiveSupport::Concern
diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb
index 593a9b3d71d..edf6ac96730 100644
--- a/app/models/concerns/chronic_duration_attribute.rb
+++ b/app/models/concerns/chronic_duration_attribute.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChronicDurationAttribute
extend ActiveSupport::Concern
diff --git a/app/models/concerns/created_at_filterable.rb b/app/models/concerns/created_at_filterable.rb
index e8a3e41203d..a1f46478b6f 100644
--- a/app/models/concerns/created_at_filterable.rb
+++ b/app/models/concerns/created_at_filterable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CreatedAtFilterable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 52851b3d0b2..91052013592 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DeploymentPlatform
# EE would override this and utilize environment argument
# rubocop:disable Gitlab/ModuleWithInstanceVariables
diff --git a/app/models/concerns/diff_file.rb b/app/models/concerns/diff_file.rb
index 72332072012..47ea14163dc 100644
--- a/app/models/concerns/diff_file.rb
+++ b/app/models/concerns/diff_file.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffFile
extend ActiveSupport::Concern
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index 8b3c55387b3..c180d7b7c9a 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Contains functionality shared between `DiffDiscussion` and `LegacyDiffDiscussion`.
module DiscussionOnDiff
extend ActiveSupport::Concern
diff --git a/app/models/concerns/each_batch.rb b/app/models/concerns/each_batch.rb
index 6ddbb8da1a9..a9e14cb55eb 100644
--- a/app/models/concerns/each_batch.rb
+++ b/app/models/concerns/each_batch.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EachBatch
extend ActiveSupport::Concern
diff --git a/app/models/concerns/editable.rb b/app/models/concerns/editable.rb
index c0a3099f676..2e49e720ac9 100644
--- a/app/models/concerns/editable.rb
+++ b/app/models/concerns/editable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Editable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/enum_with_nil.rb b/app/models/concerns/enum_with_nil.rb
index 6b37903da20..23acfe9a55f 100644
--- a/app/models/concerns/enum_with_nil.rb
+++ b/app/models/concerns/enum_with_nil.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EnumWithNil
extend ActiveSupport::Concern
diff --git a/app/models/concerns/expirable.rb b/app/models/concerns/expirable.rb
index b66ba08dc59..1f274487935 100644
--- a/app/models/concerns/expirable.rb
+++ b/app/models/concerns/expirable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Expirable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/fast_destroy_all.rb b/app/models/concerns/fast_destroy_all.rb
index 7ea042c6742..65ed46ea202 100644
--- a/app/models/concerns/fast_destroy_all.rb
+++ b/app/models/concerns/fast_destroy_all.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
##
# This module is for replacing `dependent: :destroy` and `before_destroy` hooks.
#
diff --git a/app/models/concerns/faster_cache_keys.rb b/app/models/concerns/faster_cache_keys.rb
index 5b14723fa2d..312a9aa9305 100644
--- a/app/models/concerns/faster_cache_keys.rb
+++ b/app/models/concerns/faster_cache_keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FasterCacheKeys
# A faster version of Rails' "cache_key" method.
#
diff --git a/app/models/concerns/feature_gate.rb b/app/models/concerns/feature_gate.rb
index 5db64fe82c4..3f84de54ad5 100644
--- a/app/models/concerns/feature_gate.rb
+++ b/app/models/concerns/feature_gate.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FeatureGate
def flipper_id
return nil if new_record?
diff --git a/app/models/concerns/ghost_user.rb b/app/models/concerns/ghost_user.rb
index da696127a80..15278c431fb 100644
--- a/app/models/concerns/ghost_user.rb
+++ b/app/models/concerns/ghost_user.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GhostUser
extend ActiveSupport::Concern
diff --git a/app/models/concerns/group_descendant.rb b/app/models/concerns/group_descendant.rb
index 5e9a95c3282..05cd4265133 100644
--- a/app/models/concerns/group_descendant.rb
+++ b/app/models/concerns/group_descendant.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GroupDescendant
# Returns the hierarchy of a project or group in the from of a hash upto a
# given top.
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 72c236a0fc7..b3960cbad1a 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module HasStatus
extend ActiveSupport::Concern
diff --git a/app/models/concerns/has_variable.rb b/app/models/concerns/has_variable.rb
index c8e20c0ab81..dfbe413a878 100644
--- a/app/models/concerns/has_variable.rb
+++ b/app/models/concerns/has_variable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module HasVariable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/ignorable_column.rb b/app/models/concerns/ignorable_column.rb
index 03793e8bcbb..2b074c1921c 100644
--- a/app/models/concerns/ignorable_column.rb
+++ b/app/models/concerns/ignorable_column.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Module that can be included into a model to make it easier to ignore database
# columns.
#
diff --git a/app/models/concerns/iid_routes.rb b/app/models/concerns/iid_routes.rb
index 246748cf52c..b7f99e845ca 100644
--- a/app/models/concerns/iid_routes.rb
+++ b/app/models/concerns/iid_routes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module IidRoutes
##
# This automagically enforces all related routes to use `iid` instead of `id`
diff --git a/app/models/concerns/importable.rb b/app/models/concerns/importable.rb
index c9331eaf4cc..4d2707b08ab 100644
--- a/app/models/concerns/importable.rb
+++ b/app/models/concerns/importable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Importable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 7a459078151..e8072145551 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == Issuable concern
#
# Contains common functionality shared between Issues and MergeRequests
@@ -154,7 +156,7 @@ module Issuable
end
# Break ties with the ID column for pagination
- sorted.order(id: :desc)
+ sorted.with_order_id_desc
end
def order_due_date_and_labels_priority(excluded_labels: [])
diff --git a/app/models/concerns/loaded_in_group_list.rb b/app/models/concerns/loaded_in_group_list.rb
index 935e9d10133..a2233eb2997 100644
--- a/app/models/concerns/loaded_in_group_list.rb
+++ b/app/models/concerns/loaded_in_group_list.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module LoadedInGroupList
extend ActiveSupport::Concern
diff --git a/app/models/concerns/manual_inverse_association.rb b/app/models/concerns/manual_inverse_association.rb
index 0fca8feaf89..d0d781dc15f 100644
--- a/app/models/concerns/manual_inverse_association.rb
+++ b/app/models/concerns/manual_inverse_association.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ManualInverseAssociation
extend ActiveSupport::Concern
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index c013e5a708f..7e7eccb1c27 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == Mentionable concern
#
# Contains functionality related to objects that can mention Users, Issues, MergeRequests, Commits or Snippets by
diff --git a/app/models/concerns/mentionable/reference_regexes.rb b/app/models/concerns/mentionable/reference_regexes.rb
index 2d86a70c395..f6fd28bac33 100644
--- a/app/models/concerns/mentionable/reference_regexes.rb
+++ b/app/models/concerns/mentionable/reference_regexes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mentionable
module ReferenceRegexes
def self.reference_pattern(link_patterns, issue_pattern)
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 967fd9c5eea..e44a069b730 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Milestoneish
def closed_items_count(user)
memoize_per_user(user, :closed_items_count) do
diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb
index 510b8868462..aad19329be1 100644
--- a/app/models/concerns/note_on_diff.rb
+++ b/app/models/concerns/note_on_diff.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Contains functionality shared between `DiffNote` and `LegacyDiffNote`.
module NoteOnDiff
extend ActiveSupport::Concern
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 86f28f30032..ce778eae271 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Noteable
# Names of all implementers of `Noteable` that support resolvable notes.
RESOLVABLE_TYPES = %w(MergeRequest).freeze
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 01b1ef9f82c..1f6c42f3b3a 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == Participable concern
#
# Contains functionality related to objects that can have participants, such as
diff --git a/app/models/concerns/presentable.rb b/app/models/concerns/presentable.rb
index bc4fbd19a02..06c300c2e41 100644
--- a/app/models/concerns/presentable.rb
+++ b/app/models/concerns/presentable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Presentable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index 1f7d78a2efe..f268a842db4 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Makes api V4 compatible with old project features permissions methods
#
# After migrating issues_enabled merge_requests_enabled builds_enabled snippets_enabled and wiki_enabled
diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb
index 9c36f633395..a29e80fe0c1 100644
--- a/app/models/concerns/prometheus_adapter.rb
+++ b/app/models/concerns/prometheus_adapter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module PrometheusAdapter
extend ActiveSupport::Concern
diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb
index e62f42e8e70..744f7f48dc8 100644
--- a/app/models/concerns/protected_branch_access.rb
+++ b/app/models/concerns/protected_branch_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ProtectedBranchAccess
extend ActiveSupport::Concern
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index dbe8d31de37..e62e680af6e 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ProtectedRef
extend ActiveSupport::Concern
diff --git a/app/models/concerns/protected_ref_access.rb b/app/models/concerns/protected_ref_access.rb
index 5ff7b41b82b..efa666fb3f2 100644
--- a/app/models/concerns/protected_ref_access.rb
+++ b/app/models/concerns/protected_ref_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ProtectedRefAccess
extend ActiveSupport::Concern
diff --git a/app/models/concerns/protected_tag_access.rb b/app/models/concerns/protected_tag_access.rb
index ee65de24dd8..04bd54d6b1c 100644
--- a/app/models/concerns/protected_tag_access.rb
+++ b/app/models/concerns/protected_tag_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ProtectedTagAccess
extend ActiveSupport::Concern
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index 9155d82d567..d3572875fb3 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# The ReactiveCaching concern is used to fetch some data in the background and
# store it in the Rails cache, keeping it up-to-date for as long as it is being
# requested. If the data hasn't been requested for +reactive_cache_lifetime+,
@@ -42,6 +44,8 @@
module ReactiveCaching
extend ActiveSupport::Concern
+ InvalidateReactiveCache = Class.new(StandardError)
+
included do
class_attribute :reactive_cache_lease_timeout
@@ -63,15 +67,19 @@ module ReactiveCaching
end
def with_reactive_cache(*args, &blk)
- bootstrap = !within_reactive_cache_lifetime?(*args)
- Rails.cache.write(alive_reactive_cache_key(*args), true, expires_in: self.class.reactive_cache_lifetime)
+ unless within_reactive_cache_lifetime?(*args)
+ refresh_reactive_cache!(*args)
+ return nil
+ end
- if bootstrap
- ReactiveCachingWorker.perform_async(self.class, id, *args)
- nil
- else
+ keep_alive_reactive_cache!(*args)
+
+ begin
data = Rails.cache.read(full_reactive_cache_key(*args))
yield data if data.present?
+ rescue InvalidateReactiveCache
+ refresh_reactive_cache!(*args)
+ nil
end
end
@@ -96,6 +104,16 @@ module ReactiveCaching
private
+ def refresh_reactive_cache!(*args)
+ clear_reactive_cache!(*args)
+ keep_alive_reactive_cache!(*args)
+ ReactiveCachingWorker.perform_async(self.class, id, *args)
+ end
+
+ def keep_alive_reactive_cache!(*args)
+ Rails.cache.write(alive_reactive_cache_key(*args), true, expires_in: self.class.reactive_cache_lifetime)
+ end
+
def full_reactive_cache_key(*qualifiers)
prefix = self.class.reactive_cache_key
prefix = prefix.call(self) if prefix.respond_to?(:call)
diff --git a/app/models/concerns/reactive_service.rb b/app/models/concerns/reactive_service.rb
index 713246039c1..af69da24994 100644
--- a/app/models/concerns/reactive_service.rb
+++ b/app/models/concerns/reactive_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ReactiveService
extend ActiveSupport::Concern
diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb
index 3bdc1330d23..69554f18ea2 100644
--- a/app/models/concerns/redis_cacheable.rb
+++ b/app/models/concerns/redis_cacheable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RedisCacheable
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index b782e85717e..468eaf68883 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == Referable concern
#
# Contains functionality related to making a model referable in Markdown, such
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index afacdb8cb12..85229cded5d 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RelativePositioning
extend ActiveSupport::Concern
diff --git a/app/models/concerns/resolvable_discussion.rb b/app/models/concerns/resolvable_discussion.rb
index 7c236369793..c0490af2453 100644
--- a/app/models/concerns/resolvable_discussion.rb
+++ b/app/models/concerns/resolvable_discussion.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ResolvableDiscussion
extend ActiveSupport::Concern
include ::Gitlab::Utils::StrongMemoize
diff --git a/app/models/concerns/resolvable_note.rb b/app/models/concerns/resolvable_note.rb
index 4a0f8b92b3a..f47e20229f1 100644
--- a/app/models/concerns/resolvable_note.rb
+++ b/app/models/concerns/resolvable_note.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ResolvableNote
extend ActiveSupport::Concern
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index cb91f8fbac8..b9ffc64e4a9 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Store object full path in separate table for easy lookup and uniq validation
# Object must have name and path db fields and respond to parent and parent_changed? methods.
module Routable
diff --git a/app/models/concerns/select_for_project_authorization.rb b/app/models/concerns/select_for_project_authorization.rb
index 7af0fdbd618..39306179eb8 100644
--- a/app/models/concerns/select_for_project_authorization.rb
+++ b/app/models/concerns/select_for_project_authorization.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SelectForProjectAuthorization
extend ActiveSupport::Concern
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index 3796737427a..c322c356db2 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ShaAttribute
extend ActiveSupport::Concern
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index cb76ae971d4..501bd1bb83c 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == Sortable concern
#
# Set default scope for ordering objects
@@ -6,6 +8,7 @@ module Sortable
extend ActiveSupport::Concern
included do
+ scope :with_order_id_desc, -> { order(id: :desc) }
scope :order_id_desc, -> { reorder(id: :desc) }
scope :order_id_asc, -> { reorder(id: :asc) }
scope :order_created_desc, -> { reorder(created_at: :desc) }
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 5e4274619c4..c6e3dc385fe 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Spammable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index f5225cd81ed..3b745657a9e 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Storage
module LegacyNamespace
extend ActiveSupport::Concern
diff --git a/app/models/concerns/storage/legacy_project_wiki.rb b/app/models/concerns/storage/legacy_project_wiki.rb
index ff82cb0ffa9..a377fa1e5de 100644
--- a/app/models/concerns/storage/legacy_project_wiki.rb
+++ b/app/models/concerns/storage/legacy_project_wiki.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Storage
module LegacyProjectWiki
extend ActiveSupport::Concern
diff --git a/app/models/concerns/storage/legacy_repository.rb b/app/models/concerns/storage/legacy_repository.rb
index 593749bf019..eb93d0fc7f1 100644
--- a/app/models/concerns/storage/legacy_repository.rb
+++ b/app/models/concerns/storage/legacy_repository.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Storage
module LegacyRepository
extend ActiveSupport::Concern
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
index 8806ebe897a..344f677a3f3 100644
--- a/app/models/concerns/strip_attribute.rb
+++ b/app/models/concerns/strip_attribute.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == Strip Attribute module
#
# Contains functionality to clean attributes before validation
diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb
index f478c8ede18..1d0a61364b0 100644
--- a/app/models/concerns/subscribable.rb
+++ b/app/models/concerns/subscribable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == Subscribable concern
#
# Users can subscribe to these models.
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index ccd6f0e0a7d..603d4d62578 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'task_list'
require 'task_list/filter'
diff --git a/app/models/concerns/throttled_touch.rb b/app/models/concerns/throttled_touch.rb
index ad0ff0f20d4..797c46f6cc5 100644
--- a/app/models/concerns/throttled_touch.rb
+++ b/app/models/concerns/throttled_touch.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# ThrottledTouch can be used to throttle the number of updates triggered by
# calling "touch" on an ActiveRecord model.
module ThrottledTouch
diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb
index 0fc321c52bc..f61a0bbc65b 100644
--- a/app/models/concerns/time_trackable.rb
+++ b/app/models/concerns/time_trackable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# == TimeTrackable concern
#
# Contains functionality related to objects that support time tracking.
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index ec3543f7053..522b65e4205 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TokenAuthenticatable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/triggerable_hooks.rb b/app/models/concerns/triggerable_hooks.rb
index ec0ed3b795a..f55ab2fcaf3 100644
--- a/app/models/concerns/triggerable_hooks.rb
+++ b/app/models/concerns/triggerable_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TriggerableHooks
AVAILABLE_TRIGGERS = {
repository_update_hooks: :repository_update_events,
diff --git a/app/models/concerns/uniquify.rb b/app/models/concerns/uniquify.rb
index 549a76da20e..382e826ec58 100644
--- a/app/models/concerns/uniquify.rb
+++ b/app/models/concerns/uniquify.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Uniquify
#
# Return a version of the given 'base' string that is unique
diff --git a/app/models/concerns/updated_at_filterable.rb b/app/models/concerns/updated_at_filterable.rb
index edb423b7828..1ab5ee9fbb9 100644
--- a/app/models/concerns/updated_at_filterable.rb
+++ b/app/models/concerns/updated_at_filterable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module UpdatedAtFilterable
extend ActiveSupport::Concern
diff --git a/app/models/concerns/valid_attribute.rb b/app/models/concerns/valid_attribute.rb
index 8c35cea8d58..251db9ce30b 100644
--- a/app/models/concerns/valid_attribute.rb
+++ b/app/models/concerns/valid_attribute.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ValidAttribute
extend ActiveSupport::Concern
diff --git a/app/models/concerns/with_uploads.rb b/app/models/concerns/with_uploads.rb
index 4245d083a49..e231af5368d 100644
--- a/app/models/concerns/with_uploads.rb
+++ b/app/models/concerns/with_uploads.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Mounted uploaders are destroyed by carrierwave's after_commit
# hook. This hook fetches upload location (local vs remote) from
# Upload model. So it's neccessary to make sure that during that
diff --git a/app/models/conversational_development_index/card.rb b/app/models/conversational_development_index/card.rb
index e8f09dc9161..f9180bdd97b 100644
--- a/app/models/conversational_development_index/card.rb
+++ b/app/models/conversational_development_index/card.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ConversationalDevelopmentIndex
class Card
attr_accessor :metric, :title, :description, :feature, :blog, :docs
diff --git a/app/models/conversational_development_index/idea_to_production_step.rb b/app/models/conversational_development_index/idea_to_production_step.rb
index 6e1753c9f30..e78a734693c 100644
--- a/app/models/conversational_development_index/idea_to_production_step.rb
+++ b/app/models/conversational_development_index/idea_to_production_step.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ConversationalDevelopmentIndex
class IdeaToProductionStep
attr_accessor :metric, :title, :features
diff --git a/app/models/conversational_development_index/metric.rb b/app/models/conversational_development_index/metric.rb
index 0bee62f954f..c54537572d6 100644
--- a/app/models/conversational_development_index/metric.rb
+++ b/app/models/conversational_development_index/metric.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ConversationalDevelopmentIndex
class Metric < ActiveRecord::Base
include Presentable
diff --git a/app/models/diff_viewer/added.rb b/app/models/diff_viewer/added.rb
index 1909e6ef9d8..70d13e3478c 100644
--- a/app/models/diff_viewer/added.rb
+++ b/app/models/diff_viewer/added.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
class Added < Base
include Simple
diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb
index 0cbe714288d..1176861a827 100644
--- a/app/models/diff_viewer/base.rb
+++ b/app/models/diff_viewer/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
class Base
PARTIAL_PATH_PREFIX = 'projects/diffs/viewers'.freeze
diff --git a/app/models/diff_viewer/client_side.rb b/app/models/diff_viewer/client_side.rb
index cf41d07f8eb..cc049e1ca49 100644
--- a/app/models/diff_viewer/client_side.rb
+++ b/app/models/diff_viewer/client_side.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
module ClientSide
extend ActiveSupport::Concern
diff --git a/app/models/diff_viewer/deleted.rb b/app/models/diff_viewer/deleted.rb
index 9c129bac694..78671084eeb 100644
--- a/app/models/diff_viewer/deleted.rb
+++ b/app/models/diff_viewer/deleted.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
class Deleted < Base
include Simple
diff --git a/app/models/diff_viewer/image.rb b/app/models/diff_viewer/image.rb
index 759d9a36ebb..c356c2ca50e 100644
--- a/app/models/diff_viewer/image.rb
+++ b/app/models/diff_viewer/image.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
class Image < Base
include Rich
diff --git a/app/models/diff_viewer/mode_changed.rb b/app/models/diff_viewer/mode_changed.rb
index d487d996f8d..bd07d1e21b9 100644
--- a/app/models/diff_viewer/mode_changed.rb
+++ b/app/models/diff_viewer/mode_changed.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
class ModeChanged < Base
include Simple
diff --git a/app/models/diff_viewer/no_preview.rb b/app/models/diff_viewer/no_preview.rb
index 5455fee4490..b0dd5fd151d 100644
--- a/app/models/diff_viewer/no_preview.rb
+++ b/app/models/diff_viewer/no_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
class NoPreview < Base
include Simple
diff --git a/app/models/diff_viewer/not_diffable.rb b/app/models/diff_viewer/not_diffable.rb
index 4f9638626ea..dc86599e722 100644
--- a/app/models/diff_viewer/not_diffable.rb
+++ b/app/models/diff_viewer/not_diffable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
class NotDiffable < Base
include Simple
diff --git a/app/models/diff_viewer/renamed.rb b/app/models/diff_viewer/renamed.rb
index f1fbfd8c6d5..70ab9d4ca2c 100644
--- a/app/models/diff_viewer/renamed.rb
+++ b/app/models/diff_viewer/renamed.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
class Renamed < Base
include Simple
diff --git a/app/models/diff_viewer/rich.rb b/app/models/diff_viewer/rich.rb
index 3b0ca6e4cff..2faa1be6567 100644
--- a/app/models/diff_viewer/rich.rb
+++ b/app/models/diff_viewer/rich.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
module Rich
extend ActiveSupport::Concern
diff --git a/app/models/diff_viewer/server_side.rb b/app/models/diff_viewer/server_side.rb
index aed1a0791b1..977204e6c97 100644
--- a/app/models/diff_viewer/server_side.rb
+++ b/app/models/diff_viewer/server_side.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
module ServerSide
extend ActiveSupport::Concern
diff --git a/app/models/diff_viewer/simple.rb b/app/models/diff_viewer/simple.rb
index 65750996ee4..8d28ca5239a 100644
--- a/app/models/diff_viewer/simple.rb
+++ b/app/models/diff_viewer/simple.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
module Simple
extend ActiveSupport::Concern
diff --git a/app/models/diff_viewer/static.rb b/app/models/diff_viewer/static.rb
index d761328b3f6..1278c22185c 100644
--- a/app/models/diff_viewer/static.rb
+++ b/app/models/diff_viewer/static.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
module Static
extend ActiveSupport::Concern
diff --git a/app/models/diff_viewer/text.rb b/app/models/diff_viewer/text.rb
index 98f4b2aea2a..d3d5055c402 100644
--- a/app/models/diff_viewer/text.rb
+++ b/app/models/diff_viewer/text.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DiffViewer
class Text < Base
include Simple
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index ec072882cc9..18c387f0d34 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProjectHook < WebHook
include TriggerableHooks
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index aef11514945..bda82a116a1 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ServiceHook < WebHook
belongs_to :service
validates :service, presence: true
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index 6bef00f26ea..90b4588a325 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class SystemHook < WebHook
include TriggerableHooks
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index e353abdda9c..f18aadefa5c 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class WebHook < ActiveRecord::Base
include Sortable
diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb
index 59a1f2aed69..2d9f7594e8c 100644
--- a/app/models/hooks/web_hook_log.rb
+++ b/app/models/hooks/web_hook_log.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class WebHookLog < ActiveRecord::Base
belongs_to :web_hook
diff --git a/app/models/issue/metrics.rb b/app/models/issue/metrics.rb
index 012d545c440..0f5ee957ec9 100644
--- a/app/models/issue/metrics.rb
+++ b/app/models/issue/metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Issue::Metrics < ActiveRecord::Base
belongs_to :issue
diff --git a/app/models/list.rb b/app/models/list.rb
index eabe3ffccbb..1a30acc83cf 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -4,7 +4,7 @@ class List < ActiveRecord::Base
belongs_to :board
belongs_to :label
- enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3 }
+ enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3, milestone: 4 }
validates :board, :list_type, presence: true
validates :label, :position, presence: true, if: :label?
@@ -27,11 +27,11 @@ class List < ActiveRecord::Base
end
def destroyable?
- label?
+ self.class.destroyable_types.include?(list_type&.to_sym)
end
def movable?
- label?
+ self.class.movable_types.include?(list_type&.to_sym)
end
def title
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 5da739f9618..fc49ee7ac8c 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class GroupMember < Member
SOURCE_TYPE = 'Namespace'.freeze
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 4f27d0aeaf8..0154fe5aeba 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ProjectMember < Member
SOURCE_TYPE = 'Project'.freeze
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 9b3e2d4446d..396647a14ae 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -16,8 +16,8 @@ class MergeRequest < ActiveRecord::Base
include ReactiveCaching
self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
- self.reactive_cache_refresh_interval = 1.hour
- self.reactive_cache_lifetime = 1.hour
+ self.reactive_cache_refresh_interval = 10.minutes
+ self.reactive_cache_lifetime = 10.minutes
ignore_column :locked_at,
:ref_fetched,
@@ -1041,16 +1041,21 @@ class MergeRequest < ActiveRecord::Base
return { status: :error, status_reason: 'This merge request does not have test reports' }
end
- with_reactive_cache(
- :compare_test_results,
- base_pipeline&.iid,
- actual_head_pipeline.iid) { |data| data } || { status: :parsing }
+ with_reactive_cache(:compare_test_results) do |data|
+ unless Ci::CompareTestReportsService.new(project)
+ .latest?(base_pipeline, actual_head_pipeline, data)
+ raise InvalidateReactiveCache
+ end
+
+ data
+ end || { status: :parsing }
end
def calculate_reactive_cache(identifier, *args)
case identifier.to_sym
when :compare_test_results
- Ci::CompareTestReportsService.new(project).execute(*args)
+ Ci::CompareTestReportsService.new(project).execute(
+ base_pipeline, actual_head_pipeline)
else
raise NotImplementedError, "Unknown identifier: #{identifier}"
end
diff --git a/app/models/merge_request/metrics.rb b/app/models/merge_request/metrics.rb
index 9e660eccd86..65e94a97b0a 100644
--- a/app/models/merge_request/metrics.rb
+++ b/app/models/merge_request/metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class MergeRequest::Metrics < ActiveRecord::Base
belongs_to :merge_request
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :pipeline_id
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index f2b2d291da9..cb1def1b422 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -146,22 +146,25 @@ class Milestone < ActiveRecord::Base
end
def self.sort_by_attribute(method)
- case method.to_s
- when 'due_date_asc'
- reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
- when 'due_date_desc'
- reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC'))
- when 'name_asc'
- reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower))
- when 'name_desc'
- reorder(Arel::Nodes::Descending.new(arel_table[:title].lower))
- when 'start_date_asc'
- reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC'))
- when 'start_date_desc'
- reorder(Gitlab::Database.nulls_last_order('start_date', 'DESC'))
- else
- order_by(method)
- end
+ sorted =
+ case method.to_s
+ when 'due_date_asc'
+ reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
+ when 'due_date_desc'
+ reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC'))
+ when 'name_asc'
+ reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower))
+ when 'name_desc'
+ reorder(Arel::Nodes::Descending.new(arel_table[:title].lower))
+ when 'start_date_asc'
+ reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC'))
+ when 'start_date_desc'
+ reorder(Gitlab::Database.nulls_last_order('start_date', 'DESC'))
+ else
+ order_by(method)
+ end
+
+ sorted.with_order_id_desc
end
##
diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb
index d667948deae..6c5a4c56377 100644
--- a/app/models/network/commit.rb
+++ b/app/models/network/commit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Network
class Commit
include ActionView::Helpers::TagHelper
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index 1e0d1f9edcb..1431dfefc55 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Network
class Graph
attr_reader :days, :commits, :map, :notes, :repo
diff --git a/app/services/ci/compare_test_reports_service.rb b/app/services/ci/compare_test_reports_service.rb
index 7a112211d94..ec25e934a27 100644
--- a/app/services/ci/compare_test_reports_service.rb
+++ b/app/services/ci/compare_test_reports_service.rb
@@ -2,23 +2,36 @@
module Ci
class CompareTestReportsService < ::BaseService
- def execute(base_pipeline_iid, head_pipeline_iid)
- base_pipeline = project.pipelines.find_by_iid(base_pipeline_iid) if base_pipeline_iid
- head_pipeline = project.pipelines.find_by_iid(head_pipeline_iid)
+ def execute(base_pipeline, head_pipeline)
+ comparer = Gitlab::Ci::Reports::TestReportsComparer
+ .new(base_pipeline&.test_reports, head_pipeline.test_reports)
- begin
- comparer = Gitlab::Ci::Reports::TestReportsComparer
- .new(base_pipeline&.test_reports, head_pipeline.test_reports)
+ {
+ status: :parsed,
+ key: key(base_pipeline, head_pipeline),
+ data: TestReportsComparerSerializer
+ .new(project: project)
+ .represent(comparer).as_json
+ }
+ rescue => e
+ {
+ status: :error,
+ key: key(base_pipeline, head_pipeline),
+ status_reason: e.message
+ }
+ end
+
+ def latest?(base_pipeline, head_pipeline, data)
+ data&.fetch(:key, nil) == key(base_pipeline, head_pipeline)
+ end
+
+ private
- {
- status: :parsed,
- data: TestReportsComparerSerializer
- .new(project: project)
- .represent(comparer).as_json
- }
- rescue => e
- { status: :error, status_reason: e.message }
- end
+ def key(base_pipeline, head_pipeline)
+ [
+ base_pipeline&.id, base_pipeline&.updated_at,
+ head_pipeline&.id, head_pipeline&.updated_at
+ ]
end
end
end
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index ca62a59d909..74791b81ccd 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -4,7 +4,7 @@
.modal-header
%h3.page-title
- link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer')
- = _("Authorize %{link_to_client} to use your account?")
+ = _("Authorize %{link_to_client} to use your account?").html_safe % { link_to_client: link_to_client }
.modal-body
- if current_user.admin?
diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml
index ab8263533be..0e225fe33a5 100644
--- a/app/views/groups/settings/_general.html.haml
+++ b/app/views/groups/settings/_general.html.haml
@@ -1,4 +1,5 @@
= form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f|
+ %input{ type: 'hidden', name: 'update_section', value: 'js-general-settings' }
= form_errors(@group)
%fieldset
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index f1f67af1d1e..ffce2d4b14f 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -1,4 +1,5 @@
= form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f|
+ %input{ type: 'hidden', name: 'update_section', value: 'js-permissions-settings' }
= form_errors(@group)
%fieldset
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index e7044f722c5..6f08a294c5d 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -31,17 +31,37 @@
%hr
= link_to _('Remove avatar'), profile_avatar_path, data: { confirm: _('Avatar will be removed. Are you sure?') }, method: :delete, class: 'btn btn-danger btn-inverted'
- - if show_user_status_field?
- %hr
- .row
- .col-lg-4.profile-settings-sidebar
- %h4.prepend-top-0= s_("User|Current Status")
- %p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface. The message can contain emoji codes, too.")
- .col-lg-8
- .row
- = f.fields_for :status, @user.status do |status_form|
- = status_form.text_field :emoji
- = status_form.text_field :message, maxlength: 100
+ %hr
+ .row
+ .col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0= s_("User|Current status")
+ %p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.")
+ .col-lg-8
+ = f.fields_for :status, @user.status do |status_form|
+ - emoji_button = button_tag type: :button,
+ class: 'js-toggle-emoji-menu emoji-menu-toggle-button btn has-tooltip',
+ title: s_("Profiles|Add status emoji") do
+ - if @user.status
+ = emoji_icon @user.status.emoji
+ %span#js-no-emoji-placeholder.no-emoji-placeholder{ class: ('hidden' if @user.status) }
+ = sprite_icon('emoji_slightly_smiling_face', css_class: 'award-control-icon-neutral')
+ = sprite_icon('emoji_smiley', css_class: 'award-control-icon-positive')
+ = sprite_icon('emoji_smile', css_class: 'award-control-icon-super-positive')
+ - reset_message_button = button_tag type: :button,
+ id: 'js-clear-user-status-button',
+ class: 'clear-user-status btn has-tooltip',
+ title: s_("Profiles|Clear status") do
+ = sprite_icon("close")
+
+ = status_form.hidden_field :emoji, id: 'js-status-emoji-field'
+ = status_form.text_field :message,
+ id: 'js-status-message-field',
+ class: 'form-control input-lg',
+ label: s_("Profiles|Your status"),
+ prepend: emoji_button,
+ append: reset_message_button,
+ placeholder: s_("Profiles|What's your status?")
+
%hr
.row
.col-lg-4.profile-settings-sidebar
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index c78baa5dfe4..ad8c7911fad 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -12,7 +12,13 @@
.input-group-prepend.has-tooltip{ title: root_url }
.input-group-text
= root_url
- = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1}
+ - namespace_id = namespace_id_from(params)
+ = f.select(:namespace_id,
+ namespaces_options(namespace_id || :current_user,
+ display_path: true,
+ extra_group: namespace_id),
+ {},
+ { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1})
- else
.input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index fb1ea471dec..062aa423bde 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -1,5 +1,5 @@
- expanded = Rails.env.test?
-%section.qa-deploy-keys-settings.settings.no-animate{ class: ('expanded' if expanded) }
+%section.qa-deploy-keys-settings.settings.no-animate#js-deploy-keys-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
Deploy Keys
diff --git a/app/views/projects/deploy_tokens/_form.html.haml b/app/views/projects/deploy_tokens/_form.html.haml
index 578a9e2f74d..8b7535397bc 100644
--- a/app/views/projects/deploy_tokens/_form.html.haml
+++ b/app/views/projects/deploy_tokens/_form.html.haml
@@ -1,7 +1,7 @@
%p.profile-settings-content
= s_("DeployTokens|Pick a name for the application, and we'll give you a unique deploy token.")
-= form_for token, url: create_deploy_token_namespace_project_settings_repository_path(project.namespace, project), method: :post do |f|
+= form_for token, url: create_deploy_token_namespace_project_settings_repository_path(project.namespace, project, anchor: 'js-deploy-tokens'), method: :post do |f|
= form_errors(token)
.form-group
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f483fad6142..30544dde451 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -15,6 +15,7 @@
.settings-content
.project-edit-errors
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-general-project-settings' }
%fieldset
.row
.form-group.col-md-9
@@ -75,6 +76,7 @@
Enable or disable certain project features and choose access levels.
.settings-content
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' }
-# haml-lint:disable InlineJavaScript
%script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project)
.js-project-permissions-form
@@ -94,6 +96,7 @@
= render_if_exists 'shared/promotions/promote_mr_features'
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
= render 'projects/merge_request_settings', form: f
= f.submit 'Save changes', class: "btn btn-save qa-save-merge-request-changes"
diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml
index 8a549d431ee..12cf40bb65f 100644
--- a/app/views/projects/forks/_fork_button.html.haml
+++ b/app/views/projects/forks/_fork_button.html.haml
@@ -5,7 +5,7 @@
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked
= link_to project_path(forked_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = project_identicon(namespace, class: "avatar s100 identicon")
+ = project_icon(namespace, class: "avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
@@ -18,7 +18,7 @@
class: ("disabled has-tooltip" unless can_create_project),
title: (_('You have reached your project limit') unless can_create_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = project_identicon(namespace, class: "avatar s100 identicon")
+ = project_icon(namespace, class: "avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
index c2d6c034e35..df2dcf19ed4 100644
--- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -1,4 +1,5 @@
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'new-protected-branch js-new-protected-branch' } do |f|
+ %input{ type: 'hidden', name: 'update_section', value: 'js-protected-branches-settings' }
.card
.card-header
%h3.card-title
diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml
index 4f1c6c92484..539b184e5c2 100644
--- a/app/views/projects/protected_branches/shared/_index.html.haml
+++ b/app/views/projects/protected_branches/shared/_index.html.haml
@@ -1,6 +1,6 @@
- expanded = Rails.env.test?
-%section.qa-protected-branches-settings.settings.no-animate{ class: ('expanded' if expanded) }
+%section.qa-protected-branches-settings.settings.no-animate#js-protected-branches-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
Protected Branches
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 05cee483c0e..81b07af22ad 100644
--- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
@@ -21,4 +21,4 @@
- if can_admin_project
%td
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], disabled: local_assigns[:disabled], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning"
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch, { update_section: 'js-protected-branches-settings' }], disabled: local_assigns[:disabled], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning"
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
index 0ae5ca3ff36..f98781b77f4 100644
--- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -1,4 +1,5 @@
= form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f|
+ %input{ type: 'hidden', name: 'update_section', value: 'js-protected-tags-settings' }
.card
.card-header
%h3.card-title
diff --git a/app/views/projects/protected_tags/shared/_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_protected_tag.html.haml
index 1702e38df7e..cc6f0309123 100644
--- a/app/views/projects/protected_tags/shared/_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_protected_tag.html.haml
@@ -19,4 +19,4 @@
- if can? current_user, :admin_project, @project
%td
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag], data: { confirm: 'Tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag, { update_section: 'js-protected-tags-settings' }], data: { confirm: 'Tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 7d878b38e85..ab9ba5c7569 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -1,6 +1,6 @@
.row
.col-lg-12
- = form_for @project, url: project_settings_ci_cd_path(@project) do |f|
+ = form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'autodevops-settings') do |f|
= form_errors(@project)
%fieldset.builds-feature.js-auto-devops-settings
.form-group
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 64751e5616a..434aed2f603 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -1,6 +1,6 @@
.row.prepend-top-default
.col-lg-12
- = form_for @project, url: project_settings_ci_cd_path(@project) do |f|
+ = form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings') do |f|
= form_errors(@project)
%fieldset.builds-feature
.form-group.append-bottom-default.js-secret-runner-token
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 1a353953838..e8681da6528 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -1,6 +1,7 @@
- if @project.valid?
:plain
- location.href = "#{edit_project_path(@project)}";
+ location.href = "#{edit_project_path(@project, anchor: params[:update_section])}";
+ location.reload();
- else
:plain
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index b35877e5518..e26f5260e5b 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -6,12 +6,13 @@
%i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable",
":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded }",
"aria-hidden": "true" }
+ = render_if_exists "shared/boards/components/list_milestone"
%a.user-avatar-link.js-no-trigger{ "v-if": "list.type === \"assignee\"", ":href": "list.assignee.path" }
-# haml-lint:disable AltText
%img.avatar.s20.has-tooltip{ height: "20", width: "20", ":src": "list.assignee.avatar", ":alt": "list.assignee.name" }
- %span.board-title-text.has-tooltip{ "v-if": "list.type !== \"label\"",
+ %span.board-title-text.has-tooltip.block-truncated{ "v-if": "list.type !== \"label\"",
":title" => '((list.label && list.label.description) || list.title || "")', data: { container: "body" } }
{{ list.title }}
diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index 607e7f471c9..532045f3697 100644
--- a/app/views/shared/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -19,6 +19,7 @@
":value" => "label.id" }
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
+ "v-bind:data-selected" => "selectedLabels",
data: { toggle: "dropdown",
field_name: "issue[label_names][]",
show_no: "true",
@@ -28,7 +29,7 @@
namespace_path: @namespace_path,
project_path: @project.try(:path) } }
%span.dropdown-toggle-text
- = _("Label")
+ {{ labelDropdownTitle }}
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default"
diff --git a/changelogs/unreleased/#47282-Improving-Contributor-On-Boarding-Documentation.yml b/changelogs/unreleased/#47282-Improving-Contributor-On-Boarding-Documentation.yml
new file mode 100644
index 00000000000..f7521ff9225
--- /dev/null
+++ b/changelogs/unreleased/#47282-Improving-Contributor-On-Boarding-Documentation.yml
@@ -0,0 +1,4 @@
+title: First Improvements made to the contributor on-boarding experience.
+merge_request: 20682
+author: Eddie Stubbington
+type: added
diff --git a/changelogs/unreleased/44127-board-label-edit-drop-down-is-showing-incorrect-selected-labels-summary.yml b/changelogs/unreleased/44127-board-label-edit-drop-down-is-showing-incorrect-selected-labels-summary.yml
new file mode 100644
index 00000000000..de991ef475a
--- /dev/null
+++ b/changelogs/unreleased/44127-board-label-edit-drop-down-is-showing-incorrect-selected-labels-summary.yml
@@ -0,0 +1,5 @@
+---
+title: Board label edit dropdown shows incorrect selected labels summary
+merge_request: 20673
+author:
+type: fixed
diff --git a/changelogs/unreleased/46165-web-ide-branch-picker.yml b/changelogs/unreleased/46165-web-ide-branch-picker.yml
new file mode 100644
index 00000000000..ff879cb3d37
--- /dev/null
+++ b/changelogs/unreleased/46165-web-ide-branch-picker.yml
@@ -0,0 +1,5 @@
+---
+title: Create branch and MR picker for Web IDE
+merge_request: 20978
+author:
+type: changed
diff --git a/changelogs/unreleased/46535-orphaned-uploads.yml b/changelogs/unreleased/46535-orphaned-uploads.yml
new file mode 100644
index 00000000000..1cd087a6aad
--- /dev/null
+++ b/changelogs/unreleased/46535-orphaned-uploads.yml
@@ -0,0 +1,5 @@
+---
+title: Clean orphaned files in object storage
+merge_request: 20918
+author:
+type: added
diff --git a/changelogs/unreleased/46703-group-dashboard-line-height-is-too-tall-for-group-names.yml b/changelogs/unreleased/46703-group-dashboard-line-height-is-too-tall-for-group-names.yml
new file mode 100644
index 00000000000..5b91c6d5a9f
--- /dev/null
+++ b/changelogs/unreleased/46703-group-dashboard-line-height-is-too-tall-for-group-names.yml
@@ -0,0 +1,5 @@
+---
+title: Solves group dashboard line height is too tall for group names.
+merge_request: 21033
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-homepage-link-to-status-pages.yml b/changelogs/unreleased/add-homepage-link-to-status-pages.yml
new file mode 100644
index 00000000000..0e7375f2061
--- /dev/null
+++ b/changelogs/unreleased/add-homepage-link-to-status-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Add link to homepage on static http status pages (404, 500, etc)
+merge_request: 20898
+author: Jason Funk
+type: added
diff --git a/changelogs/unreleased/ce-5666-backport.yml b/changelogs/unreleased/ce-5666-backport.yml
new file mode 100644
index 00000000000..344f1a1983f
--- /dev/null
+++ b/changelogs/unreleased/ce-5666-backport.yml
@@ -0,0 +1,5 @@
+---
+title: CE port of "List groups with developer maintainer access on project creation"
+merge_request: 21051
+author:
+type: other
diff --git a/changelogs/unreleased/ci-builds-status-index.yml b/changelogs/unreleased/ci-builds-status-index.yml
new file mode 100644
index 00000000000..8b7252f09e5
--- /dev/null
+++ b/changelogs/unreleased/ci-builds-status-index.yml
@@ -0,0 +1,5 @@
+---
+title: Remove redundant ci_builds (status) index
+merge_request: 21070
+author:
+type: performance
diff --git a/changelogs/unreleased/feat-add-default-avatar-to-group.yml b/changelogs/unreleased/feat-add-default-avatar-to-group.yml
new file mode 100644
index 00000000000..56d8f2ccd6d
--- /dev/null
+++ b/changelogs/unreleased/feat-add-default-avatar-to-group.yml
@@ -0,0 +1,5 @@
+---
+title: Add default avatar to group
+merge_request: 17271
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/fix-prometheus-updated-status.yml b/changelogs/unreleased/fix-prometheus-updated-status.yml
new file mode 100644
index 00000000000..7261c3429c8
--- /dev/null
+++ b/changelogs/unreleased/fix-prometheus-updated-status.yml
@@ -0,0 +1,5 @@
+---
+title: Fix UI error whereby prometheus application status is updated
+merge_request: 21029
+author:
+type: fixed
diff --git a/changelogs/unreleased/ide-open-empty-merge-request.yml b/changelogs/unreleased/ide-open-empty-merge-request.yml
new file mode 100644
index 00000000000..05f2de5d31c
--- /dev/null
+++ b/changelogs/unreleased/ide-open-empty-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Fix empty merge requests not opening in the Web IDE
+merge_request: 21102
+author:
+type: fixed
diff --git a/changelogs/unreleased/improve-junit-support-be.yml b/changelogs/unreleased/improve-junit-support-be.yml
new file mode 100644
index 00000000000..db4d47caa7c
--- /dev/null
+++ b/changelogs/unreleased/improve-junit-support-be.yml
@@ -0,0 +1,5 @@
+---
+title: Improve JUnit test reports in merge request widgets
+merge_request: 49966
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-fix-missing-and-duplicated-milestones-on-list.yml b/changelogs/unreleased/osw-fix-missing-and-duplicated-milestones-on-list.yml
new file mode 100644
index 00000000000..62416b7f87e
--- /dev/null
+++ b/changelogs/unreleased/osw-fix-missing-and-duplicated-milestones-on-list.yml
@@ -0,0 +1,5 @@
+---
+title: Fix missing and duplicates on project milestone listing page
+merge_request: 21058
+author:
+type: fixed
diff --git a/changelogs/unreleased/pl-json-gon.yml b/changelogs/unreleased/pl-json-gon.yml
new file mode 100644
index 00000000000..c0f93006c07
--- /dev/null
+++ b/changelogs/unreleased/pl-json-gon.yml
@@ -0,0 +1,5 @@
+---
+title: Don't set gon variables in JSON requests
+merge_request: 21016
+author: Peter Leitzen
+type: performance
diff --git a/changelogs/unreleased/sh-fix-bitbucket-cloud-importer-replies.yml b/changelogs/unreleased/sh-fix-bitbucket-cloud-importer-replies.yml
new file mode 100644
index 00000000000..3f7044833f1
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-bitbucket-cloud-importer-replies.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Bitbucket Cloud importer omitting replies
+merge_request: 21076
+author:
+type: fixed
diff --git a/changelogs/unreleased/todos-visibility-migration.yml b/changelogs/unreleased/todos-visibility-migration.yml
new file mode 100644
index 00000000000..651facc4ec8
--- /dev/null
+++ b/changelogs/unreleased/todos-visibility-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Remove todos of users without access to targets migration
+merge_request: 20927
+author:
+type: other
diff --git a/changelogs/unreleased/tz-mr-port-memory-fixes.yml b/changelogs/unreleased/tz-mr-port-memory-fixes.yml
new file mode 100644
index 00000000000..61d3c9abf71
--- /dev/null
+++ b/changelogs/unreleased/tz-mr-port-memory-fixes.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance and memory footprint of Changes tab of Merge Requests
+merge_request: 21028
+author:
+type: performance
diff --git a/changelogs/unreleased/winh-restyle-user-status.yml b/changelogs/unreleased/winh-restyle-user-status.yml
new file mode 100644
index 00000000000..90370e87825
--- /dev/null
+++ b/changelogs/unreleased/winh-restyle-user-status.yml
@@ -0,0 +1,5 @@
+---
+title: Restyle status message input on profile settings
+merge_request: 20903
+author:
+type: changed
diff --git a/config/initializers/active_record_verbose_query_logs.rb b/config/initializers/active_record_verbose_query_logs.rb
new file mode 100644
index 00000000000..44f86fec7e0
--- /dev/null
+++ b/config/initializers/active_record_verbose_query_logs.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+# This is backport of https://github.com/rails/rails/pull/26815/files
+# Enabled by default for every non-production environment
+
+module ActiveRecord
+ class LogSubscriber
+ module VerboseQueryLogs
+ def debug(progname = nil, &block)
+ return unless super
+
+ log_query_source
+ end
+
+ def log_query_source
+ source_line, line_number = extract_callstack(caller_locations)
+
+ if source_line
+ if defined?(::Rails.root)
+ app_root = "#{::Rails.root}/".freeze
+ source_line = source_line.sub(app_root, "")
+ end
+
+ logger.debug(" ↳ #{source_line}:#{line_number}")
+ end
+ end
+
+ def extract_callstack(callstack)
+ line = callstack.find do |frame|
+ frame.absolute_path && !ignored_callstack(frame.absolute_path)
+ end
+
+ offending_line = line || callstack.first
+ [
+ offending_line.path,
+ offending_line.lineno,
+ offending_line.label
+ ]
+ end
+
+ LOG_SUBSCRIBER_FILE = ActiveRecord::LogSubscriber.method(:logger).source_location.first
+ RAILS_GEM_ROOT = File.expand_path("../../../..", LOG_SUBSCRIBER_FILE) + "/"
+ APP_CONFIG_ROOT = File.expand_path("..", __dir__) + "/"
+
+ def ignored_callstack(path)
+ path.start_with?(APP_CONFIG_ROOT, RAILS_GEM_ROOT, RbConfig::CONFIG["rubylibdir"])
+ end
+ end
+
+ unless Gitlab.rails5?
+ prepend(VerboseQueryLogs) unless Rails.env.production?
+ end
+ end
+end
diff --git a/db/migrate/20160317092222_add_moved_to_to_issue.rb b/db/migrate/20160317092222_add_moved_to_to_issue.rb
index 461e7fb3a9b..2bf549d7ecd 100644
--- a/db/migrate/20160317092222_add_moved_to_to_issue.rb
+++ b/db/migrate/20160317092222_add_moved_to_to_issue.rb
@@ -1,5 +1,5 @@
class AddMovedToToIssue < ActiveRecord::Migration
def change
- add_reference :issues, :moved_to, references: :issues
+ add_reference :issues, :moved_to, references: :issues # rubocop:disable Migration/AddReference
end
end
diff --git a/db/migrate/20180717125853_remove_restricted_todos.rb b/db/migrate/20180717125853_remove_restricted_todos.rb
new file mode 100644
index 00000000000..fdf43921a73
--- /dev/null
+++ b/db/migrate/20180717125853_remove_restricted_todos.rb
@@ -0,0 +1,31 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+# frozen_string_literal: true
+
+class RemoveRestrictedTodos < ActiveRecord::Migration
+ DOWNTIME = false
+ disable_ddl_transaction!
+
+ MIGRATION = 'RemoveRestrictedTodos'.freeze
+ BATCH_SIZE = 1000
+ DELAY_INTERVAL = 5.minutes.to_i
+
+ class Project < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'projects'
+ end
+
+ def up
+ Project.where('EXISTS (SELECT 1 FROM todos WHERE todos.project_id = projects.id)')
+ .each_batch(of: BATCH_SIZE) do |batch, index|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ BackgroundMigrationWorker.perform_in(index * DELAY_INTERVAL, MIGRATION, range)
+ end
+ end
+
+ def down
+ # nothing to do
+ end
+end
diff --git a/db/migrate/20180807153545_remove_redundant_status_index_on_ci_builds.rb b/db/migrate/20180807153545_remove_redundant_status_index_on_ci_builds.rb
new file mode 100644
index 00000000000..f4f7cb6f8ca
--- /dev/null
+++ b/db/migrate/20180807153545_remove_redundant_status_index_on_ci_builds.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RemoveRedundantStatusIndexOnCiBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index :ci_builds, :status
+ end
+
+ def down
+ add_concurrent_index :ci_builds, :status
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c132f787530..f1d8f4df3b7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180726172057) do
+ActiveRecord::Schema.define(version: 20180807153545) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -346,7 +346,6 @@ ActiveRecord::Schema.define(version: 20180726172057) do
add_index "ci_builds", ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)", using: :btree
add_index "ci_builds", ["stage_id"], name: "index_ci_builds_on_stage_id", using: :btree
add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree
- add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree
add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
diff --git a/doc/api/README.md b/doc/api/README.md
index 4566319ad45..45e926d3b6b 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -401,14 +401,13 @@ GET /api/v4/projects/1/branches/my%2Fbranch/commits
## Encoding API parameters of `array` and `hash` types
-When making an API call with parameters of type `array` and/or `hash`, the parameters may be
-specified as shown below.
+We can call the API with `array` and `hash` types parameters as shown below:
### `array`
`import_sources` is a parameter of type `array`:
-```
+```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
-d "import_sources[]=github" \
-d "import_sources[]=bitbucket" \
@@ -419,7 +418,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
`override_params` is a parameter of type `hash`:
-```
+```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
--form "namespace=email" \
--form "path=impapi" \
@@ -429,6 +428,20 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
https://gitlab.example.com/api/v4/projects/import
```
+### Array of hashes
+
+`variables` is a parameter of type `array` containing hash key/value pairs `[{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }]`:
+
+```bash
+curl --globoff --request POST --header "PRIVATE-TOKEN: ********************" \
+"https://gitlab.example.com/api/v4/projects/169/pipeline?ref=master&variables[][key]=VAR1&variables[][value]=hello&variables[][key]=VAR2&variables[][value]=world"
+
+curl --request POST --header "PRIVATE-TOKEN: ********************" \
+--header "Content-Type: application/json" \
+--data '{ "ref": "master", "variables": [ {"key": "VAR1", "value": "hello"}, {"key": "VAR2", "value": "world"} ] }' \
+"https://gitlab.example.com/api/v4/projects/169/pipeline"
+```
+
## `id` vs `iid`
When you work with the API, you may notice two similar fields in API entities:
diff --git a/doc/development/contributing/design.md b/doc/development/contributing/design.md
new file mode 100644
index 00000000000..d4cce79b067
--- /dev/null
+++ b/doc/development/contributing/design.md
@@ -0,0 +1,70 @@
+<!-- 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)*
+
+- [Implement design & UI elements](#implement-design--ui-elements)
+- [Style guides](#style-guides)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+## Implement design & UI elements
+
+For guidance on UX implementation at GitLab, please refer to our [Design System](https://design.gitlab.com/).
+
+The UX team uses labels to manage their workflow.
+
+The ~"UX" label on an issue is a signal to the UX team that it will need UX attention.
+To better understand the priority by which UX tackles issues, see the [UX section](https://about.gitlab.com/handbook/engineering/ux) of the handbook.
+
+Once an issue has been worked on and is ready for development, a UXer removes the ~"UX" label and applies the ~"UX ready" label to that issue.
+
+The UX team has a special type label called ~"design artifact". This label indicates that the final output
+for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone.
+Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is
+needed until the solution has been decided.
+
+~"design artifact" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone.
+
+To prevent the misunderstanding that a feature will be be delivered in the
+assigned milestone, when only UX design is planned for that milestone, the
+Product Manager should create a separate issue for the ~"design artifact",
+assign the ~UX, ~"design artifact" and ~"Deliverable" labels, add a milestone
+and use a title that makes it clear that the scheduled issue is design only
+(e.g. `Design exploration for XYZ`).
+
+When the ~"design artifact" issue has been completed, the UXer removes the ~UX
+label, adds the ~"UX ready" label and closes the issue. This indicates the
+design artifact is complete. The UXer will also copy the designs to related
+issues for implementation in an upcoming milestone.
+
+## Style guides
+
+1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
+ Important sections include [Source Code Layout][rss-source] and
+ [Naming][rss-naming]. Use:
+ - multi-line method chaining style **Option A**: dot `.` on the second line
+ - string literal quoting style **Option A**: single quoted by default
+1. [Rails](https://github.com/bbatsov/rails-style-guide)
+1. [Newlines styleguide][newlines-styleguide]
+1. [Testing][testing]
+1. [JavaScript styleguide][js-styleguide]
+1. [SCSS styleguide][scss-styleguide]
+1. [Shell commands](../shell_commands.md) created by GitLab
+ contributors to enhance security
+1. [Database Migrations](../migration_style_guide.md)
+1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
+1. [Documentation styleguide](https://docs.gitlab.com/ee/development/documentation/styleguide.html)
+1. Interface text should be written subjectively instead of objectively. It
+ should be the GitLab core team addressing a person. It should be written in
+ present time and never use past tense (has been/was). For example instead
+ of _prohibited this user from being saved due to the following errors:_ the
+ text should be _sorry, we could not create your account because:_
+1. Code should be written in [US English][us-english]
+
+This is also the style used by linting tools such as
+[RuboCop](https://github.com/bbatsov/rubocop),
+[PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
+
+---
+
+[Return to Contributing documentation](index.md)
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
new file mode 100644
index 00000000000..64f5a2c8022
--- /dev/null
+++ b/doc/development/contributing/index.md
@@ -0,0 +1,246 @@
+<!-- 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)*
+
+- [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)
+ - [Milestone labels](#milestone-labels)
+ - [Bug Priority labels](#bug-priority-labels)
+ - [Bug Severity labels](#bug-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 -->
+
+## Contribute to GitLab
+
+For a first-time step-by-step guide to the contribution process, see
+["Contributing to GitLab"](https://about.gitlab.com/contributing/).
+
+Thank you for your interest in contributing to GitLab. This guide details how
+to contribute to GitLab in a way that is efficient for everyone.
+
+Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute).
+
+GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
+source edition, and GitLab Enterprise Edition (EE) which is our commercial
+edition. Throughout this guide you will see references to CE and EE for
+abbreviation.
+
+If you have read this guide and want to know how the GitLab [core team]
+operates please see [the GitLab contributing process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md).
+
+- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
+
+## Security vulnerability disclosure
+
+Please report suspected security vulnerabilities in private to
+`support@gitlab.com`, also see the
+[disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/).
+Please do **NOT** create publicly viewable issues for suspected security
+vulnerabilities.
+
+## Code of conduct
+
+As contributors and maintainers of this project, we pledge to respect all
+people who contribute through reporting issues, posting feature requests,
+updating documentation, submitting pull requests or patches, and other
+activities.
+
+We are committed to making participation in this project a harassment-free
+experience for everyone, regardless of level of experience, gender, gender
+identity and expression, sexual orientation, disability, personal appearance,
+body size, race, ethnicity, age, or religion.
+
+Examples of unacceptable behavior by participants include the use of sexual
+language or imagery, derogatory comments or personal attacks, trolling, public
+or private harassment, insults, or other unprofessional conduct.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct. Project maintainers who do not
+follow the Code of Conduct may be removed from the project team.
+
+This code of conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior can be
+reported by emailing `contact@gitlab.com`.
+
+This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
+available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
+
+## Closing policy for issues and merge requests
+
+GitLab is a popular open source project and the capacity to deal with issues
+and merge requests is limited. Out of respect for our volunteers, issues and
+merge requests not in line with the guidelines listed in this document may be
+closed without notice.
+
+Please treat our volunteers with courtesy and respect, it will go a long way
+towards getting your issue resolved.
+
+Issues and merge requests should be in English and contain appropriate language
+for audiences of all ages.
+
+If a contributor is no longer actively working on a submitted merge request
+we can decide that the merge request will be finished by one of our
+[Merge request coaches][team] or close the merge request. We make this decision
+based on how important the change is for our product vision. If a Merge request
+coach is going to finish the merge request we assign the
+~"coach will finish" label.
+
+## Helping others
+
+Please help other GitLab users when you can.
+The methods people will use to seek help can be found on the [getting help page][getting-help].
+
+Sign up for the mailing list, answer GitLab questions on StackOverflow or
+respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
+the remaining issues on the GitHub issue tracker.
+
+## I want to contribute!
+
+If you want to contribute to GitLab [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight]
+is a great place to start. Issues with a lower weight (1 or 2) are deemed
+suitable for beginners. These issues will be of reasonable size and challenge,
+for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
+learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
+please consider we favor
+[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
+
+## Contribution Flow
+
+When contributing to GitLab, your merge request is subject to review by merge request maintainers of a particular specialty.
+
+When you submit code to GitLab, we really want it to get merged, but there will be times when it will not be merged.
+
+When maintainers are reading through a merge request they may request guidance from other maintainers. If merge request maintainers conclude that the code should not be merged, our reasons will be fully disclosed. If it has been decided that the code quality is not up to GitLab’s standards, the merge request maintainer will refer the author to our docs and code style guides, and provide some guidance.
+
+Sometimes style guides will be followed but the code will lack structural integrity, or the maintainer will have reservations about the code’s overall quality. When there is a reservation the maintainer will inform the author and provide some guidance. The author may then choose to update the merge request. Once the merge request has been updated and reassigned to the maintainer, they will review the code again. Once the code has been resubmitted any number of times, the maintainer may choose to close the merge request with a summary of why it will not be merged, as well as some guidance. If the merge request is closed the maintainer will be open to discussion as to how to improve the code so it can be approved in the future.
+
+GitLab will do its best to review community contributions as quickly as possible. Specially appointed developers review community contributions daily. You may take a look at the [team page](https://about.gitlab.com/team/) for the merge request coach who specializes in the type of code you have written and mention them in the merge request. For example, if you have written some JavaScript in your code then you should mention the frontend merge request coach. If your code has multiple disciplines you may mention multiple merge request coaches.
+
+GitLab receives a lot of community contributions, so if your code has not been reviewed within 4 days of its initial submission feel free to re-mention the appropriate merge request coach.
+
+When submitting code to GitLab, you may feel that your contribution requires the aid of an external library. If your code includes an external library please provide a link to the library, as well as reasons for including it.
+
+When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
+
+## Workflow labels
+
+This [documentation](issue_workflow.md) outlines the current workflow labels.
+
+### Type labels
+
+This [documentation](issue_workflow.md) outlines the current type labels.
+
+### Subject labels
+
+This [documentation](issue_workflow.md) outlines the current subject labels.
+
+### Team labels
+
+This [documentation](issue_workflow.md) outlines the current team labels.
+
+### Milestone labels
+
+This [documentation](issue_workflow.md) outlines the current milestone labels.
+
+### Bug Priority labels
+
+This [documentation](issue_workflow.md) outlines the current bug priority labels.
+
+### Bug Severity labels
+
+This [documentation](issue_workflow.md) outlines the current severity labels.
+
+#### Severity impact guidance
+
+This [documentation](issue_workflow.md) outlines the current severity impact guidance.
+
+### Label for community contributors
+
+This [documentation](issue_workflow.md) outlines the current policy regarding community contributor issues.
+
+## Implement design & UI elements
+
+This [documentation](design.md) outlines the current design and UI guidelines.
+
+## Issue tracker
+
+This [documentation](issue_workflow.md) outlines the issue tracker process.
+
+### Issue triaging
+
+This [documentation](issue_workflow.md) outlines the current issue triaging process.
+
+### Feature proposals
+
+This [documentation](issue_workflow.md) outlines the feature proposal process.
+
+### Issue tracker guidelines
+
+This [documentation](issue_workflow.md) outlines the issue tracker guidelines.
+
+### Issue weight
+
+This [documentation](issue_workflow.md) outlines the issue weight guidelines.
+
+### Regression issues
+
+This [documentation](issue_workflow.md) outlines the regression issue process.
+
+### Technical and UX debt
+
+This [documentation](issue_workflow.md) about technical and UX debt has been moved.
+
+### Stewardship
+
+This [documentation](issue_workflow.md) outlines the stewardship process.
+
+## Merge requests
+
+This [documentation](merge_request_workflow.md) outlines the current merge request process.
+
+### Merge request guidelines
+
+This [documentation](merge_request_workflow.md) outlines the current merge request guidelines.
+
+### Contribution acceptance criteria
+
+This [documentation](merge_request_workflow.md) outlines the current acceptance criteria for contributions.
+
+## Definition of done
+
+This [documentation](merge_request_workflow.md) outlines the definition of done.
+
+## Style guides
+This [documentation](design.md) outlines the current style guidelines.
+
+---
+
+[Return to Development documentation](../README.md)
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
new file mode 100644
index 00000000000..6a334e9b17d
--- /dev/null
+++ b/doc/development/contributing/issue_workflow.md
@@ -0,0 +1,357 @@
+<!-- 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)*
+
+- [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)
+ - [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)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+## Workflow labels
+
+To allow for asynchronous issue handling, we use [milestones][milestones-page]
+and [labels][labels-page]. Leads and product managers handle most of the
+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.
+- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
+- Team: ~"CI/CD", ~Plan, ~Manage, ~Quality, etc.
+- Release Scoping: ~Deliverable, ~Stretch, ~"Next Patch Release"
+- Priority: ~P1, ~P2, ~P3, ~P4
+- Severity: ~S1, ~S2, ~S3, ~S4
+
+All labels, their meaning and priority are defined on the
+[labels page][labels-page].
+
+If you come across an issue that has none of these, and you're allowed to set
+labels, you can _always_ add the team and type, and often also the subject.
+
+[milestones-page]: https://gitlab.com/gitlab-org/gitlab-ce/milestones
+[labels-page]: https://gitlab.com/gitlab-org/gitlab-ce/labels
+
+### Type labels
+
+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".
+
+A number of type labels have a priority assigned to them, which automatically
+makes them float to the top, depending on their importance.
+
+Type labels are always lowercase, and can have any color, besides blue (which is
+already reserved for subject labels).
+
+The descriptions on the [labels page][labels-page] explain what falls under each type label.
+
+### Subject labels
+
+Subject labels are labels that define what area or feature of GitLab this issue
+hits. They are not always necessary, but very convenient.
+
+Examples of subject labels are ~wiki, ~ldap, ~api,
+~issues, ~"merge requests", ~labels, and ~"container registry".
+
+If you are an expert in a particular area, it makes it easier to find issues to
+work on. You can also subscribe to those labels to receive an email each time an
+issue is labeled with a subject label corresponding to your expertise.
+
+Subject labels are always all-lowercase.
+
+### Team labels
+
+Team labels specify what team is responsible for this issue.
+Assigning a team label makes sure issues get the attention of the appropriate
+people.
+
+The current team labels are:
+
+- ~Configuration
+- ~"CI/CD"
+- ~Create
+- ~Distribution
+- ~Documentation
+- ~Geo
+- ~Gitaly
+- ~Manage
+- ~Monitoring
+- ~Plan
+- ~Quality
+- ~Release
+- ~Secure
+- ~UX
+
+The descriptions on the [labels page][labels-page] explain what falls under the
+responsibility of each team.
+
+Within those team labels, we also have the ~backend and ~frontend labels to
+indicate if an issue needs backend work, frontend work, or both.
+
+Team labels are always capitalized so that they show up as the first label for
+any issue.
+
+### Release Scoping labels
+
+Release Scoping labels help us clearly communicate expectations of the work for the
+release. There are three levels of Release Scoping labels:
+
+- ~Deliverable: Issues that are expected to be delivered in the current
+ milestone.
+- ~Stretch: Issues that are a stretch goal for delivering in the current
+ milestone. If these issues are not done in the current release, they will
+ strongly be considered for the next release.
+- ~"Next Patch Release": Issues to put in the next patch release. Work on these
+ first, and add the "Pick Into X" label to the merge request, along with the
+ appropriate milestone.
+
+Each issue scheduled for the current milestone should be labeled ~Deliverable
+or ~"Stretch". Any open issue for a previous milestone should be labeled
+~"Next Patch Release", or otherwise rescheduled to a different milestone.
+
+### Priority labels
+
+Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be.
+If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
+This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes.
+
+| Label | Meaning | Estimate time to fix |
+|-------|-----------------|------------------------------------------------------------------|
+| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com |
+| ~P2 | High Priority | The next release |
+| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) |
+| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) |
+
+### Severity labels
+
+Severity labels help us clearly communicate the impact of a ~bug on users.
+
+| Label | Meaning | Impact on Functionality | Example |
+|-------|-------------------|-------------------------------------------------------|---------|
+| ~S1 | Blocker | Outage, broken feature with no workaround | Unable to create an issue. Data corruption/loss. Security breach. |
+| ~S2 | Critical Severity | Broken Feature, workaround too complex & unacceptable | Can push commits, but only via the command line. |
+| ~S3 | Major Severity | Broken Feature, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue. |
+| ~S4 | Low Severity | Functionality inconvenience or cosmetic issue | Label colors are incorrect / not being displayed. |
+
+#### Severity impact guidance
+
+Severity levels can be applied further depending on the facet of the impact; e.g. Affected customers, GitLab.com availability, performance and etc. The below is a guideline.
+
+| Severity | Affected Customers/Users | GitLab.com Availability | Performance Degradation |
+|----------|---------------------------------------------------------------------|----------------------------------------------------|------------------------------|
+| ~S1 | >50% users affected (possible company extinction level event) | Significant impact on all of GitLab.com | |
+| ~S2 | Many users or multiple paid customers affected (but not apocalyptic)| Significant impact on large portions of GitLab.com | Degradation is guaranteed to occur in the near future |
+| ~S3 | A few users or a single paid customer affected | Limited impact on important portions of GitLab.com | Degradation is likely to occur in the near future |
+| ~S4 | No paid users/customer affected, or expected to in the near future | Minor impact on on GitLab.com | Degradation _may_ occur but it's not likely |
+
+### Label for community contributors
+
+Issues that are beneficial to our users, 'nice to haves', that we currently do
+not have the capacity for or want to give the priority to, are labeled as
+~"Accepting Merge Requests", so the community can make a contribution.
+
+Community contributors can submit merge requests for any issue they want, but
+the ~"Accepting Merge Requests" label has a special meaning. It points to
+changes that:
+
+1. We already agreed on,
+1. Are well-defined,
+1. Are likely to get accepted by a maintainer.
+
+We want to avoid a situation when a contributor picks an
+~"Accepting Merge Requests" issue and then their merge request gets closed,
+because we realize that it does not fit our vision, or we want to solve it in a
+different way.
+
+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 ~"technical debt" issues
+
+After adding the ~"Accepting Merge Requests" label, we try to estimate the
+[weight](#issue-weight) of the issue. We use issue weight to let contributors
+know how difficult the issue is. Additionally:
+
+- We advertise ["Accepting Merge Requests" issues with weight < 5][up-for-grabs]
+ as suitable for people that have never contributed to GitLab before on the
+ [Up For Grabs campaign](http://up-for-grabs.net)
+- We encourage people that have never contributed to any open source project to
+ look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers]
+
+If you've decided that you would like to work on an issue, please @-mention
+the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
+as soon as possible. The product manager will then pull in appropriate GitLab team
+members to further discuss scope, design, and technical considerations. This will
+ensure that that your contribution is aligned with the GitLab product and minimize
+any rework and delay in getting it merged into master.
+
+GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
+should update the issue description with a responsible product manager, inviting
+any potential community contributor to @-mention per above.
+
+[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
+[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
+
+
+### Issue triaging
+
+Our issue triage policies are [described in our handbook]. You are very welcome
+to help the GitLab team triage issues. We also organize [issue bash events] once
+every quarter.
+
+The most important thing is making sure valid issues receive feedback from the
+development team. Therefore the priority is mentioning developers that can help
+on those issues. Please select someone with relevant experience from the
+[GitLab team][team]. If there is nobody mentioned with that expertise look in
+the commit history for the affected files to find someone.
+
+We also use [GitLab Triage] to automate some triaging policies. This is
+currently setup as a [scheduled pipeline] running on [quality/triage-ops]
+project.
+
+[described in our handbook]: https://about.gitlab.com/handbook/engineering/issue-triage/
+[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
+[GitLab Triage]: https://gitlab.com/gitlab-org/gitlab-triage
+[scheduled pipeline]: https://gitlab.com/gitlab-org/quality/triage-ops/pipeline_schedules/10512/edit
+[quality/triage-ops]: https://gitlab.com/gitlab-org/quality/triage-ops
+
+### Feature proposals
+
+To create a feature proposal for CE, open an issue on the
+[issue tracker of CE][ce-tracker].
+
+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
+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"`.
+
+Please keep feature proposals as small and simple as possible, complex ones
+might be edited to make them small and simple.
+
+Please submit Feature Proposals using the ['Feature Proposal' issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
+
+For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should
+be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may
+need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself.
+
+If you want to create something yourself, consider opening an issue first to
+discuss whether it is interesting to include this in GitLab.
+
+### Issue tracker guidelines
+
+**[Search the issue tracker][ce-tracker]** for similar entries before
+submitting your own, there's a good chance somebody else had the same issue or
+feature proposal. Show your support with an award emoji and/or join the
+discussion.
+
+Please submit bugs using the ['Bug' issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Bug.md) provided on the issue tracker.
+The text in the parenthesis is there to help you with what to include. Omit it
+when submitting the actual issue. You can copy-paste it and then edit as you
+see fit.
+
+### Issue weight
+
+Issue weight allows us to get an idea of the amount of work required to solve
+one or multiple issues. This makes it possible to schedule work more accurately.
+
+You are encouraged to set the weight of any issue. Following the guidelines
+below will make it easy to manage this, without unnecessary overhead.
+
+1. Set weight for any issue at the earliest possible convenience
+1. If you don't agree with a set weight, discuss with other developers until
+consensus is reached about the weight
+1. Issue weights are an abstract measurement of complexity of the issue. Do not
+relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring)
+and something you want to avoid.
+1. Something that has a weight of 1 (or no weight) is really small and simple.
+Something that is 9 is rewriting a large fundamental part of GitLab,
+which might lead to many hard problems to solve. Changing some text in GitLab
+is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
+1. If something is very large, it should probably be split up in multiple
+issues or chunks. You can simply not set the weight of a parent issue and set
+weights to children issues.
+
+### Regression issues
+
+Every monthly release has a corresponding issue on the CE issue tracker to keep
+track of functionality broken by that release and any fixes that need to be
+included in a patch release (see [8.3 Regressions] as an example).
+
+As outlined in the issue description, the intended workflow is to post one note
+with a reference to an issue describing the regression, and then to update that
+note with a reference to the merge request that fixes it as it becomes available.
+
+If you're a contributor who doesn't have the required permissions to update
+other users' notes, please post a new note with a reference to both the issue
+and the merge request.
+
+The release manager will [update the notes] in the regression issue as fixes are
+addressed.
+
+[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
+[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
+
+### Technical and UX debt
+
+In order to track things that can be improved in GitLab's codebase,
+we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
+For user experience improvements, we use the ~"UX debt" label.
+
+These labels should be added to issues that describe things that can be improved,
+shortcuts that have been taken, features that need additional attention, and all
+other things that have been left behind due to high velocity of development.
+For example, code that needs refactoring should use the ~"technical debt" label,
+user experience refinements should use the ~"UX debt" label.
+
+Everyone can create an issue, though you may need to ask for adding a specific
+label, if you do not have permissions to do it by yourself. Additional labels
+can be combined with these labels, to make it easier to schedule
+the improvements for a release.
+
+Issues tagged with these labels have the same priority like issues
+that describe a new feature to be introduced in GitLab, and should be scheduled
+for a release by the appropriate person.
+
+Make sure to mention the merge request that the ~"technical debt" issue or
+~"UX debt" issue is associated with in the description of the issue.
+
+### Stewardship
+
+For issues related to the open source stewardship of GitLab,
+there is the ~"stewardship" label.
+
+This label is to be used for issues in which the stewardship of GitLab
+is a topic of discussion. For instance if GitLab Inc. is planning to add
+features from GitLab EE to GitLab CE, related issues would be labelled with
+~"stewardship".
+
+A recent example of this was the issue for
+[bringing the time tracking API to GitLab CE][time-tracking-issue].
+
+[time-tracking-issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/25517#note_20019084
+
+---
+
+[Return to Contributing documentation](index.md)
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
new file mode 100644
index 00000000000..9b1da4e7bc1
--- /dev/null
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -0,0 +1,191 @@
+<!-- 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)*
+
+- [Merge requests](#merge-requests)
+ - [Merge request guidelines](#merge-request-guidelines)
+ - [Contribution acceptance criteria](#contribution-acceptance-criteria)
+- [Definition of done](#definition-of-done)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+## Merge requests
+
+We welcome merge requests with fixes and improvements to GitLab code, tests,
+and/or documentation. The issues that are specifically suitable for
+community contributions are listed with the label
+[`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
+and [EE][accepting-mrs-ee], but you are free to contribute to any other issue
+you want.
+
+Please note that if an issue is marked for the current milestone either before
+or while you are working on it, a team member may take over the merge request
+in order to ensure the work is finished before the release date.
+
+If you want to add a new feature that is not labeled it is best to first create
+a feedback issue (if there isn't one already) and leave a comment asking for it
+to be marked as `Accepting Merge Requests`. Please include screenshots or
+wireframes if the feature will also change the UI.
+
+Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
+
+If you are new to GitLab development (or web development in general), see the
+[I want to contribute!](#i-want-to-contribute) section to get you started with
+some potentially easy issues.
+
+To start with GitLab development download the [GitLab Development Kit][gdk] and
+see the [Development section](../README.md) for some guidelines.
+
+### Merge request guidelines
+
+If you can, please submit a merge request with the fix or improvements
+including tests. If you don't know how to fix the issue but can write a test
+that exposes the issue we will accept that as well. In general bug fixes that
+include a regression test are merged quickly while new features without proper
+tests are least likely to receive timely feedback. The workflow to make a merge
+request is as follows:
+
+1. Fork the project into your personal space on GitLab.com
+1. Create a feature branch, branch away from `master`
+1. Write [tests](https://docs.gitlab.com/ee/development/rake_tasks.html#run-tests) and code
+1. [Generate a changelog entry with `bin/changelog`][changelog]
+1. If you are writing documentation, make sure to follow the
+ [documentation guidelines][doc-guidelines]
+1. If you have multiple commits please combine them into a few logically
+ organized commits by [squashing them][git-squash]
+1. Push the commit(s) to your fork
+1. Submit a merge request (MR) to the `master` branch
+ 1. Your merge request needs at least 1 approval but feel free to require more.
+ For instance if you're touching backend and frontend code, it's a good idea
+ to require 2 approvals: 1 from a backend maintainer and 1 from a frontend
+ maintainer
+ 1. You don't have to select any approvers, but you can if you really want
+ specific people to approve your merge request
+1. The MR title should describe the change you want to make
+1. The MR description should give a motive for your change and the method you
+ used to achieve it.
+ 1. If you are contributing code, fill in the template already provided in the
+ "Description" field.
+ 1. If you are contributing documentation, choose `Documentation` from the
+ "Choose a template" menu and fill in the template.
+ 1. Mention the issue(s) your merge request solves, using the `Solves #XXX` or
+ `Closes #XXX` syntax to auto-close the issue(s) once the merge request will
+ be merged.
+1. If you're allowed to, set a relevant milestone and labels
+1. If the MR changes the UI it should include *Before* and *After* screenshots
+1. If the MR changes CSS classes please include the list of affected pages,
+ `grep css-class ./app -R`
+1. Be prepared to answer questions and incorporate feedback even if requests
+ for this arrive weeks or months after your MR submission
+ 1. If a discussion has been addressed, select the "Resolve discussion" button
+ beneath it to mark it resolved.
+1. If your MR touches code that executes shell commands, reads or opens files or
+ handles paths to files on disk, make sure it adheres to the
+ [shell command guidelines](../shell_commands.md)
+1. If your code creates new files on disk please read the
+ [shared files guidelines](../shared_files.md).
+1. When writing commit messages please follow
+ [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
+ [guidelines](http://chris.beams.io/posts/git-commit/).
+1. If your merge request adds one or more migrations, make sure to execute all
+ migrations on a fresh database before the MR is reviewed. If the review leads
+ to large changes in the MR, do this again once the review is complete.
+1. For more complex migrations, write tests.
+1. Merge requests **must** adhere to the [merge request performance
+ guidelines](../merge_request_performance_guidelines.md).
+1. For tests that use Capybara or PhantomJS, see this [article on how
+ to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
+
+Please keep the change in a single MR **as small as possible**. If you want to
+contribute a large feature think very hard what the minimum viable change is.
+Can you split the functionality? Can you only submit the backend/API code? Can
+you start with a very simple UI? Can you do part of the refactor? The increased
+reviewability of small MRs that leads to higher code quality is more important
+to us than having a minimal commit log. The smaller an MR is the more likely it
+is it will be merged (quickly). After that you can send more MRs to enhance it.
+The ['How to get faster PR reviews' document of Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) also has some great points regarding this.
+
+For examples of feedback on merge requests please look at already
+[closed merge requests][closed-merge-requests]. If you would like quick feedback
+on your merge request feel free to mention someone from the [core team] or one
+of the [Merge request coaches][team].
+Please ensure that your merge request meets the contribution acceptance criteria.
+
+When having your code reviewed and when reviewing merge requests please take the
+[code review guidelines](../code_review.md) into account.
+
+### Contribution acceptance criteria
+
+1. The change is as small as possible
+1. Include proper tests and make all tests pass (unless it contains a test
+ exposing a bug in existing code). Every new class should have corresponding
+ unit tests, even if the class is exercised at a higher level, such as a feature test.
+1. If you suspect a failing CI build is unrelated to your contribution, you may
+ try and restart the failing CI job or ask a developer to fix the
+ aforementioned failing test
+1. Your MR initially contains a single commit (please use `git rebase -i` to
+ squash commits)
+1. Your changes can merge without problems (if not please rebase if you're the
+ only one working on your feature branch, otherwise, merge `master`)
+1. Does not break any existing functionality
+1. Fixes one specific issue or implements one specific feature (do not combine
+ things, send separate merge requests if needed)
+1. Migrations should do only one thing (e.g., either create a table, move data
+ to a new table or remove an old table) to aid retrying on failure
+1. Keeps the GitLab code base clean and well structured
+1. Contains functionality we think other users will benefit from too
+1. Doesn't add configuration options or settings options since they complicate
+ making and testing future changes
+1. Changes do not adversely degrade performance.
+ - Avoid repeated polling of endpoints that require a significant amount of overhead
+ - Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/ce/development/mer ge_request_performance_guidelines.html)
+ - Avoid repeated access of filesystem
+1. If you need polling to support real-time features, please use
+ [polling with ETag caching][polling-etag].
+1. Changes after submitting the merge request should be in separate commits
+ (no squashing).
+1. It conforms to the [style guides](#style-guides) and the following:
+ - If your change touches a line that does not follow the style, modify the
+ entire line to follow it. This prevents linting tools from generating warnings.
+ - Don't touch neighbouring lines. As an exception, automatic mass
+ refactoring modifications may leave style non-compliant.
+1. If the merge request adds any new libraries (gems, JavaScript libraries,
+ etc.), they should conform to our [Licensing guidelines][license-finder-doc].
+ See the instructions in that document for help if your MR fails the
+ "license-finder" test with a "Dependencies that need approval" error.
+1. The merge request meets the [definition of done](#definition-of-done).
+
+## Definition of done
+
+If you contribute to GitLab please know that changes involve more than just
+code. We have the following [definition of done][definition-of-done]. Please ensure you support
+the feature you contribute through all of these steps.
+
+1. Description explaining the relevancy (see following item)
+1. Working and clean code that is commented where needed
+1. [Unit, integration, and system tests][testing] that pass on the CI server
+1. Performance/scalability implications have been considered, addressed, and tested
+1. [Documented][doc-guidelines] in the `/doc` directory
+1. [Changelog entry added][changelog], if necessary
+1. Reviewed and any concerns are addressed
+1. Merged by a project maintainer
+1. Added to the release blog article, if relevant
+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.)
+
+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. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
+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
+
+---
+
+[Return to Contributing documentation](index.md)
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index a211effdfa7..6f31e5b82e5 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -182,6 +182,34 @@ class MyMigration < ActiveRecord::Migration
end
```
+## Adding foreign-key constraints
+
+When adding a foreign-key constraint to either an existing or new
+column remember to also add a index on the column.
+
+This is _required_ if the foreign-key constraint specifies
+`ON DELETE CASCADE` or `ON DELETE SET NULL` behavior. On a cascading
+delete, the [corresponding record needs to be retrieved using an
+index](https://www.cybertec-postgresql.com/en/postgresql-indexes-and-foreign-keys/)
+(otherwise, we'd need to scan the whole table) for subsequent update or
+deletion.
+
+Here's an example where we add a new column with a foreign key
+constraint. Note it includes `index: true` to create an index for it.
+
+```ruby
+class Migration < ActiveRecord::Migration
+
+ def change
+ add_reference :model, :other_model, index: true, foreign_key: { on_delete: :cascade }
+ end
+end
+```
+
+When adding a foreign-key constraint to an existing column, we
+have to employ `add_concurrent_foreign_key` and `add_concurrent_index`
+instead of `add_reference`.
+
## Adding Columns With Default Values
When adding columns with default values you must use the method
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index 2afcb052536..bf587b5b296 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -1,4 +1,4 @@
-# Integrate your GitLab server with Bitbucket
+# Integrate your GitLab server with Bitbucket Cloud
NOTE: **Note:**
You need to [enable OmniAuth](omniauth.md) in order to use this.
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index 8ba2e8731c8..acc9db15826 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -78,8 +78,8 @@ in the **Authorized applications** section under **Profile Settings > Applicatio
---
GitLab's OAuth applications support scopes, which allow various actions that any given
-application can perform. Although there are only two scopes available at the
-moment – `read_user` and `api` – the groundwork has been laid to add more scopes easily.
+application can perform such as `read_user` and `api`. There are many more scopes
+available.
At any time you can revoke any access by just clicking **Revoke**.
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index e2eb342361a..e70a009323e 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -52,4 +52,33 @@ D, [2018-07-27T12:08:33.293568 #89817] DEBUG -- : Processing batch of 500 projec
I, [2018-07-27T12:08:33.689869 #89817] INFO -- : Did move to lost and found /opt/gitlab/embedded/service/gitlab-rails/public/uploads/test.out -> /opt/gitlab/embedded/service/gitlab-rails/public/uploads/-/project-lost-found/test.out
I, [2018-07-27T12:08:33.755624 #89817] INFO -- : Did fix /opt/gitlab/embedded/service/gitlab-rails/public/uploads/foo/bar/89a0f7b0b97008a4a18cedccfdcd93fb/foo.txt -> /opt/gitlab/embedded/service/gitlab-rails/public/uploads/qux/foo/bar/89a0f7b0b97008a4a18cedccfdcd93fb/foo.txt
I, [2018-07-27T12:08:33.760257 #89817] INFO -- : Did move to lost and found /opt/gitlab/embedded/service/gitlab-rails/public/uploads/foo/bar/1dd6f0f7eefd2acc4c2233f89a0f7b0b/image.png -> /opt/gitlab/embedded/service/gitlab-rails/public/uploads/-/project-lost-found/foo/bar/1dd6f0f7eefd2acc4c2233f89a0f7b0b/image.png
-``` \ No newline at end of file
+```
+
+Remove object store upload files if they don't exist in GitLab database.
+
+```
+# omnibus-gitlab
+sudo gitlab-rake gitlab:cleanup:remote_upload_files
+
+# installation from source
+bundle exec rake gitlab:cleanup:remote_upload_files RAILS_ENV=production
+```
+
+Example output:
+
+```
+$ sudo gitlab-rake gitlab:cleanup:remote_upload_files
+
+I, [2018-08-02T10:26:13.995978 #45011] INFO -- : Looking for orphaned remote uploads to remove. Dry run...
+I, [2018-08-02T10:26:14.120400 #45011] INFO -- : Can be moved to lost and found: @hashed/6b/DSC_6152.JPG
+I, [2018-08-02T10:26:14.120482 #45011] INFO -- : Can be moved to lost and found: @hashed/79/02/7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451/711491b29d3eb08837798c4909e2aa4d/DSC00314.jpg
+I, [2018-08-02T10:26:14.120634 #45011] INFO -- : To cleanup these files run this command with DRY_RUN=false
+```
+
+```
+$ sudo gitlab-rake gitlab:cleanup:remote_upload_files DRY_RUN=false
+
+I, [2018-08-02T10:26:47.598424 #45087] INFO -- : Looking for orphaned remote uploads to remove...
+I, [2018-08-02T10:26:47.753131 #45087] INFO -- : Moved to lost and found: @hashed/6b/DSC_6152.JPG -> lost_and_found/@hashed/6b/DSC_6152.JPG
+I, [2018-08-02T10:26:47.764356 #45087] INFO -- : Moved to lost and found: @hashed/79/02/7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451/711491b29d3eb08837798c4909e2aa4d/DSC00314.jpg -> lost_and_found/@hashed/79/02/7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451/711491b29d3eb08837798c4909e2aa4d/DSC00314.jpg
+```
diff --git a/doc/user/project/bulk_editing.md b/doc/user/project/bulk_editing.md
index 324a7fa6603..4261293b06f 100644
--- a/doc/user/project/bulk_editing.md
+++ b/doc/user/project/bulk_editing.md
@@ -1,17 +1,25 @@
-# Bulk Editing
+# Bulk editing issues and merge requests
->**Note:**
+>
+**Notes:**
- A permission level of `Reporter` or higher is required in order to manage
issues.
- A permission level of `Developer` or higher is required in order to manage
merge requests.
-Fields across multiple issues or merge requests can be updated simutaneously by using the bulk edit feature.
+Attributes can be updated simultaneously across multiple issues or merge requests
+by using the bulk editing feature.
->**Note:**
-- Bulk editing of issues and merge requests is only available at the project level.
+![Bulk editing](img/bulk-editing.png)
-To access the feature, navigate to either the issue or merge request list for the project and click 'Edit Issues' or 'Edit Merge Requests'. This will cause a sidebar to be shown on the right-hand side of the screen, where the available, editable fields are displayed. Checkboxes will also appear to the left-hand side of each issue or merge request, ready to be selected.
+NOTE: **Note:**
+Bulk editing of issues and merge requests is only available at the project level.
-Once all items have been selected, choose the appropriate fields and their values from the sidebar and click 'Update All' to apply these changes.
+To update multiple project issues or merge requests at the same time, navigate to
+their respective lists and click **Edit issues** or **Edit merge requests** available
+in the tab bar. This will open a sidebar on the right-hand side of your screen
+where editable fields will be displayed. Checkboxes will also appear to the left-hand
+side of eachissue or merge request for you to select the items you want to update.
+Once you have selected all relevant items, choose the appropriate fields and their
+values from the sidebar and click **Update all** to apply your changes.
diff --git a/doc/user/project/img/bulk-editing.png b/doc/user/project/img/bulk-editing.png
new file mode 100644
index 00000000000..f6b163f55d9
--- /dev/null
+++ b/doc/user/project/img/bulk-editing.png
Binary files differ
diff --git a/doc/user/project/img/labels_project_list_search.png b/doc/user/project/img/labels_project_list_search.png
new file mode 100644
index 00000000000..ff9bf92e1c3
--- /dev/null
+++ b/doc/user/project/img/labels_project_list_search.png
Binary files differ
diff --git a/doc/user/project/import/bitbucket.md b/doc/user/project/import/bitbucket.md
index e3d625cc621..1cc3bd3363d 100644
--- a/doc/user/project/import/bitbucket.md
+++ b/doc/user/project/import/bitbucket.md
@@ -1,17 +1,13 @@
-# Import your project from Bitbucket to GitLab
+# Import your project from Bitbucket Cloud to GitLab
-Import your projects from Bitbucket to GitLab with minimal effort.
+NOTE: **Note:**
+The Bitbucket Cloud importer works only with Bitbucket.org, not with Bitbucket
+Server (aka Stash). If you are trying to import projects from Bitbucket Server, use
+[the Bitbucket Server importer](bitbucket_server.md).
-## Overview
-
->**Note:**
-The [Bitbucket integration][bb-import] must be first enabled in order to be
-able to import your projects from Bitbucket. Ask your GitLab administrator
-to enable this if not already.
+Import your projects from Bitbucket Cloud to GitLab with minimal effort.
->**Note:**
-The BitBucket importer currently only works with BitBucket's cloud offering
-(bitbucket.org) and does not work with BitBucket Server (aka Stash).
+## Overview
- At its current state, the Bitbucket importer can import:
- the repository description (GitLab 7.7+)
@@ -26,6 +22,11 @@ The BitBucket importer currently only works with BitBucket's cloud offering
- Repository public access is retained. If a repository is private in Bitbucket
it will be created as private in GitLab as well.
+## Requirements
+
+The [Bitbucket Cloud integration][bb-import] must be first enabled in order to be
+able to import your projects from Bitbucket Cloud. Ask your GitLab administrator
+to enable this if not already.
## How it works
@@ -46,9 +47,7 @@ namespace that started the import process.
1. Sign in to GitLab and go to your dashboard.
1. Click on **New project**.
- ![New project in GitLab](img/bitbucket_import_new_project.png)
-
-1. Click on the "Bitbucket" button
+1. Click on the "Bitbucket Cloud" button.
![Bitbucket](img/import_projects_from_new_project_page.png)
diff --git a/doc/user/project/import/bitbucket_server.md b/doc/user/project/import/bitbucket_server.md
new file mode 100644
index 00000000000..dc985e87a96
--- /dev/null
+++ b/doc/user/project/import/bitbucket_server.md
@@ -0,0 +1,75 @@
+# Import your project from Bitbucket Server to GitLab
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20164)
+in GitLab 11.2.
+
+NOTE: **Note:**
+The Bitbucket Server importer does not work with Bitbucket Cloud (aka bitbucket.org).
+Use the [Bitbucket Cloud importer](bitbucket.md) for that.
+
+Import your projects from Bitbucket Server to GitLab with minimal effort.
+
+## Overview
+
+- In its current state, the Bitbucket importer can import:
+ - the repository description (GitLab 11.2+)
+ - the Git repository data (GitLab 11.2+)
+ - the pull requests (GitLab 11.2+)
+ - the pull request comments (GitLab 11.2+)
+- Repository public access is retained. If a repository is private in Bitbucket
+ it will be created as private in GitLab as well.
+
+## Limitations
+
+1. Currently GitLab doesn't allow comments on arbitrary lines of code, so any
+Bitbucket comments out of bounds will be inserted as comments in the merge
+request.
+1. Bitbucket Server allows multiple levels of threading. GitLab
+import will collapse this into one discussion and quote part of the original
+comment.
+1. Declined pull requests have unrecahable commits, which prevents the GitLab
+importer from generating a proper diff. These pull requests will show up as
+empty changes.
+1. Attachments in Markdown are currently not imported.
+1. Task lists are not imported.
+1. Emoji reactions are not imported
+
+## How it works
+
+The Bitbucket Server importer works as follows:
+
+1. The user will be prompted to enter the URl, username, and password or personal access token to login to Bitbucket.
+ These credentials are preserved only as long as the importer is running.
+1. The importer will attempt to list all the current repositories on the Bitbucket Server.
+1. Upon selection, the importer will clone the repository and import pull requests and comments.
+
+### User assignment
+
+When issues/pull requests are being imported, the Bitbucket importer tries to
+find the author's e-mail address with a confirmed e-mail address in the GitLab
+user database. If no such user is available, the project creator is set as
+the author. The importer will append a note in the comment to mark the original
+creator.
+
+The importer will create any new namespaces (groups) if they don't exist or in
+the case the namespace is taken, the repository will be imported under the user's
+namespace that started the import process.
+
+## Importing your Bitbucket repositories
+
+1. Sign in to GitLab and go to your dashboard.
+1. Click on **New project**.
+1. Click on the "Bitbucket Server" button. If the button is not present, enable the importer in
+ **Admin > Application Settings > Visibility and access controls > Import sources**.
+
+ ![Bitbucket](img/import_projects_from_new_project_page.png)
+
+1. Enter your Bitbucket Server credentials.
+
+ ![Grant access](img/bitbucket_server_import_credentials.png)
+
+1. Click on the projects that you'd like to import or **Import all projects**.
+ You can also select the namespace under which each project will be
+ imported.
+
+ ![Import projects](img/bitbucket_server_import_select_project.png)
diff --git a/doc/user/project/import/img/bitbucket_import_new_project.png b/doc/user/project/import/img/bitbucket_import_new_project.png
deleted file mode 100644
index 8ed528c2f09..00000000000
--- a/doc/user/project/import/img/bitbucket_import_new_project.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/import/img/bitbucket_server_import_credentials.png b/doc/user/project/import/img/bitbucket_server_import_credentials.png
new file mode 100644
index 00000000000..70b26e89d49
--- /dev/null
+++ b/doc/user/project/import/img/bitbucket_server_import_credentials.png
Binary files differ
diff --git a/doc/user/project/import/img/bitbucket_server_import_select_project.png b/doc/user/project/import/img/bitbucket_server_import_select_project.png
new file mode 100644
index 00000000000..e5b1b89e6a3
--- /dev/null
+++ b/doc/user/project/import/img/bitbucket_server_import_select_project.png
Binary files differ
diff --git a/doc/user/project/import/img/import_projects_from_new_project_page.png b/doc/user/project/import/img/import_projects_from_new_project_page.png
index 97ca30b2087..40402eae226 100644
--- a/doc/user/project/import/img/import_projects_from_new_project_page.png
+++ b/doc/user/project/import/img/import_projects_from_new_project_page.png
Binary files differ
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 6dfdbe6c0d5..49b49271cff 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -119,7 +119,7 @@ Issue Board, that is, create or delete lists and drag issues from one list to an
## Issue Board terminology
- **Issue Board** - Each board represents a unique view for your issues. It can have multiple lists with each list consisting of issues represented by cards.
-- **List** - A column on the issue board that displays issues matching certain attributes. In addition to the default lists of 'Backlog' and 'Closed' issue, each additional list will show issues matching your chosen label or assignee.
+- **List** - A column on the issue board that displays issues matching certain attributes. In addition to the default lists of 'Backlog' and 'Closed' issue, each additional list will show issues matching your chosen label or assignee. On the top of that list you can see the number of issues that belong to it.
- **Label list**: a list based on a label. It shows all opened issues with that label.
- **Assignee list**: a list which includes all issues assigned to a user.
- **Backlog** (default): shows all open issues that do not belong to one of the other lists. Always appears as the leftmost list.
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index 914898ea2ea..3ae6dbe585e 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -69,6 +69,16 @@ Every issue and merge request can be assigned any number of labels. The labels a
|:---:|:---:|
| ![Labels sidebar](img/labels_sidebar.png) | ![Labels sidebar assign](img/labels_sidebar_assign.png) |
+## Searching for project labels
+
+You can search for project labels by navigating from the left sidebar to your
+project's **Issues > Labels** and entering your query to the search bar on the
+top-right:
+
+![Labels project list search](img/labels_project_list_search.png)
+
+GitLab will consider the label title and description for the search.
+
## Filtering issues and merge requests by label
### Filtering in list pages
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index b0143e45ab6..511ac2d7e79 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -59,9 +59,18 @@ left.
> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19318) [GitLab Core][ce] 11.0.
Switching between your authored and assigned merge requests can be done without
-leaving the Web IDE. Click the project name in the top left to open a list of
-merge requests. You will need to commit or discard all your changes before
+leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
+of merge requests. You will need to commit or discard all your changes before
switching to a different merge request.
+## Switching branches
+
+> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20850) [GitLab Core][ce] 11.2.
+
+Switching between branches of the current project repository can be done without
+leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
+of branches. You will need to commit or discard all your changes before
+switching to a different branch.
+
[ce]: https://about.gitlab.com/pricing/
[ee]: https://about.gitlab.com/pricing/
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index 086d39d5070..0f89414148b 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -71,12 +71,10 @@ module API
success Entities::List
end
params do
- requires :label_id, type: Integer, desc: 'The ID of an existing label'
+ use :list_creation_params
end
post '/lists' do
- unless available_labels_for(user_project).exists?(params[:label_id])
- render_api_error!({ error: 'Label not found!' }, 400)
- end
+ authorize_list_type_resource!
authorize!(:admin_list, user_project)
diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb
index ead0943a74d..7e873012efe 100644
--- a/lib/api/boards_responses.rb
+++ b/lib/api/boards_responses.rb
@@ -14,7 +14,7 @@ module API
def create_list
create_list_service =
- ::Boards::Lists::CreateService.new(board_parent, current_user, { label_id: params[:label_id] })
+ ::Boards::Lists::CreateService.new(board_parent, current_user, create_list_params)
list = create_list_service.execute(board)
@@ -25,6 +25,10 @@ module API
end
end
+ def create_list_params
+ params.slice(:label_id)
+ end
+
def move_list(list)
move_list_service =
::Boards::Lists::MoveService.new(board_parent, current_user, { position: params[:position].to_i })
@@ -44,6 +48,16 @@ module API
end
end
end
+
+ def authorize_list_type_resource!
+ unless available_labels_for(board_parent).exists?(params[:label_id])
+ render_api_error!({ error: 'Label not found!' }, 400)
+ end
+ end
+
+ params :list_creation_params do
+ requires :label_id, type: Integer, desc: 'The ID of an existing label'
+ end
end
end
end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 4b223a391ae..3e445e6b1fa 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -19,6 +19,7 @@ module API
params :filter_params do
optional :search, type: String, desc: 'Return list of branches matching the search criteria'
+ optional :sort, type: String, desc: 'Return list of branches sorted by the given field'
end
end
diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb
index aa9fff25fc8..3832cdc10a8 100644
--- a/lib/api/group_boards.rb
+++ b/lib/api/group_boards.rb
@@ -70,12 +70,10 @@ module API
success Entities::List
end
params do
- requires :label_id, type: Integer, desc: 'The ID of an existing label'
+ use :list_creation_params
end
post '/lists' do
- unless available_labels_for(board_parent).exists?(params[:label_id])
- render_api_error!({ error: 'Label not found!' }, 400)
- end
+ authorize_list_type_resource!
authorize!(:admin_list, user_group)
diff --git a/lib/gitlab/background_migration/remove_restricted_todos.rb b/lib/gitlab/background_migration/remove_restricted_todos.rb
new file mode 100644
index 00000000000..68f3fa62170
--- /dev/null
+++ b/lib/gitlab/background_migration/remove_restricted_todos.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class RemoveRestrictedTodos
+ PRIVATE_FEATURE = 10
+ PRIVATE_PROJECT = 0
+
+ class Project < ActiveRecord::Base
+ self.table_name = 'projects'
+ end
+
+ class ProjectAuthorization < ActiveRecord::Base
+ self.table_name = 'project_authorizations'
+ end
+
+ class ProjectFeature < ActiveRecord::Base
+ self.table_name = 'project_features'
+ end
+
+ class Todo < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'todos'
+ end
+
+ class Issue < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'issues'
+ end
+
+ def perform(start_id, stop_id)
+ projects = Project.where('EXISTS (SELECT 1 FROM todos WHERE todos.project_id = projects.id)')
+ .where(id: start_id..stop_id)
+
+ projects.each do |project|
+ remove_confidential_issue_todos(project.id)
+
+ if project.visibility_level == PRIVATE_PROJECT
+ remove_non_members_todos(project.id)
+ else
+ remove_restricted_features_todos(project.id)
+ end
+ end
+ end
+
+ private
+
+ def remove_non_members_todos(project_id)
+ Todo.where(project_id: project_id)
+ .where('user_id NOT IN (?)', authorized_users(project_id))
+ .each_batch(of: 5000) do |batch|
+ batch.delete_all
+ end
+ end
+
+ def remove_confidential_issue_todos(project_id)
+ # min access level to access a confidential issue is reporter
+ min_reporters = authorized_users(project_id)
+ .select(:user_id)
+ .where('access_level >= ?', 20)
+
+ confidential_issues = Issue.select(:id, :author_id).where(confidential: true, project_id: project_id)
+ confidential_issues.each_batch(of: 100) do |batch|
+ batch.each do |issue|
+ assigned_users = IssueAssignee.select(:user_id).where(issue_id: issue.id)
+
+ todos = Todo.where(target_type: 'Issue', target_id: issue.id)
+ .where('user_id NOT IN (?)', min_reporters)
+ .where('user_id NOT IN (?)', assigned_users)
+ todos = todos.where('user_id != ?', issue.author_id) if issue.author_id
+
+ todos.delete_all
+ end
+ end
+ end
+
+ def remove_restricted_features_todos(project_id)
+ ProjectFeature.where(project_id: project_id).each do |project_features|
+ target_types = []
+ target_types << 'Issue' if private?(project_features.issues_access_level)
+ target_types << 'MergeRequest' if private?(project_features.merge_requests_access_level)
+ target_types << 'Commit' if private?(project_features.repository_access_level)
+
+ next if target_types.empty?
+
+ Todo.where(project_id: project_id)
+ .where('user_id NOT IN (?)', authorized_users(project_id))
+ .where(target_type: target_types)
+ .delete_all
+ end
+ end
+
+ def private?(feature_level)
+ feature_level == PRIVATE_FEATURE
+ end
+
+ def authorized_users(project_id)
+ ProjectAuthorization.select(:user_id).where(project_id: project_id)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index f3999e690fa..fa0186c854c 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -188,7 +188,8 @@ module Gitlab
end
def import_inline_comments(inline_comments, pull_request, merge_request)
- line_code_map = {}
+ position_map = {}
+ discussion_map = {}
children, parents = inline_comments.partition(&:has_parent?)
@@ -196,22 +197,28 @@ module Gitlab
# relationships. We assume that the child can appear in any order in
# the JSON.
parents.each do |comment|
- line_code_map[comment.iid] = generate_line_code(comment)
+ position_map[comment.iid] = build_position(merge_request, comment)
end
children.each do |comment|
- line_code_map[comment.iid] = line_code_map.fetch(comment.parent_id, nil)
+ position_map[comment.iid] = position_map.fetch(comment.parent_id, nil)
end
inline_comments.each do |comment|
begin
attributes = pull_request_comment_attributes(comment)
+ attributes[:discussion_id] = discussion_map[comment.parent_id] if comment.has_parent?
+
attributes.merge!(
- position: build_position(merge_request, comment),
- line_code: line_code_map.fetch(comment.iid),
+ position: position_map[comment.iid],
type: 'DiffNote')
- merge_request.notes.create!(attributes)
+ note = merge_request.notes.create!(attributes)
+
+ # We can't store a discussion ID until a note is created, so if
+ # replies are created before the parent the discussion ID won't be
+ # linked properly.
+ discussion_map[comment.iid] = note.discussion_id
rescue StandardError => e
errors << { type: :pull_request, iid: comment.iid, errors: e.message }
end
@@ -240,10 +247,6 @@ module Gitlab
end
end
- def generate_line_code(pr_comment)
- Gitlab::Git.diff_line_code(pr_comment.file_path, pr_comment.new_pos, pr_comment.old_pos)
- end
-
def pull_request_comment_attributes(comment)
{
project: project,
diff --git a/lib/gitlab/cleanup/remote_uploads.rb b/lib/gitlab/cleanup/remote_uploads.rb
new file mode 100644
index 00000000000..45a5aea4fcd
--- /dev/null
+++ b/lib/gitlab/cleanup/remote_uploads.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+module Gitlab
+ module Cleanup
+ class RemoteUploads
+ attr_reader :logger
+
+ BATCH_SIZE = 100
+
+ def initialize(logger: nil)
+ @logger = logger || Rails.logger
+ end
+
+ def run!(dry_run: false)
+ unless configuration.enabled
+ logger.warn "Object storage not enabled. Exit".color(:yellow)
+
+ return
+ end
+
+ logger.info "Looking for orphaned remote uploads to remove#{'. Dry run' if dry_run}..."
+
+ each_orphan_file do |file|
+ info = if dry_run
+ "Can be moved to lost and found: #{file.key}"
+ else
+ new_path = move_to_lost_and_found(file)
+ "Moved to lost and found: #{file.key} -> #{new_path}"
+ end
+
+ logger.info(info)
+ end
+ end
+
+ private
+
+ def each_orphan_file
+ # we want to skip files already moved to lost_and_found directory
+ lost_dir_match = "^#{lost_and_found_dir}\/"
+
+ remote_directory.files.each_slice(BATCH_SIZE) do |remote_files|
+ remote_files.reject! { |file| file.key.match(/#{lost_dir_match}/) }
+ file_paths = remote_files.map(&:key)
+ tracked_paths = Upload
+ .where(store: ObjectStorage::Store::REMOTE, path: file_paths)
+ .pluck(:path)
+
+ remote_files.reject! { |file| tracked_paths.include?(file.key) }
+ remote_files.each do |file|
+ yield file
+ end
+ end
+ end
+
+ def move_to_lost_and_found(file)
+ new_path = "#{lost_and_found_dir}/#{file.key}"
+
+ file.copy(configuration['remote_directory'], new_path)
+ file.destroy
+
+ new_path
+ end
+
+ def lost_and_found_dir
+ 'lost_and_found'
+ end
+
+ def remote_directory
+ connection.directories.get(configuration['remote_directory'])
+ end
+
+ def connection
+ ::Fog::Storage.new(configuration['connection'].symbolize_keys)
+ end
+
+ def configuration
+ Gitlab.config.uploads.object_store
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index de189ac6dfc..3e11355435b 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -382,7 +382,7 @@ module Gitlab
end
def new_blobs(newrev)
- return [] if newrev == ::Gitlab::Git::BLANK_SHA
+ return [] if newrev.blank? || newrev == ::Gitlab::Git::BLANK_SHA
strong_memoize("new_blobs_#{newrev}") do
wrapped_gitaly_errors do
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index a2feb074b1d..c8a8863443e 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -116,6 +116,16 @@ namespace :gitlab do
end
end
+ desc 'GitLab | Cleanup | Clean orphan remote upload files that do not exist in the db'
+ task remote_upload_files: :environment do
+ cleaner = Gitlab::Cleanup::RemoteUploads.new(logger: logger)
+ cleaner.run!(dry_run: dry_run?)
+
+ if dry_run?
+ logger.info "To cleanup these files run this command with DRY_RUN=false".color(:yellow)
+ end
+ end
+
def remove?
ENV['REMOVE'] == 'true'
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6c89186beaa..bec60cf592a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1930,6 +1930,9 @@ msgstr ""
msgid "Cron syntax"
msgstr ""
+msgid "Current Branch"
+msgstr ""
+
msgid "CurrentUser|Profile"
msgstr ""
@@ -2409,6 +2412,9 @@ msgstr ""
msgid "Error loading branch data. Please try again."
msgstr ""
+msgid "Error loading branches."
+msgstr ""
+
msgid "Error loading last commit."
msgstr ""
@@ -3605,6 +3611,9 @@ msgstr ""
msgid "No assignee"
msgstr ""
+msgid "No branches found"
+msgstr ""
+
msgid "No changes"
msgstr ""
@@ -4109,9 +4118,15 @@ msgstr ""
msgid "Profiles|Add key"
msgstr ""
+msgid "Profiles|Add status emoji"
+msgstr ""
+
msgid "Profiles|Change username"
msgstr ""
+msgid "Profiles|Clear status"
+msgstr ""
+
msgid "Profiles|Current path: %{path}"
msgstr ""
@@ -4139,7 +4154,7 @@ msgstr ""
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
-msgid "Profiles|This emoji and message will appear on your profile and throughout the interface. The message can contain emoji codes, too."
+msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
msgid "Profiles|Type your %{confirmationValue} to confirm:"
@@ -4157,6 +4172,9 @@ msgstr ""
msgid "Profiles|Username successfully changed"
msgstr ""
+msgid "Profiles|What's your status?"
+msgstr ""
+
msgid "Profiles|You don't have access to delete this user."
msgstr ""
@@ -4166,6 +4184,9 @@ msgstr ""
msgid "Profiles|Your account is currently an owner in these groups:"
msgstr ""
+msgid "Profiles|Your status"
+msgstr ""
+
msgid "Profiles|e.g. My MacBook key"
msgstr ""
@@ -5754,7 +5775,7 @@ msgstr ""
msgid "Users"
msgstr ""
-msgid "User|Current Status"
+msgid "User|Current status"
msgstr ""
msgid "Variables"
@@ -6045,9 +6066,6 @@ msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
-msgid "You do not have any assigned merge requests"
-msgstr ""
-
msgid "You don't have any applications"
msgstr ""
@@ -6057,9 +6075,6 @@ msgstr ""
msgid "You have no permissions"
msgstr ""
-msgid "You have not created any merge requests"
-msgstr ""
-
msgid "You have reached your project limit"
msgstr ""
diff --git a/public/404.html b/public/404.html
index 08f328da542..68b4ab0bb34 100644
--- a/public/404.html
+++ b/public/404.html
@@ -66,8 +66,10 @@
</head>
<body>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+ <a href="/">
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
alt="GitLab Logo" />
+ </a>
<h1>
404
</h1>
diff --git a/public/422.html b/public/422.html
index a67dcd02200..a931e923efb 100644
--- a/public/422.html
+++ b/public/422.html
@@ -66,8 +66,10 @@
</head>
<body>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+ <a href="/">
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
alt="GitLab Logo" />
+ </a>
<h1>
422
</h1>
diff --git a/public/500.html b/public/500.html
index 7091d14dfc4..df7b22dc9ef 100644
--- a/public/500.html
+++ b/public/500.html
@@ -66,8 +66,10 @@
</head>
<body>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+ <a href="/">
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
alt="GitLab Logo" />
+ </a>
<h1>
500
</h1>
diff --git a/public/502.html b/public/502.html
index 82afd273248..77835767fa6 100644
--- a/public/502.html
+++ b/public/502.html
@@ -66,8 +66,10 @@
</head>
<body>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+ <a href="/">
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
alt="GitLab Logo" />
+ </a>
<h1>
502
</h1>
diff --git a/public/503.html b/public/503.html
index f1486bc3e84..ee2da9b1313 100644
--- a/public/503.html
+++ b/public/503.html
@@ -66,8 +66,10 @@
</head>
<body>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
+ <a href="/">
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo="
alt="GitLab Logo" />
+ </a>
<h1>
503
</h1>
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 9e812fa7c74..1fb569b0f29 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -10,7 +10,7 @@ module QA
view 'app/views/projects/_new_project_fields.html.haml' do
element :project_namespace_select
- element :project_namespace_field, /select :namespace_id.*class: 'select2/
+ element :project_namespace_field, 'namespaces_options'
element :project_path, 'text_field :path'
element :project_description, 'text_area :description'
element :project_create_button, "submit 'Create project'"
diff --git a/rubocop/cop/migration/add_reference.rb b/rubocop/cop/migration/add_reference.rb
new file mode 100644
index 00000000000..4b67270c97a
--- /dev/null
+++ b/rubocop/cop/migration/add_reference.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that checks if a foreign key constraint is added and require a index for it
+ class AddReference < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ MSG = '`add_reference` requires `index: true`'
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ name = node.children[1]
+
+ return unless name == :add_reference
+
+ opts = node.children.last
+
+ add_offense(node, location: :selector) unless opts && opts.type == :hash
+
+ index_present = false
+
+ opts.each_node(:pair) do |pair|
+ index_present ||= index_enabled?(pair)
+ end
+
+ add_offense(node, location: :selector) unless index_present
+ end
+
+ private
+
+ def index_enabled?(pair)
+ hash_key_type(pair) == :sym && hash_key_name(pair) == :index && pair.children[1].true_type?
+ end
+
+ def hash_key_type(pair)
+ pair.children[0].type
+ end
+
+ def hash_key_name(pair)
+ pair.children[0].children[0]
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index aa7ae601f75..a427208cdab 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -11,6 +11,7 @@ require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
+require_relative 'cop/migration/add_reference'
require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime'
require_relative 'cop/migration/hash_index'
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index bad7a28556c..421ab006792 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -56,6 +56,57 @@ describe ApplicationController do
end
end
+ describe '#add_gon_variables' do
+ before do
+ Gon.clear
+ sign_in user
+ end
+
+ let(:json_response) { JSON.parse(response.body) }
+
+ controller(described_class) do
+ def index
+ render json: Gon.all_variables
+ end
+ end
+
+ shared_examples 'setting gon variables' do
+ it 'sets gon variables' do
+ get :index, format: format
+
+ expect(json_response.size).not_to be_zero
+ end
+ end
+
+ shared_examples 'not setting gon variables' do
+ it 'does not set gon variables' do
+ get :index, format: format
+
+ expect(json_response.size).to be_zero
+ end
+ end
+
+ context 'with html format' do
+ let(:format) { :html }
+
+ it_behaves_like 'setting gon variables'
+
+ context 'for peek requests' do
+ before do
+ request.path = '/-/peek'
+ end
+
+ it_behaves_like 'not setting gon variables'
+ end
+ end
+
+ context 'with json format' do
+ let(:format) { :json }
+
+ it_behaves_like 'not setting gon variables'
+ end
+ end
+
describe "#authenticate_user_from_personal_access_token!" do
before do
stub_authentication_activity_metrics(debug: false)
diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb
index d2f133f972a..73bf169085f 100644
--- a/spec/controllers/projects/deploy_keys_controller_spec.rb
+++ b/spec/controllers/projects/deploy_keys_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::DeployKeysController do
it 'redirects to blob' do
get :index, params
- expect(response).to redirect_to(namespace_project_settings_repository_path(params))
+ expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-deploy-keys-settings'))
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 375018e2229..d9bb3981539 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -597,6 +597,12 @@ describe Projects::MergeRequestsController do
context 'when comparison is being processed' do
let(:comparison_status) { { status: :parsing } }
+ it 'sends polling interval' do
+ expect(Gitlab::PollingInterval).to receive(:set_header)
+
+ subject
+ end
+
it 'returns 204 HTTP status' do
subject
@@ -607,6 +613,12 @@ describe Projects::MergeRequestsController do
context 'when comparison is done' do
let(:comparison_status) { { status: :parsed, data: { summary: 1 } } }
+ it 'does not send polling interval' do
+ expect(Gitlab::PollingInterval).not_to receive(:set_header)
+
+ subject
+ end
+
it 'returns 200 HTTP status' do
subject
@@ -618,6 +630,12 @@ describe Projects::MergeRequestsController do
context 'when user created corrupted test reports' do
let(:comparison_status) { { status: :error, status_reason: 'Failed to parse test reports' } }
+ it 'does not send polling interval' do
+ expect(Gitlab::PollingInterval).not_to receive(:set_header)
+
+ subject
+ end
+
it 'returns 400 HTTP status' do
subject
@@ -629,6 +647,12 @@ describe Projects::MergeRequestsController do
context 'when something went wrong on our system' do
let(:comparison_status) { {} }
+ it 'does not send polling interval' do
+ expect(Gitlab::PollingInterval).not_to receive(:set_header)
+
+ subject
+ end
+
it 'returns 500 HTTP status' do
subject
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 6c2d1c7e92b..3190f1ce9d4 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -42,16 +42,45 @@ describe Projects::MilestonesController do
describe "#index" do
context "as html" do
- before do
- get :index, namespace_id: project.namespace.id, project_id: project.id
+ def render_index(project:, page:)
+ get :index, namespace_id: project.namespace.id,
+ project_id: project.id,
+ page: page
end
it "queries only projects milestones" do
+ render_index project: project, page: 1
+
milestones = assigns(:milestones)
expect(milestones.count).to eq(1)
expect(milestones.where(project_id: nil)).to be_empty
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)
+
+ render_index project: project, page: 1
+ page_1_milestones = assigns(:milestones)
+ expect(page_1_milestones.size).to eq(2)
+
+ render_index project: project, page: 2
+ page_2_milestones = assigns(:milestones)
+ expect(page_2_milestones.size).to eq(2)
+
+ render_index project: project, page: 3
+ page_3_milestones = assigns(:milestones)
+ expect(page_3_milestones.size).to eq(2)
+
+ rendered_milestone_ids =
+ page_1_milestones.pluck(:id) +
+ page_2_milestones.pluck(:id) +
+ page_3_milestones.pluck(:id)
+
+ expect(rendered_milestone_ids)
+ .to match_array(project.milestones.pluck(:id))
+ end
end
context "as json" do
diff --git a/spec/controllers/projects/mirrors_controller_spec.rb b/spec/controllers/projects/mirrors_controller_spec.rb
index 5d64f362252..6114eef7003 100644
--- a/spec/controllers/projects/mirrors_controller_spec.rb
+++ b/spec/controllers/projects/mirrors_controller_spec.rb
@@ -36,7 +36,7 @@ describe Projects::MirrorsController do
it 'processes a successful update' do
do_put(project, remote_mirrors_attributes: remote_mirror_attributes)
- expect(response).to redirect_to(project_settings_repository_path(project))
+ expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-push-remote-settings'))
expect(flash[:notice]).to match(/successfully updated/)
end
@@ -53,7 +53,7 @@ describe Projects::MirrorsController do
it 'processes an unsuccessful update' do
do_put(project, remote_mirrors_attributes: remote_mirror_attributes)
- expect(response).to redirect_to(project_settings_repository_path(project))
+ expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-push-remote-settings'))
expect(flash[:alert]).to match(/Only allowed protocols are/)
end
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
index f95632e7187..019e4420212 100644
--- a/spec/factories/milestones.rb
+++ b/spec/factories/milestones.rb
@@ -18,6 +18,11 @@ FactoryBot.define do
state "closed"
end
+ trait :with_dates do
+ start_date { Date.new(2000, 1, 1) }
+ due_date { Date.new(2000, 1, 30) }
+ end
+
after(:build, :stub) do |milestone, evaluator|
if evaluator.group
milestone.group = evaluator.group
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 6c194c9a646..1db6c75b85b 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -68,10 +68,12 @@ describe "Admin::Users" do
end
describe "GET /admin/users/new" do
+ let(:user_username) { 'bang' }
+
before do
visit new_admin_user_path
fill_in "user_name", with: "Big Bang"
- fill_in "user_username", with: "bang"
+ fill_in "user_username", with: user_username
fill_in "user_email", with: "bigbang@mail.com"
end
@@ -112,6 +114,17 @@ describe "Admin::Users" do
expect(email.text_part.body).to have_content(user.email)
expect(email.text_part.body).to have_content('password')
end
+
+ context 'username contains spaces' do
+ let(:user_username) { 'Bing bang' }
+
+ it "doesn't create the user and shows an error message" do
+ expect { click_button "Create user" }.to change {User.count}.by(0)
+
+ expect(page).to have_content('The form contains the following error')
+ expect(page).to have_content('Username can contain only letters, digits')
+ end
+ end
end
describe "GET /admin/users/:id" do
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 b6b3844f2ae..b285cd7a7ac 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
describe 'Merge request > User sees merge widget', :js do
include ProjectForksHelper
+ include TestReportsHelper
let(:project) { create(:project, :repository) }
let(:project_only_mwps) { create(:project, :repository, only_allow_merge_if_pipeline_succeeds: true) }
@@ -325,4 +326,229 @@ describe 'Merge request > User sees merge widget', :js do
expect(page).to have_content('This merge request is in the process of being merged')
end
end
+
+ context 'when merge request has test reports' do
+ let!(:head_pipeline) do
+ create(:ci_pipeline,
+ :success,
+ project: project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+ end
+
+ let!(:build) { create(:ci_build, :success, pipeline: head_pipeline, project: project) }
+
+ before do
+ merge_request.update!(head_pipeline_id: head_pipeline.id)
+ end
+
+ context 'when result has not been parsed yet' do
+ let!(:job_artifact) { create(:ci_job_artifact, :junit, job: build, project: project) }
+
+ before do
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows parsing status' do
+ expect(page).to have_content('Test summary results are being parsed')
+ end
+ end
+
+ context 'when result has already been parsed' do
+ context 'when JUnit xml is correctly formatted' do
+ let!(:job_artifact) { create(:ci_job_artifact, :junit, job: build, project: project) }
+
+ before do
+ allow_any_instance_of(MergeRequest).to receive(:compare_test_reports).and_return(compared_data)
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows parsed results' do
+ expect(page).to have_content('Test summary contained')
+ end
+ end
+
+ context 'when JUnit xml is corrupted' do
+ let!(:job_artifact) { create(:ci_job_artifact, :junit_with_corrupted_data, job: build, project: project) }
+
+ before do
+ allow_any_instance_of(MergeRequest).to receive(:compare_test_reports).and_return(compared_data)
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows the error state' do
+ expect(page).to have_content('Test summary failed loading results')
+ end
+ end
+
+ def compared_data
+ Ci::CompareTestReportsService.new(project).execute(nil, head_pipeline)
+ end
+ end
+
+ context 'when test reports have been parsed correctly' do
+ let(:serialized_data) do
+ {
+ status: :parsed,
+ data: TestReportsComparerSerializer
+ .new(project: project)
+ .represent(comparer)
+ }
+ end
+
+ before do
+ allow_any_instance_of(MergeRequest)
+ .to receive(:has_test_reports?).and_return(true)
+ allow_any_instance_of(MergeRequest)
+ .to receive(:compare_test_reports).and_return(serialized_data)
+
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ context 'when a new failures exists' do
+ let(:base_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ reports.get_suite('junit').add_test_case(create_test_case_java_success)
+ end
+ end
+
+ let(:head_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ reports.get_suite('junit').add_test_case(create_test_case_java_failed)
+ end
+ end
+
+ it 'shows test reports summary which includes the new failure' do
+ within(".mr-section-container") do
+ click_button 'Expand'
+
+ expect(page).to have_content('Test summary contained 1 failed test result out of 2 total tests')
+ within(".js-report-section-container") do
+ expect(page).to have_content('rspec found no changed test results out of 1 total test')
+ expect(page).to have_content('junit found 1 failed test result out of 1 total test')
+ expect(page).to have_content('New')
+ expect(page).to have_content('subtractTest')
+ end
+ end
+ end
+
+ context 'when user clicks the new failure' do
+ it 'shows the test report detail' do
+ within(".mr-section-container") do
+ click_button 'Expand'
+
+ within(".js-report-section-container") do
+ click_button 'subtractTest'
+
+ expect(page).to have_content('6.66')
+ expect(page).to have_content(sample_java_failed_message)
+ end
+ end
+ end
+ end
+ end
+
+ context 'when an existing failure exists' do
+ let(:base_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_failed)
+ reports.get_suite('junit').add_test_case(create_test_case_java_success)
+ end
+ end
+
+ let(:head_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_failed)
+ reports.get_suite('junit').add_test_case(create_test_case_java_success)
+ end
+ end
+
+ it 'shows test reports summary which includes the existing failure' do
+ within(".mr-section-container") do
+ click_button 'Expand'
+
+ expect(page).to have_content('Test summary contained 1 failed test result out of 2 total tests')
+ within(".js-report-section-container") do
+ expect(page).to have_content('rspec found 1 failed test result out of 1 total test')
+ expect(page).to have_content('junit found no changed test results out of 1 total test')
+ expect(page).not_to have_content('New')
+ expect(page).to have_content('Test#sum when a is 2 and b is 2 returns summary')
+ end
+ end
+ end
+
+ context 'when user clicks the existing failure' do
+ it 'shows test report detail of it' do
+ within(".mr-section-container") do
+ click_button 'Expand'
+
+ within(".js-report-section-container") do
+ click_button 'Test#sum when a is 2 and b is 2 returns summary'
+
+ expect(page).to have_content('2.22')
+ expect(page).to have_content(sample_rspec_failed_message)
+ end
+ end
+ end
+ end
+ end
+
+ context 'when a resolved failure exists' do
+ let(:base_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ reports.get_suite('junit').add_test_case(create_test_case_java_failed)
+ end
+ end
+
+ let(:head_reports) do
+ Gitlab::Ci::Reports::TestReports.new.tap do |reports|
+ reports.get_suite('rspec').add_test_case(create_test_case_rspec_success)
+ reports.get_suite('junit').add_test_case(create_test_case_java_resolved)
+ end
+ end
+
+ let(:create_test_case_java_resolved) do
+ create_test_case_java_failed.tap do |test_case|
+ test_case.instance_variable_set("@status", Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
+ end
+ end
+
+ it 'shows test reports summary which includes the resolved failure' do
+ within(".mr-section-container") do
+ click_button 'Expand'
+
+ expect(page).to have_content('Test summary contained 1 fixed test result out of 2 total tests')
+ within(".js-report-section-container") do
+ expect(page).to have_content('rspec found no changed test results out of 1 total test')
+ expect(page).to have_content('junit found 1 fixed test result out of 1 total test')
+ expect(page).to have_content('subtractTest')
+ end
+ end
+ end
+
+ context 'when user clicks the resolved failure' do
+ it 'shows test report detail of it' do
+ within(".mr-section-container") do
+ click_button 'Expand'
+
+ within(".js-report-section-container") do
+ click_button 'subtractTest'
+
+ expect(page).to have_content('6.66')
+ end
+ end
+ end
+ end
+ end
+
+ def comparer
+ Gitlab::Ci::Reports::TestReportsComparer.new(base_reports, head_reports)
+ end
+ end
+ end
end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index 96bbe6f93f1..9e60b4995bd 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -8,6 +8,10 @@ describe 'User edit profile' do
visit(profile_path)
end
+ def submit_settings
+ click_button 'Update profile settings'
+ end
+
it 'changes user profile' do
fill_in 'user_skype', with: 'testskype'
fill_in 'user_linkedin', with: 'testlinkedin'
@@ -16,7 +20,7 @@ describe 'User edit profile' do
fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab'
fill_in 'user_organization', with: 'GitLab'
- click_button 'Update profile settings'
+ submit_settings
expect(user.reload).to have_attributes(
skype: 'testskype',
@@ -34,7 +38,7 @@ describe 'User edit profile' do
context 'user avatar' do
before do
attach_file(:user_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif'))
- click_button 'Update profile settings'
+ submit_settings
end
it 'changes user avatar' do
@@ -56,30 +60,75 @@ describe 'User edit profile' do
end
end
- context 'user status' do
- it 'hides user status when the feature is disabled' do
- stub_feature_flags(user_status_form: false)
+ context 'user status', :js do
+ def select_emoji(emoji_name)
+ toggle_button = find('.js-toggle-emoji-menu')
+ toggle_button.click
+ emoji_button = find(%Q{.js-status-emoji-menu .js-emoji-btn gl-emoji[data-name="#{emoji_name}"]})
+ emoji_button.click
+ end
+ it 'shows the user status form' do
visit(profile_path)
- expect(page).not_to have_content('Current Status')
+ expect(page).to have_content('Current status')
end
- it 'shows the status form when the feature is enabled' do
- stub_feature_flags(user_status_form: true)
+ it 'adds emoji to user status' do
+ emoji = 'biohazard'
+ visit(profile_path)
+ select_emoji(emoji)
+ submit_settings
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(emoji)
+ end
+ end
+
+ it 'adds message to user status' do
+ message = 'I have something to say'
visit(profile_path)
+ fill_in 'js-status-message-field', with: message
+ submit_settings
+
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji('speech_balloon')
+ expect(page).to have_content message
+ end
+ end
- expect(page).to have_content('Current Status')
+ it 'adds message and emoji to user status' do
+ emoji = 'tanabata_tree'
+ message = 'Playing outside'
+ visit(profile_path)
+ select_emoji(emoji)
+ fill_in 'js-status-message-field', with: message
+ submit_settings
+
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(emoji)
+ expect(page).to have_content message
+ end
end
- it 'shows the status form when the feature is enabled by setting a cookie', :js do
- stub_feature_flags(user_status_form: false)
- set_cookie('feature_user_status_form', 'true')
+ it 'clears the user status' do
+ user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread')
+
+ visit user_path(user)
+ within('.cover-status') do
+ expect(page).to have_emoji(user_status.emoji)
+ expect(page).to have_content user_status.message
+ end
visit(profile_path)
+ click_button 'js-clear-user-status-button'
+ submit_settings
- expect(page).to have_content('Current Status')
+ visit user_path(user)
+ expect(page).not_to have_selector '.cover-status'
end
end
end
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index d3aa4912099..9e58280b868 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -22,7 +22,7 @@ describe 'Multi-file editor new directory', :js do
end
it 'creates directory in current directory' do
- all('.ide-tree-header button').last.click
+ all('.ide-tree-actions button').last.click
page.within('.modal') do
find('.form-control').set('folder name')
@@ -30,7 +30,7 @@ describe 'Multi-file editor new directory', :js do
click_button('Create directory')
end
- first('.ide-tree-header button').click
+ first('.ide-tree-actions button').click
page.within('.modal-dialog') do
find('.form-control').set('file name')
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index f836783cbff..a04d3566a7e 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -22,7 +22,7 @@ describe 'Multi-file editor new file', :js do
end
it 'creates file in current directory' do
- first('.ide-tree-header button').click
+ first('.ide-tree-actions button').click
page.within('.modal') do
find('.form-control').set('file name')
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 5856bccb5b8..55ee87163f9 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -5,12 +5,67 @@ describe AvatarsHelper do
let(:user) { create(:user) }
- describe '#project_icon' do
- it 'returns an url for the avatar' do
- project = create(:project, :public, avatar: File.open(uploaded_image_temp_path))
+ describe '#project_icon & #group_icon' do
+ shared_examples 'resource with a default avatar' do |source_type|
+ it 'returns a default avatar div' do
+ expect(public_send("#{source_type}_icon", *helper_args))
+ .to match(%r{<div class="identicon bg\d+">F</div>})
+ end
+ end
+
+ shared_examples 'resource with a custom avatar' do |source_type|
+ it 'returns a custom avatar image' do
+ expect(public_send("#{source_type}_icon", *helper_args))
+ .to eq "<img src=\"#{resource.avatar.url}\" alt=\"Banana sample\" />"
+ end
+ end
+
+ context 'when providing a project' do
+ it_behaves_like 'resource with a default avatar', 'project' do
+ let(:resource) { create(:project, name: 'foo') }
+ let(:helper_args) { [resource] }
+ end
+
+ it_behaves_like 'resource with a custom avatar', 'project' do
+ let(:resource) { create(:project, :public, avatar: File.open(uploaded_image_temp_path)) }
+ let(:helper_args) { [resource] }
+ end
+ end
+
+ context 'when providing a project path' do
+ it_behaves_like 'resource with a default avatar', 'project' do
+ let(:resource) { create(:project, name: 'foo') }
+ let(:helper_args) { [resource.full_path] }
+ end
- expect(helper.project_icon(project.full_path).to_s)
- .to eq "<img data-src=\"#{project.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
+ it_behaves_like 'resource with a custom avatar', 'project' do
+ let(:resource) { create(:project, :public, avatar: File.open(uploaded_image_temp_path)) }
+ let(:helper_args) { [resource.full_path] }
+ end
+ end
+
+ context 'when providing a group' do
+ it_behaves_like 'resource with a default avatar', 'group' do
+ let(:resource) { create(:group, name: 'foo') }
+ let(:helper_args) { [resource] }
+ end
+
+ it_behaves_like 'resource with a custom avatar', 'group' do
+ let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
+ let(:helper_args) { [resource] }
+ end
+ end
+
+ context 'when providing a group path' do
+ it_behaves_like 'resource with a default avatar', 'group' do
+ let(:resource) { create(:group, name: 'foo') }
+ let(:helper_args) { [resource.full_path] }
+ end
+
+ it_behaves_like 'resource with a custom avatar', 'group' do
+ let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
+ let(:helper_args) { [resource.full_path] }
+ end
end
end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 115807f954b..540a8674ec2 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -3,19 +3,6 @@ require 'spec_helper'
describe GroupsHelper do
include ApplicationHelper
- describe 'group_icon' do
- it 'returns an url for the avatar' do
- avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif')
-
- group = create(:group)
- group.avatar = fixture_file_upload(avatar_file_path)
- group.save!
-
- expect(helper.group_icon(group).to_s)
- .to eq "<img data-src=\"#{group.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
- end
- end
-
describe 'group_icon_url' do
it 'returns an url for the avatar' do
avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif')
diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js
index 839b8a06b48..d0e0b214509 100644
--- a/spec/javascripts/clusters/clusters_bundle_spec.js
+++ b/spec/javascripts/clusters/clusters_bundle_spec.js
@@ -1,11 +1,9 @@
import Clusters from '~/clusters/clusters_bundle';
import {
- APPLICATION_INSTALLABLE,
- APPLICATION_INSTALLING,
- APPLICATION_INSTALLED,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
+ APPLICATION_STATUS,
} from '~/clusters/constants';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
@@ -84,7 +82,7 @@ describe('Clusters', () => {
it('does not show alert when things transition from initial null state to something', () => {
cluster.checkForNewInstalls(INITIAL_APP_MAP, {
...INITIAL_APP_MAP,
- helm: { status: APPLICATION_INSTALLABLE, title: 'Helm Tiller' },
+ helm: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Helm Tiller' },
});
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
@@ -94,10 +92,10 @@ describe('Clusters', () => {
it('shows an alert when something gets newly installed', () => {
cluster.checkForNewInstalls({
...INITIAL_APP_MAP,
- helm: { status: APPLICATION_INSTALLING, title: 'Helm Tiller' },
+ helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
}, {
...INITIAL_APP_MAP,
- helm: { status: APPLICATION_INSTALLED, title: 'Helm Tiller' },
+ helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' },
});
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
@@ -108,12 +106,12 @@ describe('Clusters', () => {
it('shows an alert when multiple things gets newly installed', () => {
cluster.checkForNewInstalls({
...INITIAL_APP_MAP,
- helm: { status: APPLICATION_INSTALLING, title: 'Helm Tiller' },
- ingress: { status: APPLICATION_INSTALLABLE, title: 'Ingress' },
+ helm: { status: APPLICATION_STATUS.INSTALLING, title: 'Helm Tiller' },
+ ingress: { status: APPLICATION_STATUS.INSTALLABLE, title: 'Ingress' },
}, {
...INITIAL_APP_MAP,
- helm: { status: APPLICATION_INSTALLED, title: 'Helm Tiller' },
- ingress: { status: APPLICATION_INSTALLED, title: 'Ingress' },
+ helm: { status: APPLICATION_STATUS.INSTALLED, title: 'Helm Tiller' },
+ ingress: { status: APPLICATION_STATUS.INSTALLED, title: 'Ingress' },
});
const flashMessage = document.querySelector('.js-cluster-application-notice .flash-text');
diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/javascripts/clusters/components/application_row_spec.js
index c83cbe90a57..9da5c248371 100644
--- a/spec/javascripts/clusters/components/application_row_spec.js
+++ b/spec/javascripts/clusters/components/application_row_spec.js
@@ -1,12 +1,7 @@
import Vue from 'vue';
import eventHub from '~/clusters/event_hub';
import {
- APPLICATION_NOT_INSTALLABLE,
- APPLICATION_SCHEDULED,
- APPLICATION_INSTALLABLE,
- APPLICATION_INSTALLING,
- APPLICATION_INSTALLED,
- APPLICATION_ERROR,
+ APPLICATION_STATUS,
REQUEST_LOADING,
REQUEST_SUCCESS,
REQUEST_FAILURE,
@@ -62,10 +57,10 @@ describe('Application Row', () => {
expect(vm.installButtonLabel).toBeUndefined();
});
- it('has disabled "Install" when APPLICATION_NOT_INSTALLABLE', () => {
+ it('has disabled "Install" when APPLICATION_STATUS.NOT_INSTALLABLE', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_NOT_INSTALLABLE,
+ status: APPLICATION_STATUS.NOT_INSTALLABLE,
});
expect(vm.installButtonLabel).toEqual('Install');
@@ -73,10 +68,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
- it('has enabled "Install" when APPLICATION_INSTALLABLE', () => {
+ it('has enabled "Install" when APPLICATION_STATUS.INSTALLABLE', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLABLE,
+ status: APPLICATION_STATUS.INSTALLABLE,
});
expect(vm.installButtonLabel).toEqual('Install');
@@ -84,10 +79,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(false);
});
- it('has loading "Installing" when APPLICATION_SCHEDULED', () => {
+ it('has loading "Installing" when APPLICATION_STATUS.SCHEDULED', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_SCHEDULED,
+ status: APPLICATION_STATUS.SCHEDULED,
});
expect(vm.installButtonLabel).toEqual('Installing');
@@ -95,10 +90,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
- it('has loading "Installing" when APPLICATION_INSTALLING', () => {
+ it('has loading "Installing" when APPLICATION_STATUS.INSTALLING', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLING,
+ status: APPLICATION_STATUS.INSTALLING,
});
expect(vm.installButtonLabel).toEqual('Installing');
@@ -106,10 +101,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
- it('has disabled "Installed" when APPLICATION_INSTALLED', () => {
+ it('has disabled "Installed" when APPLICATION_STATUS.INSTALLED', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLED,
+ status: APPLICATION_STATUS.INSTALLED,
});
expect(vm.installButtonLabel).toEqual('Installed');
@@ -117,10 +112,10 @@ describe('Application Row', () => {
expect(vm.installButtonDisabled).toEqual(true);
});
- it('has enabled "Install" when APPLICATION_ERROR', () => {
+ it('has enabled "Install" when APPLICATION_STATUS.ERROR', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_ERROR,
+ status: APPLICATION_STATUS.ERROR,
});
expect(vm.installButtonLabel).toEqual('Install');
@@ -131,7 +126,7 @@ describe('Application Row', () => {
it('has loading "Install" when REQUEST_LOADING', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLABLE,
+ status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_LOADING,
});
@@ -143,7 +138,7 @@ describe('Application Row', () => {
it('has disabled "Install" when REQUEST_SUCCESS', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLABLE,
+ status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_SUCCESS,
});
@@ -155,7 +150,7 @@ describe('Application Row', () => {
it('has enabled "Install" when REQUEST_FAILURE (so you can try installing again)', () => {
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLABLE,
+ status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_FAILURE,
});
@@ -168,7 +163,7 @@ describe('Application Row', () => {
spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLABLE,
+ status: APPLICATION_STATUS.INSTALLABLE,
});
const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
@@ -184,7 +179,7 @@ describe('Application Row', () => {
spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLABLE,
+ status: APPLICATION_STATUS.INSTALLABLE,
installApplicationRequestParams: { hostname: 'jupyter' },
});
const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
@@ -201,7 +196,7 @@ describe('Application Row', () => {
spyOn(eventHub, '$emit');
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLING,
+ status: APPLICATION_STATUS.INSTALLING,
});
const installButton = vm.$el.querySelector('.js-cluster-application-install-button');
@@ -225,11 +220,11 @@ describe('Application Row', () => {
expect(generalErrorMessage).toBeNull();
});
- it('shows status reason when APPLICATION_ERROR', () => {
+ it('shows status reason when APPLICATION_STATUS.ERROR', () => {
const statusReason = 'We broke it 0.0';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_ERROR,
+ status: APPLICATION_STATUS.ERROR,
statusReason,
});
const generalErrorMessage = vm.$el.querySelector('.js-cluster-application-general-error-message');
@@ -243,7 +238,7 @@ describe('Application Row', () => {
const requestReason = 'We broke thre request 0.0';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
- status: APPLICATION_INSTALLABLE,
+ status: APPLICATION_STATUS.INSTALLABLE,
requestStatus: REQUEST_FAILURE,
requestReason,
});
diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js
index b2b0ebf840b..c7c1412e1c6 100644
--- a/spec/javascripts/clusters/services/mock_data.js
+++ b/spec/javascripts/clusters/services/mock_data.js
@@ -1,9 +1,4 @@
-import {
- APPLICATION_INSTALLED,
- APPLICATION_INSTALLABLE,
- APPLICATION_INSTALLING,
- APPLICATION_ERROR,
-} from '~/clusters/constants';
+import { APPLICATION_STATUS } from '~/clusters/constants';
const CLUSTERS_MOCK_DATA = {
GET: {
@@ -13,25 +8,25 @@ const CLUSTERS_MOCK_DATA = {
status_reason: 'Failed to request to CloudPlatform.',
applications: [{
name: 'helm',
- status: APPLICATION_INSTALLABLE,
+ status: APPLICATION_STATUS.INSTALLABLE,
status_reason: null,
}, {
name: 'ingress',
- status: APPLICATION_ERROR,
+ status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
external_ip: null,
}, {
name: 'runner',
- status: APPLICATION_INSTALLING,
+ status: APPLICATION_STATUS.INSTALLING,
status_reason: null,
},
{
name: 'prometheus',
- status: APPLICATION_ERROR,
+ status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
}, {
name: 'jupyter',
- status: APPLICATION_INSTALLING,
+ status: APPLICATION_STATUS.INSTALLING,
status_reason: 'Cannot connect',
}],
},
@@ -42,25 +37,25 @@ const CLUSTERS_MOCK_DATA = {
status_reason: 'Failed to request to CloudPlatform.',
applications: [{
name: 'helm',
- status: APPLICATION_INSTALLED,
+ status: APPLICATION_STATUS.INSTALLED,
status_reason: null,
}, {
name: 'ingress',
- status: APPLICATION_INSTALLED,
+ status: APPLICATION_STATUS.INSTALLED,
status_reason: 'Cannot connect',
external_ip: '1.1.1.1',
}, {
name: 'runner',
- status: APPLICATION_INSTALLING,
+ status: APPLICATION_STATUS.INSTALLING,
status_reason: null,
},
{
name: 'prometheus',
- status: APPLICATION_ERROR,
+ status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
}, {
name: 'jupyter',
- status: APPLICATION_INSTALLABLE,
+ status: APPLICATION_STATUS.INSTALLABLE,
status_reason: 'Cannot connect',
}],
},
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index 9e43552f740..104a064bdd3 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -1,5 +1,5 @@
import ClustersStore from '~/clusters/stores/clusters_store';
-import { APPLICATION_INSTALLING } from '~/clusters/constants';
+import { APPLICATION_STATUS } from '~/clusters/constants';
import { CLUSTERS_MOCK_DATA } from '../services/mock_data';
describe('Clusters Store', () => {
@@ -35,7 +35,7 @@ describe('Clusters Store', () => {
it('should store new request status', () => {
expect(store.state.applications.helm.requestStatus).toEqual(null);
- const newStatus = APPLICATION_INSTALLING;
+ const newStatus = APPLICATION_STATUS.INSTALLING;
store.updateAppProperty('helm', 'requestStatus', newStatus);
expect(store.state.applications.helm.requestStatus).toEqual(newStatus);
diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
index 2d136a63c52..a1a37b342b7 100644
--- a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
@@ -48,7 +48,11 @@ describe('DiffLineGutterContent', () => {
it('should return discussions for the given lineCode', () => {
const { lineCode } = getDiffFileMock().highlightedDiffLines[1];
- const component = createComponent({ lineCode, showCommentButton: true });
+ const component = createComponent({
+ lineCode,
+ showCommentButton: true,
+ discussions: getDiscussionsMockData(),
+ });
setDiscussions(component);
diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js
index 7706c32d24d..a59b26b2634 100644
--- a/spec/javascripts/diffs/store/getters_spec.js
+++ b/spec/javascripts/diffs/store/getters_spec.js
@@ -184,6 +184,104 @@ describe('Diffs Module Getters', () => {
});
});
+ describe('singleDiscussionByLineCode', () => {
+ it('returns found discussion per line Code', () => {
+ const discussionsMock = {};
+ discussionsMock.ABC = discussionMock;
+
+ expect(
+ getters.singleDiscussionByLineCode(localState, {}, null, {
+ discussionsByLineCode: () => discussionsMock,
+ })('DEF'),
+ ).toEqual([]);
+ });
+
+ it('returns empty array when no discussions match', () => {
+ expect(
+ getters.singleDiscussionByLineCode(localState, {}, null, {
+ discussionsByLineCode: () => {},
+ })('DEF'),
+ ).toEqual([]);
+ });
+ });
+
+ describe('shouldRenderParallelCommentRow', () => {
+ let line;
+
+ beforeEach(() => {
+ line = {};
+
+ line.left = {
+ lineCode: 'ABC',
+ };
+
+ line.right = {
+ lineCode: 'DEF',
+ };
+ });
+
+ it('returns true when discussion is expanded', () => {
+ discussionMock.expanded = true;
+
+ expect(
+ getters.shouldRenderParallelCommentRow(localState, {
+ singleDiscussionByLineCode: () => [discussionMock],
+ })(line),
+ ).toEqual(true);
+ });
+
+ it('returns false when no discussion was found', () => {
+ localState.diffLineCommentForms.ABC = false;
+ localState.diffLineCommentForms.DEF = false;
+
+ expect(
+ getters.shouldRenderParallelCommentRow(localState, {
+ singleDiscussionByLineCode: () => [],
+ })(line),
+ ).toEqual(false);
+ });
+
+ it('returns true when discussionForm was found', () => {
+ localState.diffLineCommentForms.ABC = {};
+
+ expect(
+ getters.shouldRenderParallelCommentRow(localState, {
+ singleDiscussionByLineCode: () => [discussionMock],
+ })(line),
+ ).toEqual(true);
+ });
+ });
+
+ describe('shouldRenderInlineCommentRow', () => {
+ it('returns true when diffLineCommentForms has form', () => {
+ localState.diffLineCommentForms.ABC = {};
+
+ expect(
+ getters.shouldRenderInlineCommentRow(localState)({
+ lineCode: 'ABC',
+ }),
+ ).toEqual(true);
+ });
+
+ it('returns false when no line discussions were found', () => {
+ expect(
+ getters.shouldRenderInlineCommentRow(localState, {
+ singleDiscussionByLineCode: () => [],
+ })('DEF'),
+ ).toEqual(false);
+ });
+
+ it('returns true if all found discussions are expanded', () => {
+ discussionMock.expanded = true;
+
+ expect(
+ getters.shouldRenderInlineCommentRow(localState, {
+ singleDiscussionByLineCode: () => [discussionMock],
+ })('ABC'),
+ ).toEqual(true);
+ });
+ });
+
describe('getDiffFileDiscussions', () => {
it('returns an array with discussions when fileHash matches and the discussion belongs to a diff', () => {
discussionMock.diff_file.file_hash = diffFileMock.fileHash;
diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
index dd9174194a1..1972408356e 100644
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ b/spec/javascripts/helpers/vuex_action_helper.js
@@ -84,7 +84,7 @@ export default (
done();
};
- const result = action({ commit, state, dispatch, rootState: state }, payload);
+ const result = action({ commit, state, dispatch, rootState: state, rootGetters: state }, payload);
return new Promise(resolve => {
setImmediate(resolve);
diff --git a/spec/javascripts/ide/components/branches/item_spec.js b/spec/javascripts/ide/components/branches/item_spec.js
new file mode 100644
index 00000000000..8b756c8f168
--- /dev/null
+++ b/spec/javascripts/ide/components/branches/item_spec.js
@@ -0,0 +1,53 @@
+import Vue from 'vue';
+import mountCompontent from 'spec/helpers/vue_mount_component_helper';
+import router from '~/ide/ide_router';
+import Item from '~/ide/components/branches/item.vue';
+import { getTimeago } from '~/lib/utils/datetime_utility';
+import { projectData } from '../../mock_data';
+
+const TEST_BRANCH = {
+ name: 'master',
+ committedDate: '2018-01-05T05:50Z',
+};
+const TEST_PROJECT_ID = projectData.name_with_namespace;
+
+describe('IDE branch item', () => {
+ const Component = Vue.extend(Item);
+ let vm;
+
+ beforeEach(() => {
+ vm = mountCompontent(Component, {
+ item: { ...TEST_BRANCH },
+ projectId: TEST_PROJECT_ID,
+ isActive: false,
+ });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders branch name and timeago', () => {
+ const timeText = getTimeago().format(TEST_BRANCH.committedDate);
+ expect(vm.$el).toContainText(TEST_BRANCH.name);
+ expect(vm.$el.querySelector('time')).toHaveText(timeText);
+ expect(vm.$el.querySelector('.ic-mobile-issue-close')).toBe(null);
+ });
+
+ it('renders link to branch', () => {
+ const expectedHref = router.resolve(`/project/${TEST_PROJECT_ID}/edit/${TEST_BRANCH.name}`).href;
+ expect(vm.$el).toMatch('a');
+ expect(vm.$el).toHaveAttr('href', expectedHref);
+ });
+
+ it('renders icon if isActive', done => {
+ vm.isActive = true;
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.querySelector('.ic-mobile-issue-close')).not.toBe(null);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+});
diff --git a/spec/javascripts/ide/components/branches/search_list_spec.js b/spec/javascripts/ide/components/branches/search_list_spec.js
new file mode 100644
index 00000000000..c3f84ba1c24
--- /dev/null
+++ b/spec/javascripts/ide/components/branches/search_list_spec.js
@@ -0,0 +1,79 @@
+import Vue from 'vue';
+import store from '~/ide/stores';
+import * as types from '~/ide/stores/modules/branches/mutation_types';
+import List from '~/ide/components/branches/search_list.vue';
+import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
+import { branches as testBranches } from '../../mock_data';
+import { resetStore } from '../../helpers';
+
+describe('IDE branches search list', () => {
+ const Component = Vue.extend(List);
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponentWithStore(Component, store, {});
+
+ spyOn(vm, 'fetchBranches');
+
+ vm.$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+
+ resetStore(store);
+ });
+
+ it('calls fetch on mounted', () => {
+ expect(vm.fetchBranches).toHaveBeenCalledWith({
+ search: '',
+ });
+ });
+
+ it('renders loading icon', done => {
+ vm.$store.state.branches.isLoading = true;
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el).toContainElement('.loading-container');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('renders branches not found when search is not empty', done => {
+ vm.search = 'testing';
+
+ vm.$nextTick(() => {
+ expect(vm.$el).toContainText('No branches found');
+
+ done();
+ });
+ });
+
+ describe('with branches', () => {
+ const currentBranch = testBranches[1];
+
+ beforeEach(done => {
+ vm.$store.state.currentBranchId = currentBranch.name;
+ vm.$store.commit(`branches/${types.RECEIVE_BRANCHES_SUCCESS}`, testBranches);
+
+ vm.$nextTick(done);
+ });
+
+ it('renders list', () => {
+ const elementText = Array.from(vm.$el.querySelectorAll('li strong'))
+ .map(x => x.textContent.trim());
+
+ expect(elementText).toEqual(testBranches.map(x => x.name));
+ });
+
+ it('renders check next to active branch', () => {
+ const checkedText = Array.from(vm.$el.querySelectorAll('li'))
+ .filter(x => x.querySelector('.ide-search-list-current-icon svg'))
+ .map(x => x.querySelector('strong').textContent.trim());
+
+ expect(checkedText).toEqual([currentBranch.name]);
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/merge_requests/dropdown_spec.js b/spec/javascripts/ide/components/merge_requests/dropdown_spec.js
deleted file mode 100644
index 74884c9a362..00000000000
--- a/spec/javascripts/ide/components/merge_requests/dropdown_spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import Vue from 'vue';
-import { createStore } from '~/ide/stores';
-import Dropdown from '~/ide/components/merge_requests/dropdown.vue';
-import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
-import { mergeRequests } from '../../mock_data';
-
-describe('IDE merge requests dropdown', () => {
- const Component = Vue.extend(Dropdown);
- let vm;
-
- beforeEach(() => {
- const store = createStore();
-
- vm = createComponentWithStore(Component, store, { show: false }).$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('does not render tabs when show is false', () => {
- expect(vm.$el.querySelector('.nav-links')).toBe(null);
- });
-
- describe('when show is true', () => {
- beforeEach(done => {
- vm.show = true;
- vm.$store.state.mergeRequests.assigned.mergeRequests.push(mergeRequests[0]);
-
- vm.$nextTick(done);
- });
-
- it('renders tabs', () => {
- expect(vm.$el.querySelector('.nav-links')).not.toBe(null);
- });
-
- it('renders count for assigned & created data', () => {
- expect(vm.$el.querySelector('.nav-links a').textContent).toContain('Created by me');
- expect(vm.$el.querySelector('.nav-links a .badge').textContent).toContain('0');
-
- expect(vm.$el.querySelectorAll('.nav-links a')[1].textContent).toContain('Assigned to me');
- expect(
- vm.$el.querySelectorAll('.nav-links a')[1].querySelector('.badge').textContent,
- ).toContain('1');
- });
- });
-});
diff --git a/spec/javascripts/ide/components/merge_requests/item_spec.js b/spec/javascripts/ide/components/merge_requests/item_spec.js
index 51c4cddef2f..750948cae3c 100644
--- a/spec/javascripts/ide/components/merge_requests/item_spec.js
+++ b/spec/javascripts/ide/components/merge_requests/item_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import router from '~/ide/ide_router';
import Item from '~/ide/components/merge_requests/item.vue';
import mountCompontent from '../../../helpers/vue_mount_component_helper';
@@ -27,6 +28,12 @@ describe('IDE merge request item', () => {
expect(vm.$el.textContent).toContain('gitlab-org/gitlab-ce!1');
});
+ it('renders link with href', () => {
+ const expectedHref = router.resolve(`/project/${vm.item.projectPathWithNamespace}/merge_requests/${vm.item.iid}`).href;
+ expect(vm.$el).toMatch('a');
+ expect(vm.$el).toHaveAttr('href', expectedHref);
+ });
+
it('renders icon if ID matches currentId', () => {
expect(vm.$el.querySelector('.ic-mobile-issue-close')).not.toBe(null);
});
@@ -50,12 +57,4 @@ describe('IDE merge request item', () => {
done();
});
});
-
- it('emits click event on click', () => {
- spyOn(vm, '$emit');
-
- vm.$el.click();
-
- expect(vm.$emit).toHaveBeenCalledWith('click', vm.item);
- });
});
diff --git a/spec/javascripts/ide/components/merge_requests/list_spec.js b/spec/javascripts/ide/components/merge_requests/list_spec.js
index f4b393778dc..c761315444c 100644
--- a/spec/javascripts/ide/components/merge_requests/list_spec.js
+++ b/spec/javascripts/ide/components/merge_requests/list_spec.js
@@ -10,10 +10,7 @@ describe('IDE merge requests list', () => {
let vm;
beforeEach(() => {
- vm = createComponentWithStore(Component, store, {
- type: 'created',
- emptyText: 'empty text',
- });
+ vm = createComponentWithStore(Component, store, {});
spyOn(vm, 'fetchMergeRequests');
@@ -28,13 +25,13 @@ describe('IDE merge requests list', () => {
it('calls fetch on mounted', () => {
expect(vm.fetchMergeRequests).toHaveBeenCalledWith({
- type: 'created',
search: '',
+ type: '',
});
});
it('renders loading icon', done => {
- vm.$store.state.mergeRequests.created.isLoading = true;
+ vm.$store.state.mergeRequests.isLoading = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
@@ -43,10 +40,6 @@ describe('IDE merge requests list', () => {
});
});
- it('renders empty text when no merge requests exist', () => {
- expect(vm.$el.textContent).toContain('empty text');
- });
-
it('renders no search results text when search is not empty', done => {
vm.search = 'testing';
@@ -57,9 +50,29 @@ describe('IDE merge requests list', () => {
});
});
+ it('clicking on search type, sets currentSearchType and loads merge requests', done => {
+ vm.onSearchFocus();
+
+ vm.$nextTick()
+ .then(() => {
+ vm.$el.querySelector('li button').click();
+
+ return vm.$nextTick();
+ })
+ .then(() => {
+ expect(vm.currentSearchType).toEqual(vm.$options.searchTypes[0]);
+ expect(vm.fetchMergeRequests).toHaveBeenCalledWith({
+ type: vm.currentSearchType.type,
+ search: '',
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
describe('with merge requests', () => {
beforeEach(done => {
- vm.$store.state.mergeRequests.created.mergeRequests.push({
+ vm.$store.state.mergeRequests.mergeRequests.push({
...mergeRequests[0],
projectPathWithNamespace: 'gitlab-org/gitlab-ce',
});
@@ -71,35 +84,6 @@ describe('IDE merge requests list', () => {
expect(vm.$el.querySelectorAll('li').length).toBe(1);
expect(vm.$el.querySelector('li').textContent).toContain(mergeRequests[0].title);
});
-
- it('calls openMergeRequest when clicking merge request', done => {
- spyOn(vm, 'openMergeRequest');
- vm.$el.querySelector('li button').click();
-
- vm.$nextTick(() => {
- expect(vm.openMergeRequest).toHaveBeenCalledWith({
- projectPath: 'gitlab-org/gitlab-ce',
- id: 1,
- });
-
- done();
- });
- });
- });
-
- describe('focusSearch', () => {
- it('focuses search input when loading is false', done => {
- spyOn(vm.$refs.searchInput, 'focus');
-
- vm.$store.state.mergeRequests.created.isLoading = false;
- vm.focusSearch();
-
- vm.$nextTick(() => {
- expect(vm.$refs.searchInput.focus).toHaveBeenCalled();
-
- done();
- });
- });
});
describe('searchMergeRequests', () => {
@@ -123,4 +107,52 @@ describe('IDE merge requests list', () => {
expect(vm.loadMergeRequests).toHaveBeenCalled();
});
});
+
+ describe('onSearchFocus', () => {
+ it('shows search types', done => {
+ vm.$el.querySelector('input').dispatchEvent(new Event('focus'));
+
+ expect(vm.hasSearchFocus).toBe(true);
+ expect(vm.showSearchTypes).toBe(true);
+
+ vm.$nextTick()
+ .then(() => {
+ const expectedSearchTypes = vm.$options.searchTypes.map(x => x.label);
+ const renderedSearchTypes = Array.from(vm.$el.querySelectorAll('li'))
+ .map(x => x.textContent.trim());
+
+ expect(renderedSearchTypes).toEqual(expectedSearchTypes);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show search types, if already has search value', () => {
+ vm.search = 'lorem ipsum';
+ vm.$el.querySelector('input').dispatchEvent(new Event('focus'));
+
+ expect(vm.hasSearchFocus).toBe(true);
+ expect(vm.showSearchTypes).toBe(false);
+ });
+
+ it('does not show search types, if already has a search type', () => {
+ vm.currentSearchType = {};
+ vm.$el.querySelector('input').dispatchEvent(new Event('focus'));
+
+ expect(vm.hasSearchFocus).toBe(true);
+ expect(vm.showSearchTypes).toBe(false);
+ });
+
+ it('resets hasSearchFocus when search changes', done => {
+ vm.hasSearchFocus = true;
+ vm.search = 'something else';
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.hasSearchFocus).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/nav_dropdown_button_spec.js b/spec/javascripts/ide/components/nav_dropdown_button_spec.js
new file mode 100644
index 00000000000..0a58e260280
--- /dev/null
+++ b/spec/javascripts/ide/components/nav_dropdown_button_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+import NavDropdownButton from '~/ide/components/nav_dropdown_button.vue';
+import store from '~/ide/stores';
+import { trimText } from 'spec/helpers/vue_component_helper';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { resetStore } from '../helpers';
+
+describe('NavDropdown', () => {
+ const TEST_BRANCH_ID = 'lorem-ipsum-dolar';
+ const TEST_MR_ID = '12345';
+ const Component = Vue.extend(NavDropdownButton);
+ let vm;
+
+ beforeEach(() => {
+ vm = mountComponentWithStore(Component, { store });
+
+ vm.$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+
+ resetStore(store);
+ });
+
+ it('renders empty placeholders, if state is falsey', () => {
+ expect(trimText(vm.$el.textContent)).toEqual('- -');
+ });
+
+ it('renders branch name, if state has currentBranchId', done => {
+ vm.$store.state.currentBranchId = TEST_BRANCH_ID;
+
+ vm.$nextTick()
+ .then(() => {
+ expect(trimText(vm.$el.textContent)).toEqual(`${TEST_BRANCH_ID} -`);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('renders mr id, if state has currentMergeRequestId', done => {
+ vm.$store.state.currentMergeRequestId = TEST_MR_ID;
+
+ vm.$nextTick()
+ .then(() => {
+ expect(trimText(vm.$el.textContent)).toEqual(`- !${TEST_MR_ID}`);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('renders branch and mr, if state has both', done => {
+ vm.$store.state.currentBranchId = TEST_BRANCH_ID;
+ vm.$store.state.currentMergeRequestId = TEST_MR_ID;
+
+ vm.$nextTick()
+ .then(() => {
+ expect(trimText(vm.$el.textContent)).toEqual(`${TEST_BRANCH_ID} !${TEST_MR_ID}`);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+});
diff --git a/spec/javascripts/ide/components/nav_dropdown_spec.js b/spec/javascripts/ide/components/nav_dropdown_spec.js
new file mode 100644
index 00000000000..af6665bcd62
--- /dev/null
+++ b/spec/javascripts/ide/components/nav_dropdown_spec.js
@@ -0,0 +1,50 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import store from '~/ide/stores';
+import NavDropdown from '~/ide/components/nav_dropdown.vue';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+
+describe('IDE NavDropdown', () => {
+ const Component = Vue.extend(NavDropdown);
+ let vm;
+ let $dropdown;
+
+ beforeEach(() => {
+ vm = mountComponentWithStore(Component, { store });
+ $dropdown = $(vm.$el);
+
+ // block dispatch from doing anything
+ spyOn(vm.$store, 'dispatch');
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders nothing initially', () => {
+ expect(vm.$el).not.toContainElement('.ide-nav-form');
+ });
+
+ it('renders nav form when show.bs.dropdown', done => {
+ $dropdown.trigger('show.bs.dropdown');
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el).toContainElement('.ide-nav-form');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('destroys nav form when closed', done => {
+ $dropdown.trigger('show.bs.dropdown');
+ $dropdown.trigger('hide.bs.dropdown');
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el).not.toContainElement('.ide-nav-form');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+});
diff --git a/spec/javascripts/ide/components/shared/tokened_input_spec.js b/spec/javascripts/ide/components/shared/tokened_input_spec.js
new file mode 100644
index 00000000000..09940fe8c6a
--- /dev/null
+++ b/spec/javascripts/ide/components/shared/tokened_input_spec.js
@@ -0,0 +1,132 @@
+import Vue from 'vue';
+import TokenedInput from '~/ide/components/shared/tokened_input.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+const TEST_PLACEHOLDER = 'Searching in test';
+const TEST_TOKENS = [
+ { label: 'lorem', id: 1 },
+ { label: 'ipsum', id: 2 },
+ { label: 'dolar', id: 3 },
+];
+const TEST_VALUE = 'lorem';
+
+function getTokenElements(vm) {
+ return Array.from(vm.$el.querySelectorAll('.filtered-search-token button'));
+}
+
+function createBackspaceEvent() {
+ const e = new Event('keyup');
+ e.keyCode = 8;
+ e.which = e.keyCode;
+ e.altKey = false;
+ e.ctrlKey = true;
+ e.shiftKey = false;
+ e.metaKey = false;
+ return e;
+}
+
+describe('IDE shared/TokenedInput', () => {
+ const Component = Vue.extend(TokenedInput);
+ let vm;
+
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ tokens: TEST_TOKENS,
+ placeholder: TEST_PLACEHOLDER,
+ value: TEST_VALUE,
+ });
+
+ spyOn(vm, '$emit');
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders tokens', () => {
+ const renderedTokens = getTokenElements(vm)
+ .map(x => x.textContent.trim());
+
+ expect(renderedTokens).toEqual(TEST_TOKENS.map(x => x.label));
+ });
+
+ it('renders input', () => {
+ expect(vm.$refs.input).toBeTruthy();
+ expect(vm.$refs.input).toHaveValue(TEST_VALUE);
+ });
+
+ it('renders placeholder, when tokens are empty', done => {
+ vm.tokens = [];
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$refs.input).toHaveAttr('placeholder', TEST_PLACEHOLDER);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('triggers "removeToken" on token click', () => {
+ getTokenElements(vm)[0].click();
+
+ expect(vm.$emit).toHaveBeenCalledWith('removeToken', TEST_TOKENS[0]);
+ });
+
+ it('when input triggers backspace event, it calls "onBackspace"', () => {
+ spyOn(vm, 'onBackspace');
+
+ vm.$refs.input.dispatchEvent(createBackspaceEvent());
+ vm.$refs.input.dispatchEvent(createBackspaceEvent());
+
+ expect(vm.onBackspace).toHaveBeenCalledTimes(2);
+ });
+
+ it('triggers "removeToken" on backspaces when value is empty', () => {
+ vm.value = '';
+
+ vm.onBackspace();
+ expect(vm.$emit).not.toHaveBeenCalled();
+ expect(vm.backspaceCount).toEqual(1);
+
+ vm.onBackspace();
+ expect(vm.$emit).toHaveBeenCalledWith('removeToken', TEST_TOKENS[TEST_TOKENS.length - 1]);
+ expect(vm.backspaceCount).toEqual(0);
+ });
+
+ it('does not trigger "removeToken" on backspaces when value is not empty', () => {
+ vm.onBackspace();
+ vm.onBackspace();
+
+ expect(vm.backspaceCount).toEqual(0);
+ expect(vm.$emit).not.toHaveBeenCalled();
+ });
+
+ it('does not trigger "removeToken" on backspaces when tokens are empty', () => {
+ vm.tokens = [];
+
+ vm.onBackspace();
+ vm.onBackspace();
+
+ expect(vm.backspaceCount).toEqual(0);
+ expect(vm.$emit).not.toHaveBeenCalled();
+ });
+
+ it('triggers "focus" on input focus', () => {
+ vm.$refs.input.dispatchEvent(new Event('focus'));
+
+ expect(vm.$emit).toHaveBeenCalledWith('focus');
+ });
+
+ it('triggers "blur" on input blur', () => {
+ vm.$refs.input.dispatchEvent(new Event('blur'));
+
+ expect(vm.$emit).toHaveBeenCalledWith('blur');
+ });
+
+ it('triggers "input" with value on input change', () => {
+ vm.$refs.input.value = 'something-else';
+ vm.$refs.input.dispatchEvent(new Event('input'));
+
+ expect(vm.$emit).toHaveBeenCalledWith('input', 'something-else');
+ });
+});
diff --git a/spec/javascripts/ide/helpers.js b/spec/javascripts/ide/helpers.js
index 569fa5c7aae..c11c482fef8 100644
--- a/spec/javascripts/ide/helpers.js
+++ b/spec/javascripts/ide/helpers.js
@@ -4,6 +4,7 @@ import state from '~/ide/stores/state';
import commitState from '~/ide/stores/modules/commit/state';
import mergeRequestsState from '~/ide/stores/modules/merge_requests/state';
import pipelinesState from '~/ide/stores/modules/pipelines/state';
+import branchesState from '~/ide/stores/modules/branches/state';
export const resetStore = store => {
const newState = {
@@ -11,6 +12,7 @@ export const resetStore = store => {
commit: commitState(),
mergeRequests: mergeRequestsState(),
pipelines: pipelinesState(),
+ branches: branchesState(),
};
store.replaceState(newState);
};
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index 7be450a0df7..4fe826943b2 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -165,3 +165,33 @@ export const mergeRequests = [
web_url: `${gl.TEST_HOST}/namespace/project-path/merge_requests/1`,
},
];
+
+export const branches = [
+ {
+ id: 1,
+ name: 'master',
+ commit: {
+ message: 'Update master branch',
+ committed_date: '2018-08-01T00:20:05Z',
+ },
+ can_push: true,
+ },
+ {
+ id: 2,
+ name: 'feature/lorem-ipsum',
+ commit: {
+ message: 'Update some stuff',
+ committed_date: '2018-08-02T00:00:05Z',
+ },
+ can_push: true,
+ },
+ {
+ id: 3,
+ name: 'feature/dolar-amit',
+ commit: {
+ message: 'Update some more stuff',
+ committed_date: '2018-06-30T00:20:05Z',
+ },
+ can_push: true,
+ },
+];
diff --git a/spec/javascripts/ide/stores/modules/branches/actions_spec.js b/spec/javascripts/ide/stores/modules/branches/actions_spec.js
new file mode 100644
index 00000000000..a0fce578958
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/branches/actions_spec.js
@@ -0,0 +1,193 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import state from '~/ide/stores/modules/branches/state';
+import * as types from '~/ide/stores/modules/branches/mutation_types';
+import testAction from 'spec/helpers/vuex_action_helper';
+import {
+ requestBranches,
+ receiveBranchesError,
+ receiveBranchesSuccess,
+ fetchBranches,
+ resetBranches,
+ openBranch,
+} from '~/ide/stores/modules/branches/actions';
+import { branches, projectData } from '../../../mock_data';
+
+describe('IDE branches actions', () => {
+ const TEST_SEARCH = 'foosearch';
+ let mockedContext;
+ let mockedState;
+ let mock;
+
+ beforeEach(() => {
+ mockedContext = {
+ dispatch() {},
+ rootState: {
+ currentProjectId: projectData.name_with_namespace,
+ },
+ rootGetters: {
+ currentProject: projectData,
+ },
+ state: state(),
+ };
+
+ // testAction looks for rootGetters in state,
+ // so they need to be concatenated here.
+ mockedState = {
+ ...mockedContext.state,
+ ...mockedContext.rootGetters,
+ ...mockedContext.rootState,
+ };
+
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('requestBranches', () => {
+ it('should commit request', done => {
+ testAction(
+ requestBranches,
+ null,
+ mockedContext.state,
+ [{ type: types.REQUEST_BRANCHES }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveBranchesError', () => {
+ it('should should commit error', done => {
+
+ testAction(
+ receiveBranchesError,
+ { search: TEST_SEARCH },
+ mockedContext.state,
+ [{ type: types.RECEIVE_BRANCHES_ERROR }],
+ [
+ {
+ type: 'setErrorMessage',
+ payload: {
+ text: 'Error loading branches.',
+ action: jasmine.any(Function),
+ actionText: 'Please try again',
+ actionPayload: { search: TEST_SEARCH },
+ },
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('receiveBranchesSuccess', () => {
+ it('should commit received data', done => {
+ testAction(
+ receiveBranchesSuccess,
+ branches,
+ mockedContext.state,
+ [{ type: types.RECEIVE_BRANCHES_SUCCESS, payload: branches }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchBranches', () => {
+ beforeEach(() => {
+ gon.api_version = 'v4';
+ });
+
+ describe('success', () => {
+ beforeEach(() => {
+ mock.onGet(/\/api\/v4\/projects\/\d+\/repository\/branches(.*)$/).replyOnce(200, branches);
+ });
+
+ it('calls API with params', () => {
+ const apiSpy = spyOn(axios, 'get').and.callThrough();
+
+ fetchBranches(mockedContext, { search: TEST_SEARCH });
+
+ expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), {
+ params: jasmine.objectContaining({
+ search: TEST_SEARCH,
+ sort: 'updated_desc',
+ }),
+ });
+ });
+
+ it('dispatches success with received data', done => {
+ testAction(
+ fetchBranches,
+ { search: TEST_SEARCH },
+ mockedState,
+ [],
+ [
+ { type: 'requestBranches' },
+ { type: 'resetBranches' },
+ {
+ type: 'receiveBranchesSuccess',
+ payload: branches,
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(/\/api\/v4\/projects\/\d+\/repository\/branches(.*)$/).replyOnce(500);
+ });
+
+ it('dispatches error', done => {
+ testAction(
+ fetchBranches,
+ { search: TEST_SEARCH },
+ mockedState,
+ [],
+ [
+ { type: 'requestBranches' },
+ { type: 'resetBranches' },
+ {
+ type: 'receiveBranchesError',
+ payload: { search: TEST_SEARCH },
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('resetBranches', () => {
+ it('commits reset', done => {
+ testAction(
+ resetBranches,
+ null,
+ mockedContext.state,
+ [{ type: types.RESET_BRANCHES }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('openBranch', () => {
+ it('dispatches goToRoute action with path', done => {
+ const branchId = branches[0].name;
+ const expectedPath = `/project/${projectData.name_with_namespace}/edit/${branchId}`;
+ testAction(
+ openBranch,
+ branchId,
+ mockedState,
+ [],
+ [{ type: 'goToRoute', payload: expectedPath }],
+ done,
+ );
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/modules/branches/mutations_spec.js b/spec/javascripts/ide/stores/modules/branches/mutations_spec.js
new file mode 100644
index 00000000000..be91440f119
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/branches/mutations_spec.js
@@ -0,0 +1,51 @@
+import state from '~/ide/stores/modules/branches/state';
+import mutations from '~/ide/stores/modules/branches/mutations';
+import * as types from '~/ide/stores/modules/branches/mutation_types';
+import { branches } from '../../../mock_data';
+
+describe('IDE branches mutations', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = state();
+ });
+
+ describe(types.REQUEST_BRANCHES, () => {
+ it('sets loading to true', () => {
+ mutations[types.REQUEST_BRANCHES](mockedState);
+
+ expect(mockedState.isLoading).toBe(true);
+ });
+ });
+
+ describe(types.RECEIVE_BRANCHES_ERROR, () => {
+ it('sets loading to false', () => {
+ mutations[types.RECEIVE_BRANCHES_ERROR](mockedState);
+
+ expect(mockedState.isLoading).toBe(false);
+ });
+ });
+
+ describe(types.RECEIVE_BRANCHES_SUCCESS, () => {
+ it('sets branches', () => {
+ const expectedBranches = branches.map(branch => ({
+ name: branch.name,
+ committedDate: branch.commit.committed_date,
+ }));
+
+ mutations[types.RECEIVE_BRANCHES_SUCCESS](mockedState, branches);
+
+ expect(mockedState.branches).toEqual(expectedBranches);
+ });
+ });
+
+ describe(types.RESET_BRANCHES, () => {
+ it('clears branches array', () => {
+ mockedState.branches = ['test'];
+
+ mutations[types.RESET_BRANCHES](mockedState);
+
+ expect(mockedState.branches).toEqual([]);
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
index d063f1ea860..62699143a91 100644
--- a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js
@@ -8,9 +8,7 @@ import {
receiveMergeRequestsSuccess,
fetchMergeRequests,
resetMergeRequests,
- openMergeRequest,
} from '~/ide/stores/modules/merge_requests/actions';
-import router from '~/ide/ide_router';
import { mergeRequests } from '../../../mock_data';
import testAction from '../../../../helpers/vuex_action_helper';
@@ -28,12 +26,12 @@ describe('IDE merge requests actions', () => {
});
describe('requestMergeRequests', () => {
- it('should should commit request', done => {
+ it('should commit request', done => {
testAction(
requestMergeRequests,
- 'created',
+ null,
mockedState,
- [{ type: types.REQUEST_MERGE_REQUESTS, payload: 'created' }],
+ [{ type: types.REQUEST_MERGE_REQUESTS }],
[],
done,
);
@@ -46,7 +44,7 @@ describe('IDE merge requests actions', () => {
receiveMergeRequestsError,
{ type: 'created', search: '' },
mockedState,
- [{ type: types.RECEIVE_MERGE_REQUESTS_ERROR, payload: 'created' }],
+ [{ type: types.RECEIVE_MERGE_REQUESTS_ERROR }],
[
{
type: 'setErrorMessage',
@@ -67,12 +65,12 @@ describe('IDE merge requests actions', () => {
it('should commit received data', done => {
testAction(
receiveMergeRequestsSuccess,
- { type: 'created', data: 'data' },
+ mergeRequests,
mockedState,
[
{
type: types.RECEIVE_MERGE_REQUESTS_SUCCESS,
- payload: { type: 'created', data: 'data' },
+ payload: mergeRequests,
},
],
[],
@@ -129,11 +127,11 @@ describe('IDE merge requests actions', () => {
mockedState,
[],
[
- { type: 'requestMergeRequests', payload: 'created' },
- { type: 'resetMergeRequests', payload: 'created' },
+ { type: 'requestMergeRequests' },
+ { type: 'resetMergeRequests' },
{
type: 'receiveMergeRequestsSuccess',
- payload: { type: 'created', data: mergeRequests },
+ payload: mergeRequests,
},
],
done,
@@ -149,12 +147,12 @@ describe('IDE merge requests actions', () => {
it('dispatches error', done => {
testAction(
fetchMergeRequests,
- { type: 'created' },
+ { type: 'created', search: '' },
mockedState,
[],
[
- { type: 'requestMergeRequests', payload: 'created' },
- { type: 'resetMergeRequests', payload: 'created' },
+ { type: 'requestMergeRequests' },
+ { type: 'resetMergeRequests' },
{ type: 'receiveMergeRequestsError', payload: { type: 'created', search: '' } },
],
done,
@@ -167,59 +165,12 @@ describe('IDE merge requests actions', () => {
it('commits reset', done => {
testAction(
resetMergeRequests,
- 'created',
+ null,
mockedState,
- [{ type: types.RESET_MERGE_REQUESTS, payload: 'created' }],
+ [{ type: types.RESET_MERGE_REQUESTS }],
[],
done,
);
});
});
-
- describe('openMergeRequest', () => {
- beforeEach(() => {
- spyOn(router, 'push');
- });
-
- it('commits reset mutations and actions', done => {
- const commit = jasmine.createSpy();
- const dispatch = jasmine.createSpy().and.returnValue(Promise.resolve());
- openMergeRequest({ commit, dispatch }, { projectPath: 'gitlab-org/gitlab-ce', id: '1' });
-
- setTimeout(() => {
- expect(commit.calls.argsFor(0)).toEqual(['CLEAR_PROJECTS', null, { root: true }]);
- expect(commit.calls.argsFor(1)).toEqual(['SET_CURRENT_MERGE_REQUEST', '1', { root: true }]);
- expect(commit.calls.argsFor(2)).toEqual(['RESET_OPEN_FILES', null, { root: true }]);
-
- expect(dispatch.calls.argsFor(0)).toEqual(['setCurrentBranchId', '', { root: true }]);
- expect(dispatch.calls.argsFor(1)).toEqual([
- 'pipelines/stopPipelinePolling',
- null,
- { root: true },
- ]);
- expect(dispatch.calls.argsFor(2)).toEqual(['setRightPane', null, { root: true }]);
- expect(dispatch.calls.argsFor(3)).toEqual([
- 'pipelines/resetLatestPipeline',
- null,
- { root: true },
- ]);
- expect(dispatch.calls.argsFor(4)).toEqual([
- 'pipelines/clearEtagPoll',
- null,
- { root: true },
- ]);
-
- done();
- });
- });
-
- it('pushes new route', () => {
- openMergeRequest(
- { commit() {}, dispatch: () => Promise.resolve() },
- { projectPath: 'gitlab-org/gitlab-ce', id: '1' },
- );
-
- expect(router.push).toHaveBeenCalledWith('/project/gitlab-org/gitlab-ce/merge_requests/1');
- });
- });
});
diff --git a/spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js b/spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js
index ea03131d90d..664d3914564 100644
--- a/spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js
+++ b/spec/javascripts/ide/stores/modules/merge_requests/mutations_spec.js
@@ -12,29 +12,26 @@ describe('IDE merge requests mutations', () => {
describe(types.REQUEST_MERGE_REQUESTS, () => {
it('sets loading to true', () => {
- mutations[types.REQUEST_MERGE_REQUESTS](mockedState, 'created');
+ mutations[types.REQUEST_MERGE_REQUESTS](mockedState);
- expect(mockedState.created.isLoading).toBe(true);
+ expect(mockedState.isLoading).toBe(true);
});
});
describe(types.RECEIVE_MERGE_REQUESTS_ERROR, () => {
it('sets loading to false', () => {
- mutations[types.RECEIVE_MERGE_REQUESTS_ERROR](mockedState, 'created');
+ mutations[types.RECEIVE_MERGE_REQUESTS_ERROR](mockedState);
- expect(mockedState.created.isLoading).toBe(false);
+ expect(mockedState.isLoading).toBe(false);
});
});
describe(types.RECEIVE_MERGE_REQUESTS_SUCCESS, () => {
it('sets merge requests', () => {
gon.gitlab_url = gl.TEST_HOST;
- mutations[types.RECEIVE_MERGE_REQUESTS_SUCCESS](mockedState, {
- type: 'created',
- data: mergeRequests,
- });
+ mutations[types.RECEIVE_MERGE_REQUESTS_SUCCESS](mockedState, mergeRequests);
- expect(mockedState.created.mergeRequests).toEqual([
+ expect(mockedState.mergeRequests).toEqual([
{
id: 1,
iid: 1,
@@ -50,9 +47,9 @@ describe('IDE merge requests mutations', () => {
it('clears merge request array', () => {
mockedState.mergeRequests = ['test'];
- mutations[types.RESET_MERGE_REQUESTS](mockedState, 'created');
+ mutations[types.RESET_MERGE_REQUESTS](mockedState);
- expect(mockedState.created.mergeRequests).toEqual([]);
+ expect(mockedState.mergeRequests).toEqual([]);
});
});
});
diff --git a/spec/javascripts/pages/profiles/show/emoji_menu_spec.js b/spec/javascripts/pages/profiles/show/emoji_menu_spec.js
new file mode 100644
index 00000000000..b70368fc92f
--- /dev/null
+++ b/spec/javascripts/pages/profiles/show/emoji_menu_spec.js
@@ -0,0 +1,117 @@
+import $ from 'jquery';
+import axios from '~/lib/utils/axios_utils';
+import EmojiMenu from '~/pages/profiles/show/emoji_menu';
+import { TEST_HOST } from 'spec/test_constants';
+
+describe('EmojiMenu', () => {
+ const dummyEmojiTag = '<dummy></tag>';
+ const dummyToggleButtonSelector = '.toggle-button-selector';
+ const dummyMenuClass = 'dummy-menu-class';
+
+ let emojiMenu;
+ let dummySelectEmojiCallback;
+ let dummyEmojiList;
+
+ beforeEach(() => {
+ dummySelectEmojiCallback = jasmine.createSpy('dummySelectEmojiCallback');
+ dummyEmojiList = {
+ glEmojiTag() {
+ return dummyEmojiTag;
+ },
+ normalizeEmojiName(emoji) {
+ return emoji;
+ },
+ isEmojiNameValid() {
+ return true;
+ },
+ getEmojiCategoryMap() {
+ return { dummyCategory: [] };
+ },
+ };
+
+ emojiMenu = new EmojiMenu(
+ dummyEmojiList,
+ dummyToggleButtonSelector,
+ dummyMenuClass,
+ dummySelectEmojiCallback,
+ );
+ });
+
+ afterEach(() => {
+ emojiMenu.destroy();
+ });
+
+ describe('addAward', () => {
+ const dummyAwardUrl = `${TEST_HOST}/award/url`;
+ const dummyEmoji = 'tropical_fish';
+ const dummyVotesBlock = () => $('<div />');
+
+ it('calls selectEmojiCallback', done => {
+ expect(dummySelectEmojiCallback).not.toHaveBeenCalled();
+
+ emojiMenu.addAward(dummyVotesBlock(), dummyAwardUrl, dummyEmoji, false, () => {
+ expect(dummySelectEmojiCallback).toHaveBeenCalledWith(dummyEmoji, dummyEmojiTag);
+ done();
+ });
+ });
+
+ it('does not make an axios requst', done => {
+ spyOn(axios, 'request').and.stub();
+
+ emojiMenu.addAward(dummyVotesBlock(), dummyAwardUrl, dummyEmoji, false, () => {
+ expect(axios.request).not.toHaveBeenCalled();
+ done();
+ });
+ });
+ });
+
+ describe('bindEvents', () => {
+ beforeEach(() => {
+ spyOn(emojiMenu, 'registerEventListener').and.stub();
+ });
+
+ it('binds event listeners to custom toggle button', () => {
+ emojiMenu.bindEvents();
+
+ expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
+ 'one',
+ jasmine.anything(),
+ 'mouseenter focus',
+ dummyToggleButtonSelector,
+ 'mouseenter focus',
+ jasmine.anything(),
+ );
+ expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
+ 'on',
+ jasmine.anything(),
+ 'click',
+ dummyToggleButtonSelector,
+ jasmine.anything(),
+ );
+ });
+
+ it('binds event listeners to custom menu class', () => {
+ emojiMenu.bindEvents();
+
+ expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
+ 'on',
+ jasmine.anything(),
+ 'click',
+ `.js-awards-block .js-emoji-btn, .${dummyMenuClass} .js-emoji-btn`,
+ jasmine.anything(),
+ );
+ });
+ });
+
+ describe('createEmojiMenu', () => {
+ it('renders the menu with custom menu class', () => {
+ const menuElement = () =>
+ document.body.querySelector(`.emoji-menu.${dummyMenuClass} .emoji-menu-content`);
+ expect(menuElement()).toBe(null);
+
+ emojiMenu.createEmojiMenu();
+
+ expect(menuElement()).not.toBe(null);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js b/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
index b878286ae3f..dde49b4a5d7 100644
--- a/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
+++ b/spec/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
@@ -170,8 +170,6 @@ describe('ImageDiffViewer', () => {
vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click();
vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dragger').style.left).toBe('100px');
-
dragSlider(vm.$el.querySelector('.dragger'));
vm.$nextTick(() => {
diff --git a/spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js
index ba897f4660d..2796cd088c6 100644
--- a/spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js
+++ b/spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js
@@ -2,15 +2,15 @@ import Vue from 'vue';
import dropdownButtonComponent from '~/vue_shared/components/dropdown/dropdown_button.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper';
const defaultLabel = 'Select';
const customLabel = 'Select project';
-const createComponent = config => {
+const createComponent = (props, slots = {}) => {
const Component = Vue.extend(dropdownButtonComponent);
- return mountComponent(Component, config);
+ return mountComponentWithSlots(Component, { props, slots });
};
describe('DropdownButtonComponent', () => {
@@ -65,5 +65,14 @@ describe('DropdownButtonComponent', () => {
expect(dropdownIconEl).not.toBeNull();
expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true);
});
+
+ it('renders slot, if default slot exists', () => {
+ vm = createComponent({}, {
+ default: ['Lorem Ipsum Dolar'],
+ });
+
+ expect(vm.$el).not.toContainElement('.dropdown-toggle-text');
+ expect(vm.$el).toHaveText('Lorem Ipsum Dolar');
+ });
});
});
diff --git a/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb b/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
new file mode 100644
index 00000000000..dae754112dc
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, :migration, schema: 20180704204006 do
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+ let(:todos) { table(:todos) }
+ let(:issues) { table(:issues) }
+ let(:assignees) { table(:issue_assignees) }
+ let(:project_authorizations) { table(:project_authorizations) }
+ let(:project_features) { table(:project_features) }
+
+ let(:todo_params) { { author_id: 1, target_type: 'Issue', action: 1, state: :pending } }
+
+ before do
+ users.create(id: 1, email: 'user@example.com', projects_limit: 10)
+ users.create(id: 2, email: 'reporter@example.com', projects_limit: 10)
+ users.create(id: 3, email: 'guest@example.com', projects_limit: 10)
+
+ projects.create!(id: 1, name: 'project-1', path: 'project-1', visibility_level: 0, namespace_id: 1)
+ projects.create!(id: 2, name: 'project-2', path: 'project-2', visibility_level: 0, namespace_id: 1)
+
+ issues.create(id: 1, project_id: 1)
+ issues.create(id: 2, project_id: 2)
+
+ project_authorizations.create(user_id: 2, project_id: 2, access_level: 20) # reporter
+ project_authorizations.create(user_id: 3, project_id: 2, access_level: 10) # guest
+
+ todos.create(todo_params.merge(user_id: 1, project_id: 1, target_id: 1)) # out of project ids range
+ todos.create(todo_params.merge(user_id: 1, project_id: 2, target_id: 2)) # non member
+ todos.create(todo_params.merge(user_id: 2, project_id: 2, target_id: 2)) # reporter
+ todos.create(todo_params.merge(user_id: 3, project_id: 2, target_id: 2)) # guest
+ end
+
+ subject { described_class.new.perform(2, 5) }
+
+ context 'when a project is private' do
+ it 'removes todos of users without project access' do
+ expect { subject }.to change { Todo.count }.from(4).to(3)
+ end
+
+ context 'with a confidential issue' do
+ it 'removes todos of users without project access and guests for confidential issues' do
+ issues.create(id: 3, project_id: 2, confidential: true)
+ issues.create(id: 4, project_id: 1, confidential: true) # not in the batch
+ todos.create(todo_params.merge(user_id: 3, project_id: 2, target_id: 3))
+ todos.create(todo_params.merge(user_id: 2, project_id: 2, target_id: 3))
+ todos.create(todo_params.merge(user_id: 1, project_id: 1, target_id: 4))
+
+ expect { subject }.to change { Todo.count }.from(7).to(5)
+ end
+ end
+ end
+
+ context 'when a project is public' do
+ before do
+ projects.find(2).update_attribute(:visibility_level, 20)
+ end
+
+ context 'when all features have the same visibility as the project, no confidential issues' do
+ it 'does not remove any todos' do
+ expect { subject }.not_to change { Todo.count }
+ end
+ end
+
+ context 'with confidential issues' do
+ before do
+ users.create(id: 4, email: 'author@example.com', projects_limit: 10)
+ users.create(id: 5, email: 'assignee@example.com', projects_limit: 10)
+ issues.create(id: 3, project_id: 2, confidential: true, author_id: 4)
+ assignees.create(user_id: 5, issue_id: 3)
+
+ todos.create(todo_params.merge(user_id: 1, project_id: 2, target_id: 3)) # to be deleted
+ todos.create(todo_params.merge(user_id: 2, project_id: 2, target_id: 3)) # authorized user
+ todos.create(todo_params.merge(user_id: 3, project_id: 2, target_id: 3)) # to be deleted guest
+ todos.create(todo_params.merge(user_id: 4, project_id: 2, target_id: 3)) # conf issue author
+ todos.create(todo_params.merge(user_id: 5, project_id: 2, target_id: 3)) # conf issue assignee
+ end
+
+ it 'removes confidential issue todos for non authorized users' do
+ expect { subject }.to change { Todo.count }.from(9).to(7)
+ end
+ end
+
+ context 'features visibility restrictions' do
+ before do
+ todo_params.merge!(project_id: 2, user_id: 1, target_id: 3)
+ todos.create(todo_params.merge(user_id: 1, target_id: 3, target_type: 'MergeRequest'))
+ todos.create(todo_params.merge(user_id: 1, target_id: 3, target_type: 'Commit'))
+ end
+
+ context 'when issues are restricted to project members' do
+ before do
+ project_features.create(issues_access_level: 10, project_id: 2)
+ end
+
+ it 'removes non members issue todos' do
+ expect { subject }.to change { Todo.count }.from(6).to(5)
+ end
+ end
+
+ context 'when merge requests are restricted to project members' do
+ before do
+ project_features.create(merge_requests_access_level: 10, project_id: 2)
+ end
+
+ it 'removes non members issue todos' do
+ expect { subject }.to change { Todo.count }.from(6).to(5)
+ end
+ end
+
+ context 'when repository and merge requests are restricted to project members' do
+ before do
+ project_features.create(repository_access_level: 10, merge_requests_access_level: 10, project_id: 2)
+ end
+
+ it 'removes non members commit and merge requests todos' do
+ expect { subject }.to change { Todo.count }.from(6).to(4)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 05c232d22cf..7a681bc6610 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -69,6 +69,7 @@ describe Gitlab::BitbucketImport::Importer do
let(:project) do
create(
:project,
+ :repository,
import_source: project_identifier,
import_url: "https://bitbucket.org/#{project_identifier}.git",
import_data_attributes: { credentials: data }
@@ -85,10 +86,84 @@ describe Gitlab::BitbucketImport::Importer do
}
end
+ let(:sample) { RepoHelpers.sample_compare }
+
before do
allow(importer).to receive(:gitlab_shell) { gitlab_shell }
end
+ subject { described_class.new(project) }
+
+ describe '#import_pull_requests' do
+ before do
+ allow(subject).to receive(:import_wiki)
+ allow(subject).to receive(:import_issues)
+
+ pull_request = instance_double(
+ Bitbucket::Representation::PullRequest,
+ iid: 10,
+ source_branch_sha: sample.commits.last,
+ source_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.source_branch,
+ target_branch_sha: sample.commits.first,
+ target_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.target_branch,
+ title: 'This is a title',
+ description: 'This is a test pull request',
+ state: 'merged',
+ author: 'other',
+ created_at: Time.now,
+ updated_at: Time.now)
+
+ # https://gitlab.com/gitlab-org/gitlab-test/compare/c1acaa58bbcbc3eafe538cb8274ba387047b69f8...5937ac0a7beb003549fc5fd26fc247ad
+ @inline_note = instance_double(
+ Bitbucket::Representation::PullRequestComment,
+ iid: 2,
+ file_path: '.gitmodules',
+ old_pos: nil,
+ new_pos: 4,
+ note: 'Hello world',
+ author: 'root',
+ created_at: Time.now,
+ updated_at: Time.now,
+ inline?: true,
+ has_parent?: false)
+
+ @reply = instance_double(
+ Bitbucket::Representation::PullRequestComment,
+ iid: 3,
+ file_path: '.gitmodules',
+ note: 'Hello world',
+ author: 'root',
+ created_at: Time.now,
+ updated_at: Time.now,
+ inline?: true,
+ has_parent?: true,
+ parent_id: 2)
+
+ comments = [@inline_note, @reply]
+
+ allow(subject.client).to receive(:repo)
+ allow(subject.client).to receive(:pull_requests).and_return([pull_request])
+ allow(subject.client).to receive(:pull_request_comments).with(anything, pull_request.iid).and_return(comments)
+ end
+
+ it 'imports threaded discussions' do
+ expect { subject.execute }.to change { MergeRequest.count }.by(1)
+
+ merge_request = MergeRequest.first
+ expect(merge_request.notes.count).to eq(2)
+ expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1)
+
+ notes = merge_request.notes.order(:id).to_a
+ start_note = notes.first
+ expect(start_note).to be_a(DiffNote)
+ expect(start_note.note).to eq(@inline_note.note)
+
+ reply_note = notes.last
+ expect(reply_note).to be_a(DiffNote)
+ expect(reply_note.note).to eq(@reply.note)
+ end
+ end
+
context 'issues statuses' do
before do
# HACK: Bitbucket::Representation.const_get('Issue') seems to return ::Issue without this
diff --git a/spec/lib/gitlab/cleanup/remote_uploads_spec.rb b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb
new file mode 100644
index 00000000000..8d03baeb07b
--- /dev/null
+++ b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::Cleanup::RemoteUploads do
+ context 'when object_storage is enabled' do
+ let(:connection) { double }
+ let(:directory) { double }
+ let!(:uploads) do
+ [
+ create(:upload, path: 'dir/file1', store: ObjectStorage::Store::REMOTE),
+ create(:upload, path: 'dir/file2', store: ObjectStorage::Store::LOCAL)
+ ]
+ end
+ let(:remote_files) do
+ [
+ double(key: 'dir/file1'),
+ double(key: 'dir/file2'),
+ double(key: 'dir/file3'),
+ double(key: 'lost_and_found/dir/file3')
+ ]
+ end
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+
+ expect(::Fog::Storage).to receive(:new).and_return(connection)
+
+ expect(connection).to receive(:directories).and_return(double(get: directory))
+ expect(directory).to receive(:files).and_return(remote_files)
+ end
+
+ context 'when dry_run is set to false' do
+ subject { described_class.new.run!(dry_run: false) }
+
+ it 'moves files that are not in uploads table' do
+ expect(remote_files[0]).not_to receive(:copy)
+ expect(remote_files[0]).not_to receive(:destroy)
+ expect(remote_files[1]).to receive(:copy)
+ expect(remote_files[1]).to receive(:destroy)
+ expect(remote_files[2]).to receive(:copy)
+ expect(remote_files[2]).to receive(:destroy)
+ expect(remote_files[3]).not_to receive(:copy)
+ expect(remote_files[3]).not_to receive(:destroy)
+
+ subject
+ end
+ end
+
+ context 'when dry_run is set to true' do
+ subject { described_class.new.run!(dry_run: true) }
+
+ it 'does not move filese' do
+ expect(remote_files[0]).not_to receive(:copy)
+ expect(remote_files[0]).not_to receive(:destroy)
+ expect(remote_files[1]).not_to receive(:copy)
+ expect(remote_files[1]).not_to receive(:destroy)
+ expect(remote_files[2]).not_to receive(:copy)
+ expect(remote_files[2]).not_to receive(:destroy)
+ expect(remote_files[3]).not_to receive(:copy)
+ expect(remote_files[3]).not_to receive(:destroy)
+
+ subject
+ end
+ end
+ end
+
+ context 'when object_storage is not enabled' do
+ it 'does not connect to any storage' do
+ expect(::Fog::Storage).not_to receive(:new)
+
+ subject
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 3512ba6aee5..77b7332a761 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1856,9 +1856,7 @@ describe Ci::Pipeline, :mailer do
context 'when pipeline has builds with test reports' do
before do
- create(:ci_build, pipeline: pipeline, project: project).tap do |build|
- create(:ci_job_artifact, :junit, job: build, project: build.project)
- end
+ create(:ci_build, :test_reports, pipeline: pipeline, project: project)
end
context 'when pipeline status is running' do
@@ -1875,6 +1873,22 @@ describe Ci::Pipeline, :mailer do
end
context 'when pipeline does not have builds with test reports' do
+ before do
+ create(:ci_build, :artifacts, pipeline: pipeline, project: project)
+ end
+
+ let(:pipeline) { create(:ci_pipeline, :success, project: project) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when retried build has test reports' do
+ before do
+ create(:ci_build, :retried, :test_reports, pipeline: pipeline, project: project)
+ end
+
+ let(:pipeline) { create(:ci_pipeline, :success, project: project) }
+
it { is_expected.to be_falsey }
end
end
@@ -1883,14 +1897,12 @@ describe Ci::Pipeline, :mailer do
subject { pipeline.test_reports }
context 'when pipeline has multiple builds with test reports' do
- before do
- create(:ci_build, :success, name: 'rspec', pipeline: pipeline, project: project).tap do |build|
- create(:ci_job_artifact, :junit, job: build, project: build.project)
- end
+ let!(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline, project: project) }
+ let!(:build_java) { create(:ci_build, :success, name: 'java', pipeline: pipeline, project: project) }
- create(:ci_build, :success, name: 'java', pipeline: pipeline, project: project).tap do |build|
- create(:ci_job_artifact, :junit_with_ant, job: build, project: build.project)
- end
+ before do
+ create(:ci_job_artifact, :junit, job: build_rspec, project: project)
+ create(:ci_job_artifact, :junit_with_ant, job: build_java, project: project)
end
it 'returns test reports with collected data' do
@@ -1898,6 +1910,17 @@ describe Ci::Pipeline, :mailer do
expect(subject.success_count).to be(5)
expect(subject.failed_count).to be(2)
end
+
+ context 'when builds are retried' do
+ let!(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) }
+ let!(:build_java) { create(:ci_build, :retried, :success, name: 'java', pipeline: pipeline, project: project) }
+
+ it 'does not take retried builds into account' do
+ expect(subject.total_count).to be(0)
+ expect(subject.success_count).to be(0)
+ expect(subject.failed_count).to be(0)
+ end
+ end
end
context 'when pipeline does not have any builds with test reports' do
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 79f75c0ffa0..97a4c212f1c 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -85,6 +85,14 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
it { is_expected.to be_nil }
end
+
+ context 'when cache was invalidated' do
+ it 'refreshes cache' do
+ expect(ReactiveCachingWorker).to receive(:perform_async).with(CacheTest, 666)
+
+ instance.with_reactive_cache { raise described_class::InvalidateReactiveCache }
+ end
+ end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 3ab6a20cd55..6258bfa232f 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1204,10 +1204,21 @@ describe MergeRequest do
it 'returns status and data' do
expect_any_instance_of(Ci::CompareTestReportsService)
- .to receive(:execute).with(base_pipeline.iid, head_pipeline.iid)
+ .to receive(:execute).with(base_pipeline, head_pipeline).and_call_original
subject
end
+
+ context 'when cached results is not latest' do
+ before do
+ allow_any_instance_of(Ci::CompareTestReportsService)
+ .to receive(:latest?).and_return(false)
+ end
+
+ it 'raises and InvalidateReactiveCache error' do
+ expect { subject }.to raise_error(ReactiveCaching::InvalidateReactiveCache)
+ end
+ end
end
end
diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb
new file mode 100644
index 00000000000..8f795bb561e
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_reference_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/add_reference'
+
+describe RuboCop::Cop::Migration::AddReference do
+ include CopHelper
+
+ let(:cop) { described_class.new }
+
+ context 'outside of a migration' do
+ it 'does not register any offenses' do
+ expect_no_offenses(<<~RUBY)
+ def up
+ add_reference(:projects, :users)
+ end
+ RUBY
+ end
+ end
+
+ context 'in a migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when using add_reference without index' do
+ expect_offense(<<~RUBY)
+ call do
+ add_reference(:projects, :users)
+ ^^^^^^^^^^^^^ `add_reference` requires `index: true`
+ end
+ RUBY
+ end
+
+ it 'registers an offense when using add_reference index disabled' do
+ expect_offense(<<~RUBY)
+ def up
+ add_reference(:projects, :users, index: false)
+ ^^^^^^^^^^^^^ `add_reference` requires `index: true`
+ end
+ RUBY
+ end
+
+ it 'does not register an offense when using add_reference with index enabled' do
+ expect_no_offenses(<<~RUBY)
+ def up
+ add_reference(:projects, :users, index: true)
+ end
+ RUBY
+ end
+ end
+end
diff --git a/spec/services/ci/compare_test_reports_service_spec.rb b/spec/services/ci/compare_test_reports_service_spec.rb
index d3bbf17cc5c..a26c970a8f0 100644
--- a/spec/services/ci/compare_test_reports_service_spec.rb
+++ b/spec/services/ci/compare_test_reports_service_spec.rb
@@ -5,7 +5,7 @@ describe Ci::CompareTestReportsService do
let(:project) { create(:project, :repository) }
describe '#execute' do
- subject { service.execute(base_pipeline&.iid, head_pipeline.iid) }
+ subject { service.execute(base_pipeline, head_pipeline) }
context 'when head pipeline has test reports' do
let!(:base_pipeline) { nil }
@@ -42,4 +42,34 @@ describe Ci::CompareTestReportsService do
end
end
end
+
+ describe '#latest?' do
+ subject { service.latest?(base_pipeline, head_pipeline, data) }
+
+ let!(:base_pipeline) { nil }
+ let!(:head_pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
+ let!(:key) { service.send(:key, base_pipeline, head_pipeline) }
+
+ context 'when cache key is latest' do
+ let(:data) { { key: key } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when cache key is outdated' do
+ before do
+ head_pipeline.update_column(:updated_at, 10.minutes.ago)
+ end
+
+ let(:data) { { key: key } }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when cache key is empty' do
+ let(:data) { { key: nil } }
+
+ it { is_expected.to be_falsy }
+ end
+ end
end
diff --git a/spec/services/milestones/update_service_spec.rb b/spec/services/milestones/update_service_spec.rb
new file mode 100644
index 00000000000..3b91442c0ba
--- /dev/null
+++ b/spec/services/milestones/update_service_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Milestones::UpdateService do
+ let(:project) { create(:project) }
+ let(:user) { build(:user) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ describe '#execute' do
+ context "valid params" do
+ let(:inner_service) { double(:service) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ subject { described_class.new(project, user, { title: 'new_title' }).execute(milestone) }
+
+ it { expect(subject).to be_valid }
+ it { expect(subject.title).to eq('new_title') }
+
+ context 'state_event is activate' do
+ it 'calls ReopenService' do
+ expect(Milestones::ReopenService).to receive(:new).with(project, user, {}).and_return(inner_service)
+ expect(inner_service).to receive(:execute).with(milestone)
+
+ described_class.new(project, user, { state_event: 'activate' }).execute(milestone)
+ end
+ end
+
+ context 'state_event is close' do
+ it 'calls ReopenService' do
+ expect(Milestones::CloseService).to receive(:new).with(project, user, {}).and_return(inner_service)
+ expect(inner_service).to receive(:execute).with(milestone)
+
+ described_class.new(project, user, { state_event: 'close' }).execute(milestone)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 0fd37c95e42..b1290fd0d47 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -145,7 +145,9 @@ describe Notes::CreateService do
let(:note_text) { %(HELLO\n/close\n/assign @#{user.username}\nWORLD) }
it 'saves the note and does not alter the note text' do
- expect_any_instance_of(Issues::UpdateService).to receive(:execute).and_call_original
+ service = double(:service)
+ allow(Issues::UpdateService).to receive(:new).and_return(service)
+ expect(service).to receive(:execute)
note = described_class.new(project, user, opts.merge(note: note_text)).execute
diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb
index 737863ea411..6d29a97c56d 100644
--- a/spec/support/shared_examples/services/boards/issues_move_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_move_service.rb
@@ -4,7 +4,9 @@ shared_examples 'issues move service' do |group|
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
it 'delegates the label changes to Issues::UpdateService' do
- expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once
+ service = double(:service)
+ expect(Issues::UpdateService).to receive(:new).and_return(service)
+ expect(service).to receive(:execute).with(issue).once
described_class.new(parent, user, params).execute(issue)
end
diff --git a/vendor/Dockerfile/Node-alpine.Dockerfile b/vendor/Dockerfile/Node-alpine.Dockerfile
index 9776b1336b5..5b9b495644a 100644
--- a/vendor/Dockerfile/Node-alpine.Dockerfile
+++ b/vendor/Dockerfile/Node-alpine.Dockerfile
@@ -1,14 +1,15 @@
-FROM node:7.9-alpine
+FROM node:8.11-alpine
WORKDIR /usr/src/app
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
+
COPY package.json /usr/src/app/
-RUN npm install && npm cache clean
-COPY . /usr/src/app
+RUN npm install
-CMD [ "npm", "start" ]
+COPY . /usr/src/app
# replace this with your application's default port
EXPOSE 8888
+CMD [ "npm", "start" ]
diff --git a/vendor/Dockerfile/Node.Dockerfile b/vendor/Dockerfile/Node.Dockerfile
index 7e936d5e887..e8b64b3a6e4 100644
--- a/vendor/Dockerfile/Node.Dockerfile
+++ b/vendor/Dockerfile/Node.Dockerfile
@@ -1,14 +1,15 @@
-FROM node:7.9
+FROM node:8.11
WORKDIR /usr/src/app
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
+
COPY package.json /usr/src/app/
-RUN npm install && npm cache clean
-COPY . /usr/src/app
+RUN npm install
-CMD [ "npm", "start" ]
+COPY . /usr/src/app
# replace this with your application's default port
EXPOSE 8888
+CMD [ "npm", "start" ] \ No newline at end of file
diff --git a/vendor/Dockerfile/Ruby-alpine.Dockerfile b/vendor/Dockerfile/Ruby-alpine.Dockerfile
index 9db4e2130f2..dffe9a65116 100644
--- a/vendor/Dockerfile/Ruby-alpine.Dockerfile
+++ b/vendor/Dockerfile/Ruby-alpine.Dockerfile
@@ -1,8 +1,8 @@
-FROM ruby:2.4-alpine
+FROM ruby:2.5-alpine
# Edit with nodejs, mysql-client, postgresql-client, sqlite3, etc. for your needs.
# Or delete entirely if not needed.
-RUN apk --no-cache add nodejs postgresql-client
+RUN apk --no-cache add nodejs postgresql-client tzdata
# throw errors if Gemfile has been modified since Gemfile.lock
RUN bundle config --global frozen 1
@@ -11,7 +11,10 @@ RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY Gemfile Gemfile.lock /usr/src/app/
-RUN bundle install
+# Install build dependencies - required for gems with native dependencies
+RUN apk add --no-cache --virtual build-deps build-base postgresql-dev && \
+ bundle install && \
+ apk del build-deps
COPY . /usr/src/app
@@ -21,4 +24,4 @@ COPY . /usr/src/app
# For Rails
EXPOSE 3000
-CMD ["rails", "server"]
+CMD ["bundle", "exec", "rails", "server"]
diff --git a/vendor/Dockerfile/Ruby.Dockerfile b/vendor/Dockerfile/Ruby.Dockerfile
index feb880ee4b2..289ed57bfa2 100644
--- a/vendor/Dockerfile/Ruby.Dockerfile
+++ b/vendor/Dockerfile/Ruby.Dockerfile
@@ -1,4 +1,4 @@
-FROM ruby:2.4
+FROM ruby:2.5
# Edit with nodejs, mysql-client, postgresql-client, sqlite3, etc. for your needs.
# Or delete entirely if not needed.
@@ -24,4 +24,4 @@ COPY . /usr/src/app
# For Rails
EXPOSE 3000
-CMD ["rails", "server", "-b", "0.0.0.0"]
+CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
diff --git a/vendor/gitignore/Autotools.gitignore b/vendor/gitignore/Autotools.gitignore
index 96d6ed2cfea..f4f545c9ca4 100644
--- a/vendor/gitignore/Autotools.gitignore
+++ b/vendor/gitignore/Autotools.gitignore
@@ -16,6 +16,8 @@ autom4te.cache
/compile
/config.guess
/config.h.in
+/config.log
+/config.status
/config.sub
/configure
/configure.scan
diff --git a/vendor/gitignore/Laravel.gitignore b/vendor/gitignore/Laravel.gitignore
index a4854bef534..67e2146f2bc 100644
--- a/vendor/gitignore/Laravel.gitignore
+++ b/vendor/gitignore/Laravel.gitignore
@@ -1,6 +1,7 @@
vendor/
node_modules/
npm-debug.log
+yarn-error.log
# Laravel 4 specific
bootstrap/compiled.php
@@ -10,11 +11,7 @@ app/storage/
public/storage
public/hot
storage/*.key
-.env.*.php
-.env.php
.env
Homestead.yaml
Homestead.json
-
-# Rocketeer PHP task runner and deployment package. https://github.com/rocketeers/rocketeer
-.rocketeer/
+/.vagrant
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index f431ddc7cf5..94b41b913fb 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -59,7 +59,7 @@ StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
-*_i.h
+*_h.h
*.ilk
*.meta
*.obj
@@ -327,3 +327,6 @@ ASALocalRun/
# MFractors (Xamarin productivity tool) working folder
.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
diff --git a/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml b/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml
index d5ee7ed2c13..5f9c9b2c965 100644
--- a/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Maven.gitlab-ci.yml
@@ -17,7 +17,7 @@
variables:
# This will supress any download for dependencies and plugins or upload messages which would clutter the console log.
# `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
- MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
+ MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
# As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
# when running from the command line.
# `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
index ff7bdd32239..93cb31f48c0 100644
--- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -1,6 +1,6 @@
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/ruby/tags/
-image: "ruby:2.4"
+image: "ruby:2.5"
# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 7503160baa0..a462daf3067 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -7,28 +7,29 @@
@babel/template,7.0.0-beta.44,MIT
@babel/traverse,7.0.0-beta.44,MIT
@babel/types,7.0.0-beta.44,MIT
-@gitlab-org/gitlab-svgs,1.25.0,SEE LICENSE IN LICENSE
+@gitlab-org/gitlab-svgs,1.27.0,SEE LICENSE IN LICENSE
+@gitlab-org/gitlab-ui,1.0.5,UNKNOWN
@sindresorhus/is,0.7.0,MIT
@types/jquery,2.0.48,MIT
@vue/component-compiler-utils,1.2.1,MIT
-@webassemblyjs/ast,1.5.10,MIT
-@webassemblyjs/floating-point-hex-parser,1.5.10,MIT
-@webassemblyjs/helper-api-error,1.5.10,MIT
-@webassemblyjs/helper-buffer,1.5.10,MIT
-@webassemblyjs/helper-code-frame,1.5.10,MIT
-@webassemblyjs/helper-fsm,1.5.10,ISC
-@webassemblyjs/helper-module-context,1.5.10,MIT
-@webassemblyjs/helper-wasm-bytecode,1.5.10,MIT
-@webassemblyjs/helper-wasm-section,1.5.10,MIT
-@webassemblyjs/ieee754,1.5.10,Unknown
-@webassemblyjs/leb128,1.5.10,Apache 2.0
-@webassemblyjs/utf8,1.5.10,MIT
-@webassemblyjs/wasm-edit,1.5.10,MIT
-@webassemblyjs/wasm-gen,1.5.10,MIT
-@webassemblyjs/wasm-opt,1.5.10,MIT
-@webassemblyjs/wasm-parser,1.5.10,MIT
-@webassemblyjs/wast-parser,1.5.10,MIT
-@webassemblyjs/wast-printer,1.5.10,MIT
+@webassemblyjs/ast,1.5.13,MIT
+@webassemblyjs/floating-point-hex-parser,1.5.13,MIT
+@webassemblyjs/helper-api-error,1.5.13,MIT
+@webassemblyjs/helper-buffer,1.5.13,MIT
+@webassemblyjs/helper-code-frame,1.5.13,MIT
+@webassemblyjs/helper-fsm,1.5.13,ISC
+@webassemblyjs/helper-module-context,1.5.13,MIT
+@webassemblyjs/helper-wasm-bytecode,1.5.13,MIT
+@webassemblyjs/helper-wasm-section,1.5.13,MIT
+@webassemblyjs/ieee754,1.5.13,MIT
+@webassemblyjs/leb128,1.5.13,MIT
+@webassemblyjs/utf8,1.5.13,MIT
+@webassemblyjs/wasm-edit,1.5.13,MIT
+@webassemblyjs/wasm-gen,1.5.13,MIT
+@webassemblyjs/wasm-opt,1.5.13,MIT
+@webassemblyjs/wasm-parser,1.5.13,MIT
+@webassemblyjs/wast-parser,1.5.13,MIT
+@webassemblyjs/wast-printer,1.5.13,MIT
RedCloth,4.3.2,MIT
abbrev,1.0.9,ISC
abbrev,1.1.1,ISC
@@ -36,6 +37,7 @@ accepts,1.3.4,MIT
ace-rails-ap,4.1.2,MIT
acorn,3.3.0,MIT
acorn,5.6.2,MIT
+acorn,5.7.1,MIT
acorn-dynamic-import,3.0.0,MIT
acorn-jsx,3.0.1,MIT
actionmailer,4.2.10,MIT
@@ -50,31 +52,29 @@ addressable,2.5.2,Apache 2.0
addressparser,1.0.1,MIT
aes_key_wrap,1.0.1,MIT
after,0.8.2,MIT
-agent-base,2.1.1,MIT
+agent-base,4.2.1,MIT
ajv,5.5.2,MIT
ajv,6.1.1,MIT
ajv-keywords,2.1.1,MIT
ajv-keywords,3.1.0,MIT
akismet,2.0.0,MIT
align-text,0.1.4,MIT
-alphanum-sort,1.0.2,MIT
amdefine,1.0.1,BSD-3-Clause OR MIT
amqplib,0.5.2,MIT
ansi-align,2.0.0,ISC
+ansi-escapes,1.4.0,MIT
ansi-escapes,3.0.0,MIT
ansi-html,0.0.7,Apache 2.0
ansi-regex,2.1.1,MIT
ansi-regex,3.0.0,MIT
ansi-styles,2.2.1,MIT
ansi-styles,3.2.1,MIT
-anymatch,1.3.2,ISC
anymatch,2.0.0,ISC
append-transform,0.4.0,MIT
aproba,1.2.0,ISC
are-we-there-yet,1.1.4,ISC
arel,6.0.4,MIT
argparse,1.0.9,MIT
-arr-diff,2.0.0,MIT
arr-diff,4.0.0,MIT
arr-flatten,1.1.0,MIT
arr-union,3.1.0,MIT
@@ -102,8 +102,8 @@ asset_sync,2.4.0,MIT
assign-symbols,1.0.0,MIT
ast-types,0.11.3,MIT
async,1.5.2,MIT
-async,2.1.5,MIT
async,2.6.0,MIT
+async,2.6.1,MIT
async-each,1.0.1,MIT
async-limiter,1.0.0,MIT
asynckit,0.4.0,MIT
@@ -111,7 +111,6 @@ atob,2.0.3,(MIT OR Apache-2.0)
atomic,1.1.99,Apache 2.0
attr_encrypted,3.1.0,MIT
attr_required,1.0.0,MIT
-autoprefixer,6.7.7,MIT
autosize,4.0.0,MIT
aws-sign2,0.6.0,Apache 2.0
aws-sign2,0.7.0,Apache 2.0
@@ -138,7 +137,7 @@ babel-helper-regex,6.26.0,MIT
babel-helper-remap-async-to-generator,6.24.1,MIT
babel-helper-replace-supers,6.24.1,MIT
babel-helpers,6.24.1,MIT
-babel-loader,7.1.4,MIT
+babel-loader,7.1.5,MIT
babel-messages,6.23.0,MIT
babel-plugin-check-es2015-constants,6.22.0,MIT
babel-plugin-istanbul,4.1.6,New BSD
@@ -182,6 +181,7 @@ babel-plugin-transform-exponentiation-operator,6.24.1,MIT
babel-plugin-transform-object-rest-spread,6.23.0,MIT
babel-plugin-transform-regenerator,6.26.0,MIT
babel-plugin-transform-strict-mode,6.24.1,MIT
+babel-polyfill,6.23.0,MIT
babel-preset-es2015,6.24.1,MIT
babel-preset-es2016,6.24.1,MIT
babel-preset-es2017,6.24.1,MIT
@@ -197,7 +197,6 @@ babosa,1.0.2,MIT
babylon,6.18.0,MIT
babylon,7.0.0-beta.44,MIT
backo2,1.0.2,MIT
-balanced-match,0.4.2,MIT
balanced-match,1.0.0,MIT
base,0.11.2,MIT
base32,0.3.2,MIT
@@ -213,8 +212,9 @@ better-assert,1.0.2,MIT
bfj-node4,5.2.1,MIT
big.js,3.1.3,MIT
binary-extensions,1.11.0,MIT
+binaryextensions,2.1.1,MIT
bindata,2.4.3,ruby
-bitsyntax,0.0.4,Unknown
+bitsyntax,0.0.4,UNKNOWN
bl,1.1.2,MIT
blackst0ne-mermaid,7.1.0-fixed,MIT
blob,0.0.4,MIT*
@@ -226,11 +226,12 @@ boom,2.10.1,New BSD
boom,4.3.1,New BSD
boom,5.2.0,New BSD
bootstrap,4.1.1,MIT
+bootstrap,4.1.2,MIT
+bootstrap-vue,2.0.0-rc.11,MIT
bootstrap_form,2.7.0,MIT
boxen,1.3.0,MIT
brace-expansion,1.1.11,MIT
braces,0.1.5,MIT
-braces,1.8.5,MIT
braces,2.3.1,MIT
brorand,1.1.0,MIT
browser,2.2.0,MIT
@@ -240,7 +241,6 @@ browserify-des,1.0.0,MIT
browserify-rsa,4.0.1,MIT
browserify-sign,4.0.4,ISC
browserify-zlib,0.2.0,MIT
-browserslist,1.7.7,MIT
buffer,4.9.1,MIT
buffer-from,1.0.0,MIT
buffer-indexof,1.1.0,MIT
@@ -263,8 +263,6 @@ camelcase,1.2.1,MIT
camelcase,2.1.1,MIT
camelcase,4.1.0,MIT
camelcase-keys,2.1.0,MIT
-caniuse-api,1.6.1,MIT
-caniuse-db,1.0.30000649,CC-BY-4.0
capture-stack-trace,1.0.0,MIT
carrierwave,1.2.3,MIT
caseless,0.11.0,Apache 2.0
@@ -274,22 +272,22 @@ center-align,0.1.3,MIT
chalk,1.1.3,MIT
chalk,2.4.1,MIT
chardet,0.4.2,MIT
+chardet,0.5.0,MIT
charenc,0.0.2,New BSD
charlock_holmes,0.7.6,MIT
chart.js,1.0.2,MIT
check-types,7.3.0,MIT
-chokidar,1.7.0,MIT
chokidar,2.0.2,MIT
+chokidar,2.0.4,MIT
chownr,1.0.1,ISC
-chrome-trace-event,0.1.2,MIT
+chrome-trace-event,1.0.0,MIT
chronic,0.10.2,MIT
chronic_duration,0.10.6,MIT
chunky_png,1.3.5,MIT
cipher-base,1.0.4,MIT
circular-json,0.3.3,MIT
-circular-json,0.5.1,MIT
+circular-json,0.5.5,MIT
citrus,3.0.2,MIT
-clap,1.1.3,MIT
class-utils,0.3.6,MIT
classlist-polyfill,1.2.0,Unlicense
cli-boxes,1.0.0,MIT
@@ -298,19 +296,16 @@ cli-width,2.1.0,ISC
clipboard,1.7.1,MIT
cliui,2.1.0,ISC
cliui,4.0.0,ISC
-clone,1.0.3,MIT
clone-response,1.0.2,MIT
-co,3.0.6,MIT
co,4.6.0,MIT
-coa,1.0.1,MIT
code-point-at,1.1.0,MIT
+codesandbox-api,0.0.18,MIT
+codesandbox-import-util-types,1.2.11,UNKNOWN
+codesandbox-import-utils,1.2.11,UNKNOWN
coercible,1.0.0,MIT
collection-visit,1.0.0,MIT
-color,0.11.4,MIT
color-convert,1.9.1,MIT
color-name,1.1.2,MIT
-color-string,0.3.0,MIT
-colormin,1.1.2,MIT
colors,1.1.2,MIT
combine-lists,1.0.1,MIT
combined-stream,1.0.6,MIT
@@ -361,28 +356,36 @@ cryptiles,2.0.5,New BSD
cryptiles,3.1.2,New BSD
crypto-browserify,3.12.0,MIT
crypto-random-string,1.0.0,MIT
-css-color-names,0.0.4,MIT
-css-loader,0.28.11,MIT
+css-loader,1.0.0,MIT
css-selector-tokenizer,0.7.0,MIT
css_parser,1.5.0,MIT
cssesc,0.1.0,MIT
-cssnano,3.10.0,MIT
-csso,2.3.2,MIT
currently-unhandled,0.4.1,MIT
custom-event,1.0.1,MIT
cyclist,0.2.2,MIT*
d3,3.5.17,New BSD
+d3,4.12.2,New BSD
d3-array,1.2.1,New BSD
d3-axis,1.0.8,New BSD
d3-brush,1.0.4,New BSD
+d3-chord,1.0.4,New BSD
d3-collection,1.0.4,New BSD
d3-color,1.0.3,New BSD
d3-dispatch,1.0.3,New BSD
d3-drag,1.2.1,New BSD
+d3-dsv,1.0.8,New BSD
d3-ease,1.0.3,New BSD
+d3-force,1.1.0,New BSD
d3-format,1.2.1,New BSD
+d3-geo,1.9.1,New BSD
+d3-hierarchy,1.1.5,New BSD
d3-interpolate,1.1.6,New BSD
d3-path,1.0.5,New BSD
+d3-polygon,1.0.3,New BSD
+d3-quadtree,1.0.3,New BSD
+d3-queue,3.0.7,New BSD
+d3-random,1.1.0,New BSD
+d3-request,1.0.6,New BSD
d3-scale,1.0.7,New BSD
d3-selection,1.2.0,New BSD
d3-shape,1.2.0,New BSD
@@ -390,6 +393,8 @@ d3-time,1.0.8,New BSD
d3-time-format,2.1.1,New BSD
d3-timer,1.0.7,New BSD
d3-transition,1.1.1,New BSD
+d3-voronoi,1.1.2,New BSD
+d3-zoom,1.7.1,New BSD
dagre-d3-renderer,0.4.24,MIT
dagre-layout,0.8.0,MIT
dashdash,1.14.1,MIT
@@ -398,7 +403,6 @@ date-format,1.2.0,MIT
date-now,0.1.4,MIT
dateformat,3.0.3,MIT
de-indent,1.0.2,MIT
-debug,2.2.0,MIT
debug,2.6.8,MIT
debug,2.6.9,MIT
debug,3.1.0,MIT
@@ -418,7 +422,6 @@ define-properties,1.1.2,MIT
define-property,0.2.5,MIT
define-property,1.0.0,MIT
define-property,2.0.2,MIT
-defined,1.0.0,MIT
degenerator,1.0.4,MIT
del,2.2.2,MIT
del,3.0.0,MIT
@@ -426,6 +429,7 @@ delayed-stream,1.0.0,MIT
delegate,3.1.2,MIT
delegates,1.0.0,MIT
depd,1.1.1,MIT
+depd,1.1.2,MIT
des.js,1.0.0,MIT
descendants_tracker,0.0.4,MIT
destroy,1.0.4,MIT
@@ -465,14 +469,15 @@ duplexer3,0.1.4,New BSD
duplexify,3.5.3,MIT
ecc-jsbn,0.1.1,MIT
ed25519,1.2.4,MIT
+editions,1.3.4,MIT
ee-first,1.1.1,MIT
ejs,2.5.9,Apache 2.0
-electron-to-chromium,1.3.3,ISC
elliptic,6.4.0,MIT
email_reply_trimmer,0.1.6,MIT
emoji-unicode-version,0.2.1,MIT
emojis-list,2.1.0,MIT
encodeurl,1.0.2,MIT
+encoding,0.1.12,MIT
encryptor,3.0.0,MIT
end-of-stream,1.4.1,MIT
engine.io,3.1.5,MIT
@@ -480,6 +485,7 @@ engine.io-client,3.1.5,MIT
engine.io-parser,2.1.2,MIT
enhanced-resolve,0.9.1,MIT
enhanced-resolve,4.0.0,MIT
+enhanced-resolve,4.1.0,MIT
ent,2.2.0,MIT
entities,1.1.1,Simplified BSD
equalizer,0.0.11,MIT
@@ -490,6 +496,8 @@ erubis,2.7.0,MIT
es-abstract,1.10.0,MIT
es-to-primitive,1.1.1,MIT
es6-promise,3.0.2,MIT
+es6-promise,4.2.4,MIT
+es6-promisify,5.0.0,MIT
escape-html,1.0.3,MIT
escape-string-regexp,1.0.5,MIT
escape_utils,1.1.1,MIT
@@ -530,10 +538,8 @@ excon,0.62.0,MIT
execa,0.7.0,MIT
execjs,2.6.0,MIT
expand-braces,0.1.2,MIT
-expand-brackets,0.1.5,MIT
expand-brackets,2.1.4,MIT
expand-range,0.1.1,MIT
-expand-range,1.8.2,MIT
exports-loader,0.7.0,MIT
express,4.16.2,MIT
expression_parser,0.9.0,MIT
@@ -541,7 +547,7 @@ extend,3.0.1,MIT
extend-shallow,2.0.1,MIT
extend-shallow,3.0.2,MIT
external-editor,2.2.0,MIT
-extglob,0.3.2,MIT
+external-editor,3.0.0,MIT
extglob,2.0.4,MIT
extsprintf,1.3.0,MIT
extsprintf,1.4.0,MIT
@@ -561,10 +567,8 @@ figures,2.0.0,MIT
file-entry-cache,2.0.0,MIT
file-loader,1.1.11,MIT
file-uri-to-path,1.0.0,MIT
-filename-regex,2.0.1,MIT
fileset,2.0.3,MIT
filesize,3.6.0,New BSD
-fill-range,2.2.3,MIT
fill-range,4.0.0,MIT
finalhandler,1.1.0,MIT
find-cache-dir,1.0.0,MIT
@@ -572,7 +576,6 @@ find-root,1.1.0,MIT
find-up,1.1.2,MIT
find-up,2.1.0,MIT
flat-cache,1.2.2,MIT
-flatten,1.0.2,MIT
flipper,0.13.0,MIT
flipper-active_record,0.13.0,MIT
flipper-active_support_cache_store,0.13.0,MIT
@@ -591,13 +594,12 @@ follow-redirects,1.0.0,MIT
follow-redirects,1.2.6,MIT
font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License"
for-in,1.0.2,MIT
-for-own,0.1.5,MIT
foreach,2.0.5,MIT
forever-agent,0.6.1,Apache 2.0
form-data,2.0.0,MIT
-form-data,2.1.4,MIT
form-data,2.3.2,MIT
formatador,0.2.5,MIT
+formdata-polyfill,3.0.11,MIT
forwarded,0.1.2,MIT
fragment-cache,0.2.1,MIT
fresh,0.5.2,MIT
@@ -620,13 +622,13 @@ generate-object-property,1.2.0,MIT
get-caller-file,1.0.2,ISC
get-stdin,4.0.1,MIT
get-stream,3.0.0,MIT
-get-uri,2.0.1,MIT
+get-uri,2.0.2,MIT
get-value,2.0.6,MIT
get_process_mem,0.2.0,MIT
getpass,0.1.7,MIT
gettext_i18n_rails,1.8.0,MIT
gettext_i18n_rails_js,1.3.0,MIT
-gitaly-proto,0.105.0,MIT
+gitaly-proto,0.112.0,MIT
github-linguist,5.3.3,MIT
github-markup,1.7.0,MIT
gitlab-flowdock-git-hook,1.0.1,MIT
@@ -637,8 +639,6 @@ gitlab-markup,1.6.4,MIT
gitlab_omniauth-ldap,2.0.4,MIT
glob,5.0.15,ISC
glob,7.1.2,ISC
-glob-base,0.3.0,MIT
-glob-parent,2.0.0,ISC
glob-parent,3.1.0,ISC
global-dirs,0.1.1,MIT
global-modules-path,2.1.0,Apache 2.0
@@ -660,16 +660,17 @@ gpgme,2.0.13,LGPL-2.1+
graceful-fs,4.1.11,ISC
grape,1.0.3,MIT
grape-entity,0.7.1,MIT
-grape-path-helpers,1.0.5,MIT
+grape-path-helpers,1.0.6,MIT
grape_logging,1.7.0,MIT
graphiql-rails,1.4.10,MIT
graphlib,2.1.1,MIT
graphql,1.8.1,MIT
grpc,1.11.0,Apache 2.0
gzip-size,4.1.0,MIT
-hamlit,2.6.1,MIT
+hamlit,2.8.8,MIT
handle-thing,1.2.5,MIT
handlebars,4.0.6,MIT
+hangouts-chat,0.0.5,MIT
har-schema,2.0.0,ISC
har-validator,2.0.6,ISC
har-validator,5.0.3,ISC
@@ -704,9 +705,8 @@ hoek,4.2.1,New BSD
home-or-tmp,2.0.0,MIT
hosted-git-info,2.2.0,ISC
hpack.js,2.1.6,MIT
-html-comment-regex,1.1.1,MIT
html-entities,1.2.0,MIT
-html-pipeline,2.8.3,MIT
+html-pipeline,2.8.4,MIT
html2text,0.2.0,MIT
htmlentities,4.3.4,MIT
htmlparser2,3.9.2,MIT
@@ -715,9 +715,10 @@ http-cache-semantics,3.8.1,Simplified BSD
http-cookie,1.0.3,MIT
http-deceiver,1.2.7,MIT
http-errors,1.6.2,MIT
+http-errors,1.6.3,MIT
http-form_data,1.0.3,MIT
http-proxy,1.16.2,MIT
-http-proxy-agent,1.0.0,MIT
+http-proxy-agent,2.1.0,MIT
http-proxy-middleware,0.18.0,MIT
http-signature,1.1.1,MIT
http-signature,1.2.0,MIT
@@ -727,7 +728,7 @@ httpclient,2.8.3,ruby
httpntlm,1.6.1,MIT
httpreq,0.4.24,MIT
https-browserify,1.0.0,MIT
-https-proxy-agent,1.0.0,MIT
+https-proxy-agent,2.2.1,MIT
i18n,0.9.5,MIT
icalendar,2.4.1,ruby
ice_nine,0.11.2,MIT
@@ -749,25 +750,24 @@ imurmurhash,0.1.4,MIT
indent-string,2.1.0,MIT
indexes-of,1.0.1,MIT
indexof,0.0.1,MIT*
-inflection,1.10.0,MIT
+inflection,1.12.0,MIT
inflection,1.3.8,MIT
inflight,1.0.6,ISC
influxdb,0.2.3,MIT
inherits,2.0.1,ISC
inherits,2.0.3,ISC
ini,1.3.5,ISC
+inquirer,3.0.6,MIT
inquirer,3.3.0,MIT
-inquirer,5.2.0,MIT
+inquirer,6.0.0,MIT
internal-ip,1.2.0,MIT
interpret,1.1.0,MIT
into-stream,3.1.0,MIT
invariant,2.2.2,New BSD
invert-kv,1.0.0,MIT
-ip,1.0.1,MIT
ip,1.1.5,MIT
ipaddr.js,1.6.0,MIT
ipaddress,0.8.3,MIT
-is-absolute-url,2.1.0,MIT
is-accessor-descriptor,0.1.6,MIT
is-accessor-descriptor,1.0.0,MIT
is-arrayish,0.2.1,MIT
@@ -780,16 +780,12 @@ is-data-descriptor,1.0.0,MIT
is-date-object,1.0.1,MIT
is-descriptor,0.1.6,MIT
is-descriptor,1.0.2,MIT
-is-dotfile,1.0.3,MIT
-is-equal-shallow,0.1.3,MIT
is-extendable,0.1.1,MIT
is-extendable,1.0.1,MIT
-is-extglob,1.0.0,MIT
is-extglob,2.1.1,MIT
is-finite,1.0.2,MIT
is-fullwidth-code-point,1.0.0,MIT
is-fullwidth-code-point,2.0.0,MIT
-is-glob,2.0.1,MIT
is-glob,3.1.0,MIT
is-glob,4.0.0,MIT
is-installed-globally,0.1.0,MIT
@@ -797,7 +793,6 @@ is-my-ip-valid,1.0.0,MIT
is-my-json-valid,2.17.2,MIT
is-npm,1.0.0,MIT
is-number,0.1.1,MIT
-is-number,2.1.0,MIT
is-number,3.0.0,MIT
is-number,4.0.0,MIT
is-obj,1.0.1,MIT
@@ -808,8 +803,6 @@ is-path-in-cwd,1.0.0,MIT
is-path-inside,1.0.0,MIT
is-plain-obj,1.1.0,MIT
is-plain-object,2.0.4,MIT
-is-posix-bracket,0.1.1,MIT
-is-primitive,2.0.0,MIT
is-promise,2.1.0,MIT
is-property,1.0.2,MIT
is-redirect,1.0.0,MIT
@@ -817,7 +810,6 @@ is-regex,1.0.4,MIT
is-resolvable,1.0.0,MIT
is-retry-allowed,1.1.0,MIT
is-stream,1.1.0,MIT
-is-svg,2.1.0,MIT
is-symbol,1.0.1,MIT
is-typedarray,1.0.0,MIT
is-utf8,0.2.1,MIT
@@ -840,8 +832,10 @@ istanbul-lib-instrument,1.10.1,New BSD
istanbul-lib-report,1.1.2,New BSD
istanbul-lib-source-maps,1.2.2,New BSD
istanbul-reports,1.1.3,New BSD
+istextorbinary,2.2.1,MIT
isurl,1.0.0,MIT
jasmine-core,2.9.0,MIT
+jasmine-diff,0.1.3,MIT
jasmine-jquery,2.1.1,MIT
jed,1.1.1,MIT
jira-ruby,1.4.1,MIT
@@ -849,11 +843,9 @@ jquery,3.3.1,MIT
jquery-atwho-rails,1.3.2,MIT
jquery-ujs,1.2.2,MIT
jquery.waitforimages,2.2.0,MIT
-js-base64,2.1.9,New BSD
js-cookie,2.1.3,MIT
js-tokens,3.0.2,MIT
js-yaml,3.11.0,MIT
-js-yaml,3.7.0,MIT
jsbn,0.1.1,MIT
jsesc,0.5.0,MIT
jsesc,1.3.0,MIT
@@ -877,13 +869,13 @@ kaminari,1.0.1,MIT
kaminari-actionview,1.0.1,MIT
kaminari-activerecord,1.0.1,MIT
kaminari-core,1.0.1,MIT
-karma,2.0.2,MIT
+karma,2.0.4,MIT
karma-chrome-launcher,2.2.0,MIT
karma-coverage-istanbul-reporter,1.4.2,MIT
-karma-jasmine,1.1.1,MIT
+karma-jasmine,1.1.2,MIT
karma-mocha-reporter,2.2.5,MIT
karma-sourcemap-loader,0.3.7,MIT
-karma-webpack,3.0.0,MIT
+karma-webpack,4.0.0-beta.0,MIT
katex,0.8.3,MIT
keyv,3.0.0,MIT
kgio,2.10.0,LGPL-2.1+
@@ -897,7 +889,6 @@ latest-version,3.1.0,MIT
lazy-cache,1.0.4,MIT
lazy-cache,2.0.2,MIT
lcid,1.0.0,MIT
-leb,0.3.0,Apache 2.0
levn,0.3.0,MIT
libbase64,0.1.0,MIT
libmime,3.0.0,MIT
@@ -915,43 +906,44 @@ lodash,4.17.10,MIT
lodash,4.17.4,MIT
lodash.camelcase,4.3.0,MIT
lodash.clonedeep,4.5.0,MIT
+lodash.debounce,4.0.8,MIT
lodash.escaperegexp,4.1.2,MIT
+lodash.get,4.4.2,MIT
+lodash.isequal,4.5.0,MIT
lodash.kebabcase,4.1.1,MIT
-lodash.memoize,4.1.2,MIT
lodash.mergewith,4.6.0,MIT
lodash.snakecase,4.1.1,MIT
-lodash.uniq,4.5.0,MIT
+lodash.startcase,4.4.0,MIT
lodash.upperfirst,4.3.1,MIT
log-symbols,2.2.0,MIT
-log4js,2.5.3,Apache 2.0
+log4js,2.11.0,Apache 2.0
logging,2.2.2,MIT
loggly,1.1.1,MIT
loglevel,1.4.1,MIT
loglevelnext,1.0.3,MIT
lograge,0.10.0,MIT
long,3.2.0,Apache 2.0
+long,4.0.0,Apache 2.0
longest,1.0.1,MIT
loofah,2.2.2,MIT
loose-envify,1.3.1,MIT
loud-rejection,1.6.0,MIT
lowercase-keys,1.0.0,MIT
lru-cache,2.2.4,MIT
-lru-cache,2.6.5,ISC
lru-cache,4.1.3,ISC
-macaddress,0.2.8,MIT
+lz-string,1.4.4,WTFPL
mail,2.7.0,MIT
mail_room,0.9.1,MIT
mailcomposer,4.0.1,MIT
-mailgun-js,0.7.15,MIT
+mailgun-js,0.18.1,MIT
make-dir,1.2.0,MIT
mamacro,0.0.3,MIT
map-cache,0.2.2,MIT
map-obj,1.0.1,MIT
-map-stream,0.1.0,Unknown
+map-stream,0.1.0,UNKNOWN
map-visit,1.0.0,MIT
marked,0.3.12,MIT
match-at,0.1.1,MIT
-math-expression-evaluator,1.2.16,MIT
md5.js,1.3.4,MIT
media-typer,0.3.0,MIT
mem,1.1.0,MIT
@@ -963,7 +955,6 @@ merge-descriptors,1.0.1,MIT
merge-source-map,1.1.0,MIT
method_source,0.8.2,MIT
methods,1.1.2,MIT
-micromatch,2.3.11,MIT
micromatch,3.1.10,MIT
miller-rabin,4.0.1,MIT
mime,1.4.1,MIT
@@ -996,8 +987,8 @@ monaco-editor-webpack-plugin,1.4.0,MIT
mousetrap,1.4.6,Apache 2.0
mousetrap-rails,1.4.6,"MIT,Apache"
move-concurrently,1.0.1,ISC
-ms,0.7.1,MIT
ms,2.0.0,MIT
+msgpack,1.2.4,Apache 2.0
multi_json,1.13.1,MIT
multi_xml,0.6.0,MIT
multicast-dns,6.1.1,MIT
@@ -1018,6 +1009,7 @@ net-ssh,5.0.1,MIT
netmask,1.0.6,MIT
netrc,0.11.0,MIT
nice-try,1.0.4,MIT
+node-fetch,1.6.3,MIT
node-forge,0.6.33,New BSD
node-libs-browser,2.1.0,MIT
node-pre-gyp,0.10.0,New BSD
@@ -1029,23 +1021,20 @@ nodemailer-shared,1.1.0,MIT
nodemailer-smtp-pool,2.8.2,MIT
nodemailer-smtp-transport,2.7.2,MIT
nodemailer-wellknown,0.1.10,MIT
-nodemon,1.17.3,MIT
-nokogiri,1.8.3,MIT
+nodemon,1.18.2,MIT
+nokogiri,1.8.4,MIT
nokogumbo,1.5.0,Apache 2.0
nopt,1.0.10,MIT
nopt,3.0.6,ISC
nopt,4.0.1,ISC
normalize-package-data,2.4.0,Simplified BSD
normalize-path,2.1.1,MIT
-normalize-range,0.1.2,MIT
-normalize-url,1.9.1,MIT
normalize-url,2.0.1,MIT
npm-bundled,1.0.3,ISC
npm-packlist,1.1.10,ISC
npm-run-path,2.0.2,MIT
npmlog,4.1.2,ISC
null-check,1.0.0,MIT
-num2fraction,1.2.2,MIT
number-is-nan,1.0.1,MIT
numerizer,0.1.1,MIT
oauth,0.5.4,MIT
@@ -1056,7 +1045,6 @@ object-component,0.0.3,MIT*
object-copy,0.1.0,MIT
object-keys,1.0.11,MIT
object-visit,1.0.1,MIT
-object.omit,2.0.1,MIT
object.pick,1.3.0,MIT
obuf,1.1.1,MIT
octokit,4.9.0,MIT
@@ -1082,7 +1070,9 @@ on-finished,2.3.0,MIT
on-headers,1.0.1,MIT
once,1.4.0,ISC
onetime,2.0.1,MIT
+opencollective,1.0.3,MIT
opener,1.4.3,(WTFPL OR MIT)
+opn,4.0.2,MIT
opn,5.2.0,MIT
optimist,0.6.1,MIT
optionator,0.8.2,MIT
@@ -1103,13 +1093,12 @@ p-locate,2.0.0,MIT
p-map,1.1.1,MIT
p-timeout,2.0.1,MIT
p-try,1.0.0,MIT
-pac-proxy-agent,1.1.0,MIT
-pac-resolver,2.0.0,MIT
+pac-proxy-agent,2.0.2,MIT
+pac-resolver,3.0.0,MIT
package-json,4.0.1,MIT
pako,1.0.6,(MIT AND Zlib)
parallel-transform,1.1.0,MIT
parse-asn1,5.1.0,ISC
-parse-glob,3.0.4,MIT
parse-json,2.2.0,MIT
parseqs,0.0.5,MIT
parseuri,0.0.5,MIT
@@ -1151,47 +1140,19 @@ popper.js,1.14.3,MIT
portfinder,1.0.13,MIT
posix-character-classes,0.1.1,MIT
posix-spawn,0.3.13,MIT
-postcss,5.2.16,MIT
postcss,6.0.22,MIT
-postcss-calc,5.3.1,MIT
-postcss-colormin,2.2.2,MIT
-postcss-convert-values,2.6.1,MIT
-postcss-discard-comments,2.0.4,MIT
-postcss-discard-duplicates,2.1.0,MIT
-postcss-discard-empty,2.1.0,MIT
-postcss-discard-overridden,0.1.1,MIT
-postcss-discard-unused,2.2.3,MIT
-postcss-filter-plugins,2.0.2,MIT
-postcss-merge-idents,2.1.7,MIT
-postcss-merge-longhand,2.0.2,MIT
-postcss-merge-rules,2.1.2,MIT
-postcss-message-helpers,2.0.0,MIT
-postcss-minify-font-values,1.0.5,MIT
-postcss-minify-gradients,1.0.5,MIT
-postcss-minify-params,1.2.2,MIT
-postcss-minify-selectors,2.1.1,MIT
+postcss,6.0.23,MIT
postcss-modules-extract-imports,1.2.0,ISC
postcss-modules-local-by-default,1.2.0,MIT
postcss-modules-scope,1.1.0,ISC
postcss-modules-values,1.3.0,ISC
-postcss-normalize-charset,1.1.1,MIT
-postcss-normalize-url,3.0.8,MIT
-postcss-ordered-values,2.2.3,MIT
-postcss-reduce-idents,2.4.0,MIT
-postcss-reduce-initial,1.0.1,MIT
-postcss-reduce-transforms,1.0.4,MIT
-postcss-selector-parser,2.2.3,MIT
postcss-selector-parser,3.1.1,MIT
-postcss-svgo,2.1.6,MIT
-postcss-unique-selectors,2.0.2,MIT
postcss-value-parser,3.3.0,MIT
-postcss-zindex,2.2.0,MIT
prelude-ls,1.1.2,MIT
premailer,1.10.4,New BSD
premailer-rails,1.9.7,MIT
prepend-http,1.0.4,MIT
prepend-http,2.0.0,MIT
-preserve,0.2.0,MIT
prettier,1.12.1,MIT
prismjs,1.6.0,MIT
private,0.1.8,MIT
@@ -1199,10 +1160,12 @@ process,0.11.10,MIT
process-nextick-args,1.0.7,MIT
process-nextick-args,2.0.0,MIT
progress,2.0.0,MIT
-prometheus-client-mmap,0.9.3,Apache 2.0
+prometheus-client-mmap,0.9.4,Apache 2.0
promise-inflight,1.0.1,ISC
+promisify-call,2.0.4,MIT
proxy-addr,2.0.3,MIT
-proxy-agent,2.0.0,MIT
+proxy-agent,3.0.1,MIT
+proxy-from-env,1.0.0,MIT
prr,0.0.0,MIT
prr,1.0.1,MIT
ps-tree,1.1.0,MIT
@@ -1215,12 +1178,9 @@ pumpify,1.4.0,MIT
punycode,1.3.2,MIT
punycode,1.4.1,MIT
pyu-ruby-sasl,0.0.3.3,MIT
-q,1.4.1,MIT
-q,1.5.0,MIT
qjobs,1.2.0,MIT
qs,6.2.3,New BSD
qs,6.5.1,New BSD
-query-string,4.3.2,MIT
query-string,5.1.1,MIT
querystring,0.2.0,MIT
querystring-es3,0.2.1,MIT
@@ -1243,16 +1203,17 @@ railties,4.2.10,MIT
rainbow,2.2.2,MIT
raindrops,0.18.0,LGPL-2.1+
rake,12.3.1,MIT
-randomatic,1.1.7,MIT
randombytes,2.0.6,MIT
randomfill,1.0.4,MIT
range-parser,1.2.0,MIT
raphael,2.2.7,MIT
raven-js,3.22.1,Simplified BSD
raw-body,2.3.2,MIT
+raw-body,2.3.3,MIT
raw-loader,0.5.1,MIT
rb-fsevent,0.10.2,MIT
rb-inotify,0.9.10,MIT
+rbtrace,0.4.10,MIT
rc,1.2.5,(BSD-2-Clause OR MIT OR Apache-2.0)
rdoc,6.0.4,ruby
re2,1.1.1,New BSD
@@ -1279,12 +1240,10 @@ redis-parser,2.6.0,MIT
redis-rack,2.0.4,MIT
redis-rails,5.0.2,MIT
redis-store,1.4.1,MIT
-reduce-css-calc,1.3.0,MIT
-reduce-function-call,1.0.2,MIT
regenerate,1.3.2,MIT
+regenerator-runtime,0.10.5,MIT
regenerator-runtime,0.11.0,MIT
regenerator-transform,0.10.1,BSD
-regex-cache,0.4.4,MIT
regex-not,1.0.2,MIT
regexpu-core,1.0.0,MIT
regexpu-core,2.0.0,MIT
@@ -1323,7 +1282,7 @@ rimraf,2.6.2,ISC
rinku,2.0.0,ISC
ripemd160,2.0.1,MIT
rotp,2.1.2,MIT
-rouge,3.1.1,MIT
+rouge,3.2.0,MIT
rqrcode,0.7.0,MIT
rqrcode-rails3,0.1.7,MIT
ruby-enum,0.7.2,MIT
@@ -1338,21 +1297,22 @@ rufus-scheduler,3.4.0,MIT
rugged,0.27.2,MIT
run-async,2.3.0,MIT
run-queue,1.0.3,ISC
+rw,1.3.3,New BSD
+rx,4.1.0,Apache 2.0
rx-lite,4.0.8,Apache 2.0
rx-lite-aggregates,4.0.8,Apache 2.0
-rxjs,5.5.10,Apache 2.0
+rxjs,6.2.1,Apache 2.0
safe-buffer,5.1.1,MIT
safe-buffer,5.1.2,MIT
safe-regex,1.1.0,MIT
safe_yaml,1.0.4,MIT
safer-buffer,2.1.2,MIT
-sanitize,4.6.5,MIT
+sanitize,4.6.6,MIT
sanitize-html,1.16.3,MIT
sass,3.5.5,MIT
sass-listen,4.0.0,MIT
sass-rails,5.0.6,MIT
sawyer,0.8.1,MIT
-sax,1.2.2,ISC
sax,1.2.4,ISC
schema-utils,0.4.5,MIT
seed-fu,2.3.7,MIT
@@ -1361,7 +1321,6 @@ select-hose,2.0.0,MIT
select2,3.5.2-browserify,Apache*
select2-rails,3.5.9.3,MIT
selfsigned,1.10.1,MIT
-semver,5.0.3,ISC
semver,5.5.0,ISC
semver-diff,2.1.0,MIT
send,0.16.1,MIT
@@ -1393,6 +1352,8 @@ slack-notifier,1.5.1,MIT
slash,1.0.0,MIT
slice-ansi,1.0.0,MIT
smart-buffer,1.1.15,MIT
+smart-buffer,4.0.1,MIT
+smooshpack,0.0.48,SEE LICENSE.MD IN ROOT
smtp-connection,2.12.0,MIT
snapdragon,0.8.1,MIT
snapdragon-node,2.1.1,MIT
@@ -1407,8 +1368,9 @@ sockjs,0.3.19,MIT
sockjs-client,1.1.4,MIT
socks,1.1.10,MIT
socks,1.1.9,MIT
-socks-proxy-agent,2.1.1,MIT
-sort-keys,1.1.2,MIT
+socks,2.2.1,MIT
+socks-proxy-agent,3.0.1,MIT
+socks-proxy-agent,4.0.1,MIT
sort-keys,2.0.0,MIT
sortablejs,1.7.0,MIT
source-list-map,2.0.0,MIT
@@ -1442,6 +1404,7 @@ state_machines-activerecord,0.5.1,MIT
static-extend,0.1.2,MIT
statuses,1.3.1,MIT
statuses,1.4.0,MIT
+statuses,1.5.0,MIT
stickyfilljs,2.0.5,MIT
stream-browserify,2.0.1,MIT
stream-combiner,0.0.4,MIT
@@ -1469,18 +1432,17 @@ supports-color,2.0.0,MIT
supports-color,3.2.3,MIT
supports-color,5.4.0,MIT
svg4everybody,2.1.9,CC0-1.0
-svgo,0.7.2,MIT
-symbol-observable,1.0.1,MIT
sys-filesystem,1.1.6,Artistic 2.0
table,4.0.2,New BSD
tapable,0.1.10,MIT
tapable,1.0.0,MIT
tar,4.4.4,ISC
-temple,0.7.7,MIT
+temple,0.8.0,MIT
term-size,1.2.0,MIT
test-exclude,4.2.1,ISC
text,1.3.1,MIT
text-table,0.2.0,MIT
+textextensions,2.2.0,MIT
thor,0.19.4,MIT
thread_safe,0.3.6,Apache 2.0
three,0.84.0,MIT
@@ -1490,7 +1452,7 @@ through,2.3.8,MIT
through2,2.0.3,MIT
thunkify,2.1.2,MIT
thunky,0.1.0,MIT*
-tilt,2.0.6,MIT
+tilt,2.0.8,MIT
timeago.js,3.0.2,MIT
timed-out,4.0.1,MIT
timers-browserify,2.0.10,MIT
@@ -1511,9 +1473,11 @@ tough-cookie,2.3.3,New BSD
traverse,0.6.6,MIT
trim-newlines,1.0.0,MIT
trim-right,1.0.1,MIT
+trollop,2.1.3,MIT
truncato,0.7.10,MIT
tryer,1.0.0,MIT
tryit,1.0.3,MIT
+tslib,1.9.3,Apache 2.0
tsscmp,1.0.5,MIT
tty-browserify,0.0.0,MIT
tunnel-agent,0.4.3,Apache 2.0
@@ -1540,8 +1504,6 @@ unicorn,5.1.0,ruby
unicorn-worker-killer,0.4.4,ruby
union-value,1.0.0,MIT
uniq,1.0.1,MIT
-uniqid,4.1.1,MIT
-uniqs,2.0.0,MIT
unique-filename,1.1.0,ISC
unique-slug,2.0.0,ISC
unique-string,1.0.0,MIT
@@ -1549,10 +1511,10 @@ unpipe,1.0.0,MIT
unset-value,1.0.0,MIT
unzip-response,2.0.1,MIT
upath,1.0.5,MIT
+upath,1.1.0,MIT
update-notifier,2.3.0,Simplified BSD
urix,0.1.0,MIT
url,0.11.0,MIT
-url-join,2.0.5,MIT
url-join,4.0.0,MIT
url-loader,1.0.1,MIT
url-parse,1.0.5,MIT
@@ -1572,7 +1534,6 @@ validate-npm-package-license,3.0.1,Apache 2.0
validates_hostname,1.0.6,MIT
vary,1.1.1,MIT
vary,1.1.2,MIT
-vendors,1.0.1,MIT
verror,1.10.0,MIT
version_sorter,2.1.0,MIT
virtus,1.0.5,MIT
@@ -1582,8 +1543,9 @@ vmstat,2.3.0,MIT
void-elements,2.0.1,MIT
vue,2.5.16,MIT
vue-eslint-parser,2.0.3,MIT
+vue-functional-data-merge,2.0.6,MIT
vue-hot-reload-api,2.3.0,MIT
-vue-loader,15.2.0,MIT
+vue-loader,15.2.4,MIT
vue-resource,1.5.0,MIT
vue-router,3.0.1,MIT
vue-style-loader,4.1.0,MIT
@@ -1594,10 +1556,9 @@ vuex,3.0.1,MIT
warden,1.2.7,MIT
watchpack,1.5.0,MIT
wbuf,1.7.2,MIT
-webpack,4.11.1,MIT
-webpack-bundle-analyzer,2.11.1,MIT
-webpack-cli,3.0.2,MIT
-webpack-dev-middleware,2.0.6,MIT
+webpack,4.16.0,MIT
+webpack-bundle-analyzer,2.13.1,MIT
+webpack-cli,3.0.8,MIT
webpack-dev-middleware,3.1.3,MIT
webpack-dev-server,3.1.4,MIT
webpack-log,1.2.0,MIT
@@ -1607,13 +1568,13 @@ webpack-stats-plugin,0.2.1,MIT
websocket-driver,0.6.5,MIT
websocket-extensions,0.1.1,MIT
when,3.7.8,MIT
-whet.extend,0.9.9,MIT
which,1.3.0,ISC
which-module,2.0.0,ISC
wide-align,1.1.2,ISC
widest-line,2.0.0,MIT
wikicloth,0.8.1,MIT
window-size,0.1.0,MIT
+with-callback,1.0.2,MIT
wordwrap,0.0.2,MIT
wordwrap,0.0.3,MIT
wordwrap,1.0.0,MIT
@@ -1627,9 +1588,11 @@ ws,3.3.3,MIT
ws,4.0.0,MIT
xdg-basedir,3.0.0,MIT
xml-simple,1.1.5,ruby
+xmlhttprequest,1.8.0,MIT
xmlhttprequest-ssl,1.5.5,MIT
xregexp,2.0.0,MIT
xtend,4.0.1,MIT
+xterm,3.5.0,MIT
y18n,3.2.1,ISC
y18n,4.0.0,ISC
yallist,2.1.2,ISC