summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock14
-rw-r--r--app/assets/javascripts/activities.js8
-rw-r--r--app/assets/javascripts/admin.js114
-rw-r--r--app/assets/javascripts/aside.js24
-rw-r--r--app/assets/javascripts/commits.js3
-rw-r--r--app/assets/javascripts/compare.js3
-rw-r--r--app/assets/javascripts/contextual_sidebar.js2
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue3
-rw-r--r--app/assets/javascripts/diff_notes/models/discussion.js3
-rw-r--r--app/assets/javascripts/dispatcher.js20
-rw-r--r--app/assets/javascripts/groups/components/item_actions.vue14
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js2
-rw-r--r--app/assets/javascripts/issue.js2
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue10
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue6
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue2
-rw-r--r--app/assets/javascripts/job.js3
-rw-r--r--app/assets/javascripts/lib/utils/cache.js4
-rw-r--r--app/assets/javascripts/lib/utils/constants.js1
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js218
-rw-r--r--app/assets/javascripts/main.js18
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js3
-rw-r--r--app/assets/javascripts/merge_request_tabs.js8
-rw-r--r--app/assets/javascripts/milestone_select.js3
-rw-r--r--app/assets/javascripts/notes.js5
-rw-r--r--app/assets/javascripts/pipelines/components/empty_state.vue47
-rw-r--r--app/assets/javascripts/pipelines/pipelines_bundle.js3
-rw-r--r--app/assets/javascripts/profile/account/components/delete_account_modal.vue8
-rw-r--r--app/assets/javascripts/project_find_file.js298
-rw-r--r--app/assets/javascripts/render_gfm.js4
-rw-r--r--app/assets/javascripts/repo/components/new_dropdown/modal.vue8
-rw-r--r--app/assets/javascripts/repo/components/repo_commit_section.vue16
-rw-r--r--app/assets/javascripts/repo/components/repo_edit_button.vue6
-rw-r--r--app/assets/javascripts/repo/components/repo_preview.vue3
-rw-r--r--app/assets/javascripts/right_sidebar.js8
-rw-r--r--app/assets/javascripts/search.js207
-rw-r--r--app/assets/javascripts/search_autocomplete.js769
-rw-r--r--app/assets/javascripts/single_file_diff.js3
-rw-r--r--app/assets/javascripts/syntax_highlight.js14
-rw-r--r--app/assets/javascripts/users/activity_calendar.js5
-rw-r--r--app/assets/javascripts/users/user_tabs.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/memory_graph.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/modal.vue (renamed from app/assets/javascripts/vue_shared/components/popup_dialog.vue)4
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue (renamed from app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue)12
-rw-r--r--app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js (renamed from app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js)4
-rw-r--r--app/assets/javascripts/vue_shared/mixins/timeago.js6
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss301
-rw-r--r--app/assets/stylesheets/framework/filters.scss59
-rw-r--r--app/assets/stylesheets/framework/header.scss6
-rw-r--r--app/assets/stylesheets/framework/images.scss7
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss6
-rw-r--r--app/assets/stylesheets/framework/layout.scss5
-rw-r--r--app/assets/stylesheets/framework/lists.scss2
-rw-r--r--app/assets/stylesheets/framework/mobile.scss17
-rw-r--r--app/assets/stylesheets/framework/modal.scss13
-rw-r--r--app/assets/stylesheets/framework/secondary-navigation-elements.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss4
-rw-r--r--app/assets/stylesheets/framework/typography.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss3
-rw-r--r--app/assets/stylesheets/pages/boards.scss2
-rw-r--r--app/assets/stylesheets/pages/builds.scss2
-rw-r--r--app/assets/stylesheets/pages/clusters.scss2
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss2
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss2
-rw-r--r--app/assets/stylesheets/pages/editor.scss2
-rw-r--r--app/assets/stylesheets/pages/environments.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss26
-rw-r--r--app/assets/stylesheets/pages/issues.scss2
-rw-r--r--app/assets/stylesheets/pages/labels.scss2
-rw-r--r--app/assets/stylesheets/pages/members.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss8
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/notifications.scss4
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss6
-rw-r--r--app/assets/stylesheets/pages/projects.scss6
-rw-r--r--app/assets/stylesheets/pages/repo.scss13
-rw-r--r--app/assets/stylesheets/pages/search.scss7
-rw-r--r--app/assets/stylesheets/pages/todos.scss4
-rw-r--r--app/assets/stylesheets/pages/tree.scss2
-rw-r--r--app/controllers/admin/groups_controller.rb8
-rw-r--r--app/controllers/admin/projects_controller.rb11
-rw-r--r--app/controllers/concerns/members_presentation.rb11
-rw-r--r--app/controllers/concerns/with_performance_bar.rb1
-rw-r--r--app/controllers/groups/group_members_controller.rb7
-rw-r--r--app/controllers/projects/project_members_controller.rb6
-rw-r--r--app/helpers/issues_helper.rb2
-rw-r--r--app/helpers/labels_helper.rb7
-rw-r--r--app/helpers/members_helper.rb7
-rw-r--r--app/mailers/emails/notes.rb10
-rw-r--r--app/mailers/notify.rb16
-rw-r--r--app/models/blob.rb12
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/commit.rb14
-rw-r--r--app/models/concerns/cache_markdown_field.rb3
-rw-r--r--app/models/member.rb1
-rw-r--r--app/models/merge_request.rb35
-rw-r--r--app/models/merge_request_diff.rb6
-rw-r--r--app/models/note.rb13
-rw-r--r--app/models/project.rb5
-rw-r--r--app/models/repository.rb29
-rw-r--r--app/presenters/group_member_presenter.rb15
-rw-r--r--app/presenters/member_presenter.rb38
-rw-r--r--app/presenters/members_presenter.rb15
-rw-r--r--app/presenters/project_member_presenter.rb15
-rw-r--r--app/services/ci/create_pipeline_service.rb2
-rw-r--r--app/services/issuable_base_service.rb6
-rw-r--r--app/services/members/approve_access_request_service.rb11
-rw-r--r--app/services/members/destroy_service.rb11
-rw-r--r--app/views/dashboard/todos/index.html.haml6
-rw-r--r--app/views/layouts/_page.html.haml4
-rw-r--r--app/views/layouts/nav/projects_dropdown/_show.html.haml2
-rw-r--r--app/views/projects/blob/_template_selectors.html.haml10
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/clusters/_empty_state.html.haml6
-rw-r--r--app/views/projects/commits/_commit.html.haml4
-rw-r--r--app/views/projects/environments/metrics.html.haml6
-rw-r--r--app/views/projects/issues/show.html.haml6
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml4
-rw-r--r--app/views/projects/project_members/_team.html.haml10
-rw-r--r--app/views/projects/project_members/index.html.haml2
-rw-r--r--app/views/projects/tags/new.html.haml8
-rw-r--r--app/views/shared/_label.html.haml33
-rw-r--r--app/views/shared/_show_aside.html.haml2
-rw-r--r--app/views/shared/empty_states/_issues.html.haml21
-rw-r--r--app/views/shared/empty_states/_labels.html.haml10
-rw-r--r--app/views/shared/empty_states/_merge_requests.html.haml17
-rw-r--r--app/views/shared/members/_member.html.haml24
-rw-r--r--app/views/shared/members/_requests.html.haml19
-rw-r--r--app/workers/all_queues.yml98
-rw-r--r--app/workers/build_finished_worker.rb2
-rw-r--r--app/workers/build_hooks_worker.rb2
-rw-r--r--app/workers/build_queue_worker.rb2
-rw-r--r--app/workers/build_success_worker.rb2
-rw-r--r--app/workers/concerns/application_worker.rb24
-rw-r--r--app/workers/concerns/cluster_queue.rb2
-rw-r--r--app/workers/concerns/cronjob_queue.rb3
-rw-r--r--app/workers/concerns/gitlab/github_import/queue.rb4
-rw-r--r--app/workers/concerns/pipeline_queue.rb10
-rw-r--r--app/workers/concerns/repository_check_queue.rb4
-rw-r--r--app/workers/create_pipeline_worker.rb2
-rw-r--r--app/workers/expire_job_cache_worker.rb2
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb2
-rw-r--r--app/workers/gitlab/github_import/advance_stage_worker.rb2
-rw-r--r--app/workers/pages_worker.rb2
-rw-r--r--app/workers/pipeline_hooks_worker.rb2
-rw-r--r--app/workers/pipeline_process_worker.rb2
-rw-r--r--app/workers/pipeline_success_worker.rb2
-rw-r--r--app/workers/pipeline_update_worker.rb2
-rw-r--r--app/workers/stage_update_worker.rb2
-rw-r--r--app/workers/stuck_merge_jobs_worker.rb7
-rw-r--r--app/workers/update_head_pipeline_for_merge_request_worker.rb16
-rw-r--r--changelogs/unreleased/13695-order-contributors-in-api.yml5
-rw-r--r--changelogs/unreleased/25317-prioritize-author-date-over-commit.yml5
-rw-r--r--changelogs/unreleased/28004-consider-refactoring-member-view-by-using-presenter.yml4
-rw-r--r--changelogs/unreleased/33926-update-issuable-icons.yml5
-rw-r--r--changelogs/unreleased/40285-prometheus-loading-screen-no-longer-seems-to-appear.yml5
-rw-r--r--changelogs/unreleased/40509_sorting_tags_api.yml5
-rw-r--r--changelogs/unreleased/40895-fix-frequent-projects-stale-path.yml5
-rw-r--r--changelogs/unreleased/add-tcp-check-rake-task.yml5
-rw-r--r--changelogs/unreleased/fix-create-mr-from-issue-with-template.yml5
-rw-r--r--changelogs/unreleased/issue-description-field-typo.yml5
-rw-r--r--changelogs/unreleased/optimize-issues-avoid-noop-empty-cache-updates2.yml6
-rw-r--r--changelogs/unreleased/optimize-projects-for-imported-projects.yml6
-rw-r--r--changelogs/unreleased/remove-tabindexes-from-tag-form.yml5
-rw-r--r--changelogs/unreleased/sophie-h-gitlab-ce-patch-15.yml5
-rw-r--r--changelogs/unreleased/tc-correct-email-in-reply-to.yml5
-rw-r--r--changelogs/unreleased/zj-memoization-mr-commits.yml5
-rw-r--r--config/initializers/flipper.rb24
-rw-r--r--config/initializers/sidekiq.rb18
-rw-r--r--config/no_todos_messages.yml6
-rw-r--r--config/sidekiq_queues.yml5
-rw-r--r--db/post_migrate/20171213160445_migrate_github_importer_advance_stage_sidekiq_queue.rb16
-rw-r--r--db/schema.rb2
-rw-r--r--doc/administration/auth/README.md1
-rw-r--r--doc/administration/high_availability/README.md1
-rw-r--r--doc/administration/index.md13
-rw-r--r--doc/administration/raketasks/maintenance.md19
-rw-r--r--doc/administration/reply_by_email.md8
-rw-r--r--doc/api/issues.md6
-rw-r--r--doc/api/merge_requests.md8
-rw-r--r--doc/api/repositories.md2
-rw-r--r--doc/api/tags.md6
-rw-r--r--doc/ci/docker/using_docker_images.md61
-rw-r--r--doc/ci/ssh_keys/README.md249
-rw-r--r--doc/ci/variables/README.md13
-rw-r--r--doc/customization/issue_closing.md4
-rw-r--r--doc/customization/welcome_message.md2
-rw-r--r--doc/development/fe_guide/dropdowns.md12
-rw-r--r--doc/development/sidekiq_style_guide.md59
-rw-r--r--doc/development/ux_guide/components.md18
-rw-r--r--doc/development/ux_guide/copy.md12
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/topics/git/index.md4
-rw-r--r--doc/topics/git/troubleshooting_git.md82
-rw-r--r--doc/update/10.2-to-10.3.md360
-rw-r--r--doc/user/group/index.md10
-rw-r--r--doc/user/profile/index.md9
-rw-r--r--doc/user/project/merge_requests/img/create_from_email.pngbin0 -> 152975 bytes
-rw-r--r--doc/user/project/merge_requests/index.md9
-rw-r--r--doc/user/project/pipelines/settings.md2
-rw-r--r--doc/user/project/settings/index.md6
-rw-r--r--lib/api/repositories.rb4
-rw-r--r--lib/api/tags.rb7
-rw-r--r--lib/feature.rb9
-rw-r--r--lib/gitlab/conflict/file_collection.rb2
-rw-r--r--lib/gitlab/git/repository.rb11
-rw-r--r--lib/gitlab/metrics/method_call.rb17
-rw-r--r--lib/gitlab/sidekiq_config.rb37
-rw-r--r--lib/gitlab/sidekiq_versioning.rb25
-rw-r--r--lib/gitlab/sidekiq_versioning/manager.rb12
-rw-r--r--lib/gitlab/tcp_checker.rb45
-rw-r--r--lib/gitlab/utils/strong_memoize.rb20
-rw-r--r--lib/gitlab/view/presenter/factory.rb2
-rw-r--r--lib/tasks/gitlab/tcp_check.rake20
-rw-r--r--qa/Dockerfile2
-rw-r--r--qa/Gemfile12
-rw-r--r--qa/Gemfile.lock84
-rw-r--r--qa/qa.rb2
-rw-r--r--qa/qa/page/base.rb16
-rw-r--r--qa/qa/page/main/entry.rb26
-rw-r--r--qa/qa/page/main/login.rb8
-rw-r--r--qa/qa/page/mattermost/login.rb8
-rw-r--r--qa/qa/runtime/browser.rb109
-rw-r--r--qa/qa/scenario/entrypoint.rb1
-rw-r--r--qa/qa/scenario/gitlab/admin/hashed_storage.rb1
-rw-r--r--qa/qa/specs/config.rb63
-rw-r--r--qa/qa/specs/features/login/standard_spec.rb4
-rw-r--r--qa/qa/specs/features/mattermost/group_create_spec.rb2
-rw-r--r--qa/qa/specs/features/mattermost/login_spec.rb25
-rw-r--r--qa/qa/specs/features/project/create_spec.rb2
-rw-r--r--qa/qa/specs/features/repository/clone_spec.rb2
-rw-r--r--qa/qa/specs/features/repository/push_spec.rb2
-rw-r--r--qa/qa/specs/runner.rb2
-rw-r--r--rubocop/cop/include_sidekiq_worker.rb29
-rw-r--r--rubocop/cop/sidekiq_options_queue.rb27
-rw-r--r--rubocop/rubocop.rb2
-rw-r--r--spec/factories/commits.rb19
-rw-r--r--spec/features/commits_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb2
-rw-r--r--spec/features/groups/labels/user_sees_links_to_issuables.rb15
-rw-r--r--spec/features/profile_spec.rb4
-rw-r--r--spec/features/projects/labels/user_sees_links_to_issuables.rb75
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb2
-rw-r--r--spec/features/projects/tree/create_file_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/contributor.json18
-rw-r--r--spec/fixtures/api/schemas/contributors.json4
-rw-r--r--spec/helpers/labels_helper_spec.rb63
-rw-r--r--spec/helpers/members_helper_spec.rb8
-rw-r--r--spec/javascripts/activities_spec.js4
-rw-r--r--spec/javascripts/datetime_utility_spec.js186
-rw-r--r--spec/javascripts/deploy_keys/components/key_spec.js3
-rw-r--r--spec/javascripts/groups/components/item_actions_spec.js24
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js6
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js4
-rw-r--r--spec/javascripts/issue_spec.js2
-rw-r--r--spec/javascripts/notes_spec.js2
-rw-r--r--spec/javascripts/right_sidebar_spec.js2
-rw-r--r--spec/javascripts/search_autocomplete_spec.js4
-rw-r--r--spec/javascripts/syntax_highlight_spec.js74
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js3
-rw-r--r--spec/javascripts/vue_shared/components/modal_spec.js (renamed from spec/javascripts/vue_shared/components/popup_dialog_spec.js)8
-rw-r--r--spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js6
-rw-r--r--spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb27
-rw-r--r--spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb19
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb17
-rw-r--r--spec/lib/gitlab/metrics/method_call_spec.rb32
-rw-r--r--spec/lib/gitlab/sidekiq_config_spec.rb23
-rw-r--r--spec/lib/gitlab/sidekiq_versioning/manager_spec.rb22
-rw-r--r--spec/lib/gitlab/sidekiq_versioning_spec.rb44
-rw-r--r--spec/lib/gitlab/tcp_checker_spec.rb32
-rw-r--r--spec/lib/gitlab/utils/strong_memoize_spec.rb12
-rw-r--r--spec/lib/gitlab/view/presenter/factory_spec.rb8
-rw-r--r--spec/mailers/notify_spec.rb40
-rw-r--r--spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb11
-rw-r--r--spec/models/ci/build_spec.rb7
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb20
-rw-r--r--spec/models/merge_request_spec.rb49
-rw-r--r--spec/models/note_spec.rb22
-rw-r--r--spec/models/project_spec.rb5
-rw-r--r--spec/models/repository_spec.rb134
-rw-r--r--spec/presenters/group_member_presenter_spec.rb138
-rw-r--r--spec/presenters/project_member_presenter_spec.rb138
-rw-r--r--spec/requests/api/repositories_spec.rb22
-rw-r--r--spec/requests/api/tags_spec.rb38
-rw-r--r--spec/rubocop/cop/include_sidekiq_worker_spec.rb31
-rw-r--r--spec/rubocop/cop/sidekiq_options_queue_spec.rb26
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb16
-rw-r--r--spec/services/ci/register_job_service_spec.rb9
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb12
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb8
-rw-r--r--spec/support/batch_loader.rb5
-rw-r--r--spec/workers/concerns/application_worker_spec.rb8
-rw-r--r--spec/workers/concerns/cluster_queue_spec.rb2
-rw-r--r--spec/workers/concerns/cronjob_queue_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/queue_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_queue_spec.rb11
-rw-r--r--spec/workers/concerns/repository_check_queue_spec.rb2
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb31
-rw-r--r--spec/workers/stuck_merge_jobs_worker_spec.rb6
-rw-r--r--spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb2
-rw-r--r--vendor/Dockerfile/CONTRIBUTING.md25
-rw-r--r--vendor/Dockerfile/LICENSE16
-rw-r--r--vendor/gitignore/Global/Matlab.gitignore3
-rw-r--r--vendor/gitignore/Go.gitignore3
-rw-r--r--vendor/gitignore/Haskell.gitignore1
-rw-r--r--vendor/gitignore/Jekyll.gitignore1
-rw-r--r--vendor/gitignore/ROS.gitignore2
-rw-r--r--vendor/gitignore/Symfony.gitignore4
-rw-r--r--vendor/gitignore/TeX.gitignore6
-rw-r--r--vendor/gitignore/Unity.gitignore13
-rw-r--r--vendor/gitignore/UnrealEngine.gitignore2
-rw-r--r--vendor/gitignore/VisualStudio.gitignore14
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml31
-rw-r--r--vendor/gitlab-ci-yml/CONTRIBUTING.md19
-rw-r--r--vendor/gitlab-ci-yml/Chef.gitlab-ci.yml51
-rw-r--r--vendor/gitlab-ci-yml/Go.gitlab-ci.yml4
-rw-r--r--vendor/gitlab-ci-yml/LICENSE16
-rw-r--r--vendor/gitlab-ci-yml/Rust.gitlab-ci.yml2
-rw-r--r--vendor/licenses.csv1097
326 files changed, 4477 insertions, 3366 deletions
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 269fb5dfe2c..e030a0157c9 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.10.2
+5.10.3
diff --git a/Gemfile b/Gemfile
index e9701fab27a..ba339408ba9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -405,8 +405,9 @@ gem 'gitaly-proto', '~> 0.59.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
# Feature toggles
-gem 'flipper', '~> 0.10.2'
-gem 'flipper-active_record', '~> 0.10.2'
+gem 'flipper', '~> 0.11.0'
+gem 'flipper-active_record', '~> 0.11.0'
+gem 'flipper-active_support_cache_store', '~> 0.11.0'
# Structured logging
gem 'lograge', '~> 0.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index efae71efdb7..f2546efa906 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -215,10 +215,13 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
- flipper (0.10.2)
- flipper-active_record (0.10.2)
+ flipper (0.11.0)
+ flipper-active_record (0.11.0)
activerecord (>= 3.2, < 6)
- flipper (~> 0.10.2)
+ flipper (~> 0.11.0)
+ flipper-active_support_cache_store (0.11.0)
+ activesupport (>= 3.2, < 6)
+ flipper (~> 0.11.0)
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
@@ -1021,8 +1024,9 @@ DEPENDENCIES
faraday (~> 0.12)
ffaker (~> 2.4)
flay (~> 2.8.0)
- flipper (~> 0.10.2)
- flipper-active_record (~> 0.10.2)
+ flipper (~> 0.11.0)
+ flipper-active_record (~> 0.11.0)
+ flipper-active_support_cache_store (~> 0.11.0)
fog-aliyun (~> 0.2.0)
fog-aws (~> 1.4)
fog-core (~> 1.44)
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index 5d060165f4b..f5f6b67f26e 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -2,8 +2,9 @@
/* global Pager */
import Cookies from 'js-cookie';
+import { localTimeAgo } from './lib/utils/datetime_utility';
-class Activities {
+export default class Activities {
constructor() {
Pager.init(20, true, false, data => data, this.updateTooltips);
@@ -15,7 +16,7 @@ class Activities {
}
updateTooltips() {
- gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
+ localTimeAgo($('.js-timeago', '.content_list'));
}
reloadActivities() {
@@ -33,6 +34,3 @@ class Activities {
$sender.closest('li').toggleClass('active');
}
}
-
-window.gl = window.gl || {};
-window.gl.Activities = Activities;
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index b0b72c40f25..c1f7fa2aced 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -1,63 +1,59 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */
import { refreshCurrentPage } from './lib/utils/url_utility';
-window.Admin = (function() {
- function Admin() {
- var modal, showBlacklistType;
- $('input#user_force_random_password').on('change', function(elem) {
- var elems;
- elems = $('#user_password, #user_password_confirmation');
- if ($(this).attr('checked')) {
- return elems.val('').attr('disabled', true);
- } else {
- return elems.removeAttr('disabled');
- }
- });
- $('body').on('click', '.js-toggle-colors-link', function(e) {
- e.preventDefault();
- return $('.js-toggle-colors-container').toggle();
- });
- $('.log-tabs a').click(function(e) {
- e.preventDefault();
- return $(this).tab('show');
- });
- $('.log-bottom').click(function(e) {
- var visible_log;
- e.preventDefault();
- visible_log = $(".file-content:visible");
- return visible_log.animate({
- scrollTop: visible_log.find('ol').height()
- }, "fast");
- });
- modal = $('.change-owner-holder');
- $('.change-owner-link').bind("click", function(e) {
- e.preventDefault();
- $(this).hide();
- return modal.show();
- });
- $('.change-owner-cancel-link').bind("click", function(e) {
- e.preventDefault();
- modal.hide();
- return $('.change-owner-link').show();
- });
- $('li.project_member').bind('ajax:success', function() {
- return refreshCurrentPage();
- });
- $('li.group_member').bind('ajax:success', function() {
- return refreshCurrentPage();
- });
- showBlacklistType = function() {
- if ($("input[name='blacklist_type']:checked").val() === 'file') {
- $('.blacklist-file').show();
- return $('.blacklist-raw').hide();
- } else {
- $('.blacklist-file').hide();
- return $('.blacklist-raw').show();
- }
- };
- $("input[name='blacklist_type']").click(showBlacklistType);
- showBlacklistType();
+function showBlacklistType() {
+ if ($('input[name="blacklist_type"]:checked').val() === 'file') {
+ $('.blacklist-file').show();
+ $('.blacklist-raw').hide();
+ } else {
+ $('.blacklist-file').hide();
+ $('.blacklist-raw').show();
}
+}
- return Admin;
-})();
+export default function adminInit() {
+ const modal = $('.change-owner-holder');
+
+ $('input#user_force_random_password').on('change', function randomPasswordClick() {
+ const $elems = $('#user_password, #user_password_confirmation');
+ if ($(this).attr('checked')) {
+ $elems.val('').attr('disabled', true);
+ } else {
+ $elems.removeAttr('disabled');
+ }
+ });
+
+ $('body').on('click', '.js-toggle-colors-link', (e) => {
+ e.preventDefault();
+ $('.js-toggle-colors-container').toggle();
+ });
+
+ $('.log-tabs a').on('click', function logTabsClick(e) {
+ e.preventDefault();
+ $(this).tab('show');
+ });
+
+ $('.log-bottom').on('click', (e) => {
+ e.preventDefault();
+ const $visibleLog = $('.file-content:visible');
+ $visibleLog.animate({
+ scrollTop: $visibleLog.find('ol').height(),
+ }, 'fast');
+ });
+
+ $('.change-owner-link').on('click', function changeOwnerLinkClick(e) {
+ e.preventDefault();
+ $(this).hide();
+ modal.show();
+ });
+
+ $('.change-owner-cancel-link').on('click', (e) => {
+ e.preventDefault();
+ modal.hide();
+ $('.change-owner-link').show();
+ });
+
+ $('li.project_member, li.group_member').on('ajax:success', refreshCurrentPage);
+
+ $("input[name='blacklist_type']").on('click', showBlacklistType);
+ showBlacklistType();
+}
diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js
deleted file mode 100644
index 88756884d16..00000000000
--- a/app/assets/javascripts/aside.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, max-len */
-
-window.Aside = (function() {
- function Aside() {
- $(document).off("click", "a.show-aside");
- $(document).on("click", 'a.show-aside', function(e) {
- var btn, icon;
- e.preventDefault();
- btn = $(e.currentTarget);
- icon = btn.find('i');
- if (icon.hasClass('fa-angle-left')) {
- btn.parent().find('section').hide();
- btn.parent().find('aside').fadeIn();
- return icon.removeClass('fa-angle-left').addClass('fa-angle-right');
- } else {
- btn.parent().find('aside').hide();
- btn.parent().find('section').fadeIn();
- return icon.removeClass('fa-angle-right').addClass('fa-angle-left');
- }
- });
- }
-
- return Aside;
-})();
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 9b952ea7b60..be58392135c 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -4,6 +4,7 @@
/* global Pager */
import { pluralize } from './lib/utils/text_utility';
+import { localTimeAgo } from './lib/utils/datetime_utility';
export default (function () {
const CommitsList = {};
@@ -91,7 +92,7 @@ export default (function () {
$commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${pluralize('commit', commitsCount)}`);
}
- gl.utils.localTimeAgo($processedData.find('.js-timeago'));
+ localTimeAgo($processedData.find('.js-timeago'));
return processedData;
};
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index 0ce467a3bd4..144caf1d278 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -1,4 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
+import { localTimeAgo } from './lib/utils/datetime_utility';
export default class Compare {
constructor(opts) {
@@ -81,7 +82,7 @@ export default class Compare {
loading.hide();
$target.html(html);
var className = '.' + $target[0].className.replace(' ', '.');
- gl.utils.localTimeAgo($('.js-timeago', className));
+ localTimeAgo($('.js-timeago', className));
}
});
}
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index cd20dde2951..74520675a7c 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -9,7 +9,7 @@ export default class ContextualSidebar {
}
initDomElements() {
- this.$page = $('.page-with-sidebar');
+ this.$page = $('.layout-page');
this.$sidebar = $('.nav-sidebar');
this.$innerScroll = $('.nav-sidebar-inner-scroll', this.$sidebar);
this.$overlay = $('.mobile-overlay');
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index b41d464475f..2a05c6f001e 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -1,5 +1,6 @@
<script>
import actionBtn from './action_btn.vue';
+ import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
props: {
@@ -21,7 +22,7 @@
},
computed: {
timeagoDate() {
- return gl.utils.getTimeago().format(this.deployKey.created_at);
+ return getTimeago().format(this.deployKey.created_at);
},
editDeployKeyPath() {
return `${this.endpoint}/${this.deployKey.id}/edit`;
diff --git a/app/assets/javascripts/diff_notes/models/discussion.js b/app/assets/javascripts/diff_notes/models/discussion.js
index dc43e4b2cc7..1b8a9af9390 100644
--- a/app/assets/javascripts/diff_notes/models/discussion.js
+++ b/app/assets/javascripts/diff_notes/models/discussion.js
@@ -2,6 +2,7 @@
/* global NoteModel */
import Vue from 'vue';
+import { localTimeAgo } from '../../lib/utils/datetime_utility';
class DiscussionModel {
constructor (discussionId) {
@@ -71,7 +72,7 @@ class DiscussionModel {
$(`${discussionSelector} .discussion-header`).append(data.discussion_headline_html);
}
- gl.utils.localTimeAgo($('.js-timeago', `${discussionSelector}`));
+ localTimeAgo($('.js-timeago', `${discussionSelector}`));
} else {
$discussionHeadline.remove();
}
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 299e43a4e90..522f5d12b30 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -15,8 +15,8 @@ import GroupLabelSubscription from './group_label_subscription';
import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select';
-/* global Search */
-/* global Admin */
+import Search from './search';
+import initAdmin from './admin';
import NamespaceSelect from './namespace_select';
import NewCommitForm from './new_commit_form';
import Project from './project';
@@ -24,7 +24,7 @@ import projectAvatar from './project_avatar';
/* global MergeRequest */
import Compare from './compare';
import initCompareAutocomplete from './compare_autocomplete';
-/* global ProjectFindFile */
+import ProjectFindFile from './project_find_file';
import ProjectNew from './project_new';
import projectImport from './project_import';
import Labels from './labels';
@@ -91,6 +91,8 @@ import DueDateSelectors from './due_date_select';
import Diff from './diff';
import ProjectLabelSubscription from './project_label_subscription';
import ProjectVariables from './project_variables';
+import SearchAutocomplete from './search_autocomplete';
+import Activities from './activities';
(function() {
var Dispatcher;
@@ -333,7 +335,7 @@ import ProjectVariables from './project_variables';
shortcut_handler = new ShortcutsIssuable(true);
break;
case 'dashboard:activity':
- new gl.Activities();
+ new Activities();
break;
case 'projects:commit:show':
new Diff();
@@ -354,7 +356,7 @@ import ProjectVariables from './project_variables';
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break;
case 'projects:activity':
- new gl.Activities();
+ new Activities();
shortcut_handler = new ShortcutsNavigation();
break;
case 'projects:commits:show':
@@ -372,7 +374,7 @@ import ProjectVariables from './project_variables';
if ($('#tree-slider').length) new TreeView();
if ($('.blob-viewer').length) new BlobViewer();
- if ($('.project-show-activity').length) new gl.Activities();
+ if ($('.project-show-activity').length) new Activities();
$('#tree-slider').waitForImages(function() {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
});
@@ -406,7 +408,7 @@ import ProjectVariables from './project_variables';
});
break;
case 'groups:activity':
- new gl.Activities();
+ new Activities();
break;
case 'groups:show':
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
@@ -583,7 +585,7 @@ import ProjectVariables from './project_variables';
// needed in rspec
gl.u2fAuthenticate = u2fAuthenticate;
case 'admin':
- new Admin();
+ initAdmin();
switch (path[1]) {
case 'broadcast_messages':
initBroadcastMessagesForm();
@@ -683,7 +685,7 @@ import ProjectVariables from './project_variables';
Dispatcher.prototype.initSearch = function() {
// Only when search form is present
if ($('.search').length) {
- return new gl.SearchAutocomplete();
+ return new SearchAutocomplete();
}
};
diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue
index 09cb79c1afd..58ba5aff7cf 100644
--- a/app/assets/javascripts/groups/components/item_actions.vue
+++ b/app/assets/javascripts/groups/components/item_actions.vue
@@ -1,7 +1,7 @@
<script>
import { s__ } from '../../locale';
import tooltip from '../../vue_shared/directives/tooltip';
-import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
+import modal from '../../vue_shared/components/modal.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
import Icon from '../../vue_shared/components/icon.vue';
@@ -9,7 +9,7 @@ import Icon from '../../vue_shared/components/icon.vue';
export default {
components: {
Icon,
- PopupDialog,
+ modal,
},
directives: {
tooltip,
@@ -27,7 +27,7 @@ export default {
},
data() {
return {
- dialogStatus: false,
+ modalStatus: false,
};
},
computed: {
@@ -43,10 +43,10 @@ export default {
},
methods: {
onLeaveGroup() {
- this.dialogStatus = true;
+ this.modalStatus = true;
},
leaveGroup(leaveConfirmed) {
- this.dialogStatus = false;
+ this.modalStatus = false;
if (leaveConfirmed) {
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
}
@@ -82,8 +82,8 @@ export default {
class="fa fa-sign-out"
aria-hidden="true"/>
</a>
- <popup-dialog
- v-show="dialogStatus"
+ <modal
+ v-show="modalStatus"
:primary-button-label="__('Leave')"
kind="warning"
:title="__('Are you sure?')"
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index ba2b6737988..bf77b93b643 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -21,7 +21,7 @@ export default class IssuableBulkUpdateSidebar {
}
initDomElements() {
- this.$page = $('.page-with-sidebar');
+ this.$page = $('.layout-page');
this.$sidebar = $('.right-sidebar');
this.$sidebarInnerContainer = this.$sidebar.find('.issuable-sidebar');
this.$bulkEditCancelBtn = $('.js-bulk-update-menu-hide');
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 91b5ef1c10a..411c820cc43 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -48,7 +48,7 @@ export default class Issue {
})
.fail(() => new Flash(issueFailMessage))
.done((data) => {
- const isClosedBadge = $('div.status-box-closed');
+ const isClosedBadge = $('div.status-box-issue-closed');
const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter');
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index fd1a50dd533..25ebe5314e0 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -9,7 +9,7 @@ import titleComponent from './title.vue';
import descriptionComponent from './description.vue';
import editedComponent from './edited.vue';
import formComponent from './form.vue';
-import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor';
+import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
export default {
props: {
@@ -152,7 +152,7 @@ export default {
},
mixins: [
- RecaptchaDialogImplementor,
+ recaptchaModalImplementor,
],
methods: {
@@ -197,7 +197,7 @@ export default {
});
},
- closeRecaptchaDialog() {
+ closeRecaptchaModal() {
this.store.setFormState({
updateLoading: false,
});
@@ -273,10 +273,10 @@ export default {
:enable-autocomplete="enableAutocomplete"
/>
- <recaptcha-dialog
+ <recaptcha-modal
v-show="showRecaptcha"
:html="recaptchaHTML"
- @close="closeRecaptchaDialog"
+ @close="closeRecaptchaModal"
/>
</div>
<div v-else>
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index feb73481422..c3f2bf130bb 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,12 +1,12 @@
<script>
import animateMixin from '../mixins/animate';
import TaskList from '../../task_list';
- import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor';
+ import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
export default {
mixins: [
animateMixin,
- RecaptchaDialogImplementor,
+ recaptchaModalImplementor,
],
props: {
@@ -126,7 +126,7 @@
>
</textarea>
- <recaptcha-dialog
+ <recaptcha-modal
v-show="showRecaptcha"
:html="recaptchaHTML"
@close="closeRecaptcha"
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
index 52fe4ecd08b..4e577546551 100644
--- a/app/assets/javascripts/issue_show/components/fields/description.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -53,7 +53,7 @@
<textarea
id="issue-description"
class="note-textarea js-gfm-input js-autosize markdown-area"
- data-supports-quick-actionss="false"
+ data-supports-quick-actions="false"
aria-label="Description"
v-model="formState.description"
ref="textarea"
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
index 06b0e02a870..198a7823381 100644
--- a/app/assets/javascripts/job.js
+++ b/app/assets/javascripts/job.js
@@ -3,6 +3,7 @@ import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
+import { timeFor } from './lib/utils/datetime_utility';
export default class Job {
constructor(options) {
@@ -261,7 +262,7 @@ export default class Job {
if ($date.length) {
const date = $date.text();
return $date.text(
- gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '),
+ timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3'))),
);
}
}
diff --git a/app/assets/javascripts/lib/utils/cache.js b/app/assets/javascripts/lib/utils/cache.js
index 3141f1eeafc..596bd1e388a 100644
--- a/app/assets/javascripts/lib/utils/cache.js
+++ b/app/assets/javascripts/lib/utils/cache.js
@@ -1,4 +1,4 @@
-class Cache {
+export default class Cache {
constructor() {
this.internalStorage = { };
}
@@ -15,5 +15,3 @@ class Cache {
delete this.internalStorage[key];
}
}
-
-export default Cache;
diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js
index 7a72509d234..9a61003ef30 100644
--- a/app/assets/javascripts/lib/utils/constants.js
+++ b/app/assets/javascripts/lib/utils/constants.js
@@ -1,3 +1,2 @@
-/* eslint-disable import/prefer-default-export */
export const BYTES_IN_KIB = 1024;
export const HIDDEN_CLASS = 'hidden';
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index d0578b230b1..198b5164c92 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -1,9 +1,6 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */
-
import timeago from 'timeago.js';
import dateFormat from 'vendor/date.format';
import { pluralize } from './text_utility';
-
import {
lang,
s__,
@@ -12,121 +9,125 @@ import {
window.timeago = timeago;
window.dateFormat = dateFormat;
-(function() {
- (function(w) {
- var base;
- var timeagoInstance;
+/**
+ * Given a date object returns the day of the week in English
+ * @param {date} date
+ * @returns {String}
+ */
+export const getDayName = date => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
- if (w.gl == null) {
- w.gl = {};
- }
- if ((base = w.gl).utils == null) {
- base.utils = {};
- }
- w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+/**
+ * @example
+ * dateFormat('2017-12-05','mmm d, yyyy h:MMtt Z' ) -> "Dec 5, 2017 12:00am GMT+0000"
+ * @param {date} datetime
+ * @returns {String}
+ */
+export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
- w.gl.utils.formatDate = function(datetime) {
- return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
+let timeagoInstance;
+/**
+ * Sets a timeago Instance
+ */
+export function getTimeago() {
+ if (!timeagoInstance) {
+ const localeRemaining = function getLocaleRemaining(number, index) {
+ return [
+ [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
+ [s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
+ [s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
+ [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
+ [s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')],
+ [s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')],
+ [s__('Timeago|a day ago'), s__('Timeago|1 day remaining')],
+ [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
+ [s__('Timeago|a week ago'), s__('Timeago|1 week remaining')],
+ [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
+ [s__('Timeago|a month ago'), s__('Timeago|1 month remaining')],
+ [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
+ [s__('Timeago|a year ago'), s__('Timeago|1 year remaining')],
+ [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
+ ][index];
};
-
- w.gl.utils.getDayName = function(date) {
- return this.days[date.getDay()];
+ const locale = function getLocale(number, index) {
+ return [
+ [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
+ [s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
+ [s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
+ [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
+ [s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')],
+ [s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')],
+ [s__('Timeago|a day ago'), s__('Timeago|in 1 day')],
+ [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
+ [s__('Timeago|a week ago'), s__('Timeago|in 1 week')],
+ [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
+ [s__('Timeago|a month ago'), s__('Timeago|in 1 month')],
+ [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
+ [s__('Timeago|a year ago'), s__('Timeago|in 1 year')],
+ [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
+ ][index];
};
- w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) {
- $timeagoEls.each((i, el) => {
- if (setTimeago) {
- // Recreate with custom template
- $(el).tooltip({
- template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
- });
- }
+ timeago.register(lang, locale);
+ timeago.register(`${lang}-remaining`, localeRemaining);
+ timeagoInstance = timeago();
+ }
- el.classList.add('js-timeago-render');
- });
+ return timeagoInstance;
+}
- gl.utils.renderTimeago($timeagoEls);
- };
+/**
+ * For the given element, renders a timeago instance.
+ * @param {jQuery} $els
+ */
+export const renderTimeago = ($els) => {
+ const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
- w.gl.utils.getTimeago = function() {
- var locale;
-
- if (!timeagoInstance) {
- const localeRemaining = function(number, index) {
- return [
- [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
- [s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
- [s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
- [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
- [s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')],
- [s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')],
- [s__('Timeago|a day ago'), s__('Timeago|1 day remaining')],
- [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
- [s__('Timeago|a week ago'), s__('Timeago|1 week remaining')],
- [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
- [s__('Timeago|a month ago'), s__('Timeago|1 month remaining')],
- [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
- [s__('Timeago|a year ago'), s__('Timeago|1 year remaining')],
- [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')]
- ][index];
- };
- locale = function(number, index) {
- return [
- [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
- [s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
- [s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
- [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
- [s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')],
- [s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')],
- [s__('Timeago|a day ago'), s__('Timeago|in 1 day')],
- [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
- [s__('Timeago|a week ago'), s__('Timeago|in 1 week')],
- [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
- [s__('Timeago|a month ago'), s__('Timeago|in 1 month')],
- [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
- [s__('Timeago|a year ago'), s__('Timeago|in 1 year')],
- [s__('Timeago|%s years ago'), s__('Timeago|in %s years')]
- ][index];
- };
-
- timeago.register(lang, locale);
- timeago.register(`${lang}-remaining`, localeRemaining);
- timeagoInstance = timeago();
- }
-
- return timeagoInstance;
- };
+ // timeago.js sets timeouts internally for each timeago value to be updated in real time
+ getTimeago().render(timeagoEls, lang);
+};
- w.gl.utils.timeFor = function(time, suffix, expiredLabel) {
- var timefor;
- if (!time) {
- return '';
- }
- if (new Date(time) < new Date()) {
- expiredLabel || (expiredLabel = s__('Timeago|Past due'));
- timefor = expiredLabel;
- } else {
- timefor = gl.utils.getTimeago().format(time, `${lang}-remaining`).trim();
- }
- return timefor;
- };
+/**
+ * For the given elements, sets a tooltip with a formatted date.
+ * @param {jQuery}
+ * @param {Boolean} setTimeago
+ */
+export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
+ $timeagoEls.each((i, el) => {
+ if (setTimeago) {
+ // Recreate with custom template
+ $(el).tooltip({
+ template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
+ });
+ }
- w.gl.utils.renderTimeago = function($els) {
- const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
+ el.classList.add('js-timeago-render');
+ });
- // timeago.js sets timeouts internally for each timeago value to be updated in real time
- gl.utils.getTimeago().render(timeagoEls, lang);
- };
+ renderTimeago($timeagoEls);
+};
- w.gl.utils.getDayDifference = function(a, b) {
- var millisecondsPerDay = 1000 * 60 * 60 * 24;
- var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
- var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
+/**
+ * Returns remaining or passed time over the given time.
+ * @param {*} time
+ * @param {*} expiredLabel
+ */
+export const timeFor = (time, expiredLabel) => {
+ if (!time) {
+ return '';
+ }
+ if (new Date(time) < new Date()) {
+ return expiredLabel || s__('Timeago|Past due');
+ }
+ return getTimeago().format(time, `${lang}-remaining`).trim();
+};
- return Math.floor((date2 - date1) / millisecondsPerDay);
- };
- })(window);
-}).call(window);
+export const getDayDifference = (a, b) => {
+ const millisecondsPerDay = 1000 * 60 * 60 * 24;
+ const date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
+ const date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
+
+ return Math.floor((date2 - date1) / millisecondsPerDay);
+};
/**
* Port of ruby helper time_interval_in_words.
@@ -162,3 +163,10 @@ export function dateInWords(date, abbreviated = false) {
return `${monthName} ${date.getDate()}, ${year}`;
}
+
+window.gl = window.gl || {};
+window.gl.utils = {
+ ...(window.gl.utils || {}),
+ getTimeago,
+ localTimeAgo,
+};
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 9e4047b6840..96284c4c168 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -1,6 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
/* global ConfirmDangerModal */
-/* global Aside */
import jQuery from 'jquery';
import _ from 'underscore';
@@ -28,7 +27,7 @@ import './commit/image_file';
// lib/utils
import { handleLocationHash } from './lib/utils/common_utils';
-import './lib/utils/datetime_utility';
+import { localTimeAgo, renderTimeago } from './lib/utils/datetime_utility';
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// behaviors
@@ -37,7 +36,6 @@ import './behaviors/';
// everything else
import './activities';
import './admin';
-import './aside';
import loadAwardsHandler from './awards_handler';
import bp from './breakpoints';
import './confirm_danger_modal';
@@ -60,15 +58,10 @@ import './notifications_dropdown';
import './notifications_form';
import './pager';
import './preview_markdown';
-import './project_find_file';
import './project_import';
import './projects_dropdown';
-import './projects_list';
-import './syntax_highlight';
import './render_gfm';
import './right_sidebar';
-import './search';
-import './search_autocomplete';
import initBreadcrumbs from './breadcrumb';
import './dispatcher';
@@ -125,7 +118,7 @@ $(function () {
});
if (bootstrapBreakpoint === 'xs') {
- const $rightSidebar = $('aside.right-sidebar, .page-with-sidebar');
+ const $rightSidebar = $('aside.right-sidebar, .layout-page');
$rightSidebar
.removeClass('right-sidebar-expanded')
@@ -185,13 +178,13 @@ $(function () {
trigger: 'focus',
// set the viewport to the main content, excluding the navigation bar, so
// the navigation can't overlap the popover
- viewport: '.page-with-sidebar'
+ viewport: '.layout-page'
});
$('.trigger-submit').on('change', function () {
return $(this).parents('form').submit();
// Form submitter
});
- gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
+ localTimeAgo($('abbr.timeago, .js-timeago'), true);
// Disable form buttons while a form is submitting
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
var buttons;
@@ -278,9 +271,8 @@ $(function () {
return fitSidebarForSize();
});
loadAwardsHandler();
- new Aside();
- gl.utils.renderTimeago();
+ renderTimeago();
$(document).trigger('init.scrolling-tabs');
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 17591829b76..94561d6b7c3 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -10,6 +10,7 @@ import './mixins/line_conflict_actions';
import './components/diff_file_editor';
import './components/inline_conflict_lines';
import './components/parallel_conflict_lines';
+import syntaxHighlight from '../syntax_highlight';
$(() => {
const INTERACTIVE_RESOLVE_MODE = 'interactive';
@@ -53,7 +54,7 @@ $(() => {
mergeConflictsStore.setLoadingState(false);
this.$nextTick(() => {
- $('.js-syntax-highlight').syntaxHighlight();
+ syntaxHighlight($('.js-syntax-highlight'));
});
});
},
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 589e65647ac..de84e28f915 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -14,6 +14,8 @@ import {
import { getLocationHash } from './lib/utils/url_utility';
import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
+import { localTimeAgo } from './lib/utils/datetime_utility';
+import syntaxHighlight from './syntax_highlight';
/* eslint-disable max-len */
// MergeRequestTabs
@@ -247,7 +249,7 @@ import Diff from './diff';
url: `${source}.json`,
success: (data) => {
document.querySelector('div#commits').innerHTML = data.html;
- gl.utils.localTimeAgo($('.js-timeago', 'div#commits'));
+ localTimeAgo($('.js-timeago', 'div#commits'));
this.commitsLoaded = true;
this.scrollToElement('#commits');
},
@@ -294,8 +296,8 @@ import Diff from './diff';
gl.diffNotesCompileComponents();
}
- gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
- $('#diffs .js-syntax-highlight').syntaxHighlight();
+ localTimeAgo($('.js-timeago', 'div#diffs'));
+ syntaxHighlight($('#diffs .js-syntax-highlight'));
if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) {
this.expandViewContainer();
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 74e5a4f1cea..2e5e818d61d 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -2,6 +2,7 @@
/* global Issuable */
/* global ListMilestone */
import _ from 'underscore';
+import { timeFor } from './lib/utils/datetime_utility';
(function() {
this.MilestoneSelect = (function() {
@@ -216,7 +217,7 @@ import _ from 'underscore';
$value.css('display', '');
if (data.milestone != null) {
data.milestone.full_path = _this.currentProject.full_path;
- data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
+ data.milestone.remaining = timeFor(data.milestone.due_date);
data.milestone.name = data.milestone.title;
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 2a570ac705e..042fe44e1c6 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -25,6 +25,7 @@ import Autosave from './autosave';
import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
+import { localTimeAgo } from './lib/utils/datetime_utility';
window.autosize = Autosize;
@@ -311,7 +312,7 @@ export default class Notes {
setupNewNote($note) {
// Update datetime format on the recent note
- gl.utils.localTimeAgo($note.find('.js-timeago'), false);
+ localTimeAgo($note.find('.js-timeago'), false);
this.collapseLongCommitList();
this.taskList.init();
@@ -463,7 +464,7 @@ export default class Notes {
this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
}
- gl.utils.localTimeAgo($('.js-timeago'), false);
+ localTimeAgo($('.js-timeago'), false);
Notes.checkMergeRequestStatus();
return this.updateNotesCount(1);
}
diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue
index 0eaac8dd64f..78322f30685 100644
--- a/app/assets/javascripts/pipelines/components/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/empty_state.vue
@@ -1,36 +1,41 @@
<script>
-export default {
- props: {
- helpPagePath: {
- type: String,
- required: true,
+ export default {
+ props: {
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ emptyStateSvgPath: {
+ type: String,
+ required: true,
+ },
},
- emptyStateSvgPath: {
- type: String,
- required: true,
- },
- },
-};
+ };
</script>
-
<template>
<div class="row empty-state js-empty-state">
<div class="col-xs-12">
- <div class="svg-content">
- <img :src="emptyStateSvgPath"/>
+ <div class="svg-content svg-250">
+ <img :src="emptyStateSvgPath" />
</div>
</div>
- <div class="col-xs-12 text-center">
+ <div class="col-xs-12">
<div class="text-content">
- <h4>Build with confidence</h4>
+ <h4 class="text-center">
+ {{ s__("Pipelines|Build with confidence") }}
+ </h4>
<p>
- Continous Integration can help catch bugs by running your tests automatically,
- while Continuous Deployment can help you deliver code to your product environment.
+ {{ s__("Pipelines|Continous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment.") }}
</p>
- <a :href="helpPagePath" class="btn btn-info">
- Get started with Pipelines
- </a>
+ <div class="text-center">
+ <a
+ :href="helpPagePath"
+ class="btn btn-info"
+ >
+ {{ s__("Pipelines|Get started with Pipelines") }}
+ </a>
+ </div>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/pipelines_bundle.js b/app/assets/javascripts/pipelines/pipelines_bundle.js
index 923d9bfb248..3e4b6eeb5bf 100644
--- a/app/assets/javascripts/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/pipelines/pipelines_bundle.js
@@ -1,6 +1,9 @@
import Vue from 'vue';
import PipelinesStore from './stores/pipelines_store';
import pipelinesComponent from './components/pipelines.vue';
+import Translate from '../vue_shared/translate';
+
+Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#pipelines-list-vue',
diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
index 6348a2e331d..78be6b6e884 100644
--- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue
+++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
@@ -1,5 +1,5 @@
<script>
- import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
+ import modal from '../../../vue_shared/components/modal.vue';
import { __, s__, sprintf } from '../../../locale';
import csrf from '../../../lib/utils/csrf';
@@ -26,7 +26,7 @@
};
},
components: {
- popupDialog,
+ modal,
},
computed: {
csrfToken() {
@@ -89,7 +89,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
<template>
<div>
- <popup-dialog
+ <modal
v-if="isOpen"
:title="s__('Profiles|Delete your account?')"
:text="text"
@@ -134,7 +134,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
</form>
</template>
- </popup-dialog>
+ </modal>
<button
type="button"
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 19682b20a4a..0da32b4a3cc 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -2,169 +2,163 @@
import fuzzaldrinPlus from 'fuzzaldrin-plus';
-(function() {
- this.ProjectFindFile = (function() {
- var highlighter;
-
- function ProjectFindFile(element1, options) {
- this.element = element1;
- this.options = options;
- this.goToBlob = this.goToBlob.bind(this);
- this.goToTree = this.goToTree.bind(this);
- this.selectRowDown = this.selectRowDown.bind(this);
- this.selectRowUp = this.selectRowUp.bind(this);
- this.filePaths = {};
- this.inputElement = this.element.find(".file-finder-input");
- // init event
- this.initEvent();
- // focus text input box
- this.inputElement.focus();
- // load file list
- this.load(this.options.url);
+// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
+const highlighter = function(element, text, matches) {
+ var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
+ lastIndex = 0;
+ highlightText = "";
+ matchedChars = [];
+ for (j = 0, len = matches.length; j < len; j += 1) {
+ matchIndex = matches[j];
+ unmatched = text.substring(lastIndex, matchIndex);
+ if (unmatched) {
+ if (matchedChars.length) {
+ element.append(matchedChars.join("").bold());
+ }
+ matchedChars = [];
+ element.append(document.createTextNode(unmatched));
}
-
- ProjectFindFile.prototype.initEvent = function() {
- this.inputElement.off("keyup");
- this.inputElement.on("keyup", (function(_this) {
- return function(event) {
- var oldValue, ref, target, value;
- target = $(event.target);
- value = target.val();
- oldValue = (ref = target.data("oldValue")) != null ? ref : "";
- if (value !== oldValue) {
- target.data("oldValue", value);
- _this.findFile();
- return _this.element.find("tr.tree-item").eq(0).addClass("selected").focus();
- }
- };
- })(this));
- };
-
- ProjectFindFile.prototype.findFile = function() {
- var result, searchText;
- searchText = this.inputElement.val();
- result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
- return this.renderList(result, searchText);
- // find file
- };
+ matchedChars.push(text[matchIndex]);
+ lastIndex = matchIndex + 1;
+ }
+ if (matchedChars.length) {
+ element.append(matchedChars.join("").bold());
+ }
+ return element.append(document.createTextNode(text.substring(lastIndex)));
+};
+
+export default class ProjectFindFile {
+ constructor(element1, options) {
+ this.element = element1;
+ this.options = options;
+ this.goToBlob = this.goToBlob.bind(this);
+ this.goToTree = this.goToTree.bind(this);
+ this.selectRowDown = this.selectRowDown.bind(this);
+ this.selectRowUp = this.selectRowUp.bind(this);
+ this.filePaths = {};
+ this.inputElement = this.element.find(".file-finder-input");
+ // init event
+ this.initEvent();
+ // focus text input box
+ this.inputElement.focus();
+ // load file list
+ this.load(this.options.url);
+ }
+
+ initEvent() {
+ this.inputElement.off("keyup");
+ this.inputElement.on("keyup", (function(_this) {
+ return function(event) {
+ var oldValue, ref, target, value;
+ target = $(event.target);
+ value = target.val();
+ oldValue = (ref = target.data("oldValue")) != null ? ref : "";
+ if (value !== oldValue) {
+ target.data("oldValue", value);
+ _this.findFile();
+ return _this.element.find("tr.tree-item").eq(0).addClass("selected").focus();
+ }
+ };
+ })(this));
+ }
+
+ findFile() {
+ var result, searchText;
+ searchText = this.inputElement.val();
+ result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
+ return this.renderList(result, searchText);
+ // find file
+ }
// files pathes load
- ProjectFindFile.prototype.load = function(url) {
- return $.ajax({
- url: url,
- method: "get",
- dataType: "json",
- success: (function(_this) {
- return function(data) {
- _this.element.find(".loading").hide();
- _this.filePaths = data;
- _this.findFile();
- return _this.element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus();
- };
- })(this)
- });
- };
+ load(url) {
+ return $.ajax({
+ url: url,
+ method: "get",
+ dataType: "json",
+ success: (function(_this) {
+ return function(data) {
+ _this.element.find(".loading").hide();
+ _this.filePaths = data;
+ _this.findFile();
+ return _this.element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus();
+ };
+ })(this)
+ });
+ }
// render result
- ProjectFindFile.prototype.renderList = function(filePaths, searchText) {
- var blobItemUrl, filePath, html, i, j, len, matches, results;
- this.element.find(".tree-table > tbody").empty();
- results = [];
- for (i = j = 0, len = filePaths.length; j < len; i = (j += 1)) {
- filePath = filePaths[i];
- if (i === 20) {
- break;
- }
- if (searchText) {
- matches = fuzzaldrinPlus.match(filePath, searchText);
- }
- blobItemUrl = this.options.blobUrlTemplate + "/" + filePath;
- html = this.makeHtml(filePath, matches, blobItemUrl);
- results.push(this.element.find(".tree-table > tbody").append(html));
- }
- return results;
- };
-
- // highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
- highlighter = function(element, text, matches) {
- var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
- lastIndex = 0;
- highlightText = "";
- matchedChars = [];
- for (j = 0, len = matches.length; j < len; j += 1) {
- matchIndex = matches[j];
- unmatched = text.substring(lastIndex, matchIndex);
- if (unmatched) {
- if (matchedChars.length) {
- element.append(matchedChars.join("").bold());
- }
- matchedChars = [];
- element.append(document.createTextNode(unmatched));
- }
- matchedChars.push(text[matchIndex]);
- lastIndex = matchIndex + 1;
- }
- if (matchedChars.length) {
- element.append(matchedChars.join("").bold());
+ renderList(filePaths, searchText) {
+ var blobItemUrl, filePath, html, i, j, len, matches, results;
+ this.element.find(".tree-table > tbody").empty();
+ results = [];
+ for (i = j = 0, len = filePaths.length; j < len; i = (j += 1)) {
+ filePath = filePaths[i];
+ if (i === 20) {
+ break;
}
- return element.append(document.createTextNode(text.substring(lastIndex)));
- };
-
- // make tbody row html
- ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
- var $tr;
- $tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>");
- if (matches) {
- $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
- } else {
- $tr.find("a").attr("href", blobItemUrl);
- $tr.find(".str-truncated").text(filePath);
+ if (searchText) {
+ matches = fuzzaldrinPlus.match(filePath, searchText);
}
- return $tr;
- };
-
- ProjectFindFile.prototype.selectRow = function(type) {
- var next, rows, selectedRow;
- rows = this.element.find(".files-slider tr.tree-item");
- selectedRow = this.element.find(".files-slider tr.tree-item.selected");
- if (rows && rows.length > 0) {
- if (selectedRow && selectedRow.length > 0) {
- if (type === "UP") {
- next = selectedRow.prev();
- } else if (type === "DOWN") {
- next = selectedRow.next();
- }
- if (next.length > 0) {
- selectedRow.removeClass("selected");
- selectedRow = next;
- }
- } else {
- selectedRow = rows.eq(0);
+ blobItemUrl = this.options.blobUrlTemplate + "/" + filePath;
+ html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
+ results.push(this.element.find(".tree-table > tbody").append(html));
+ }
+ return results;
+ }
+
+ // make tbody row html
+ static makeHtml(filePath, matches, blobItemUrl) {
+ var $tr;
+ $tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>");
+ if (matches) {
+ $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
+ } else {
+ $tr.find("a").attr("href", blobItemUrl);
+ $tr.find(".str-truncated").text(filePath);
+ }
+ return $tr;
+ }
+
+ selectRow(type) {
+ var next, rows, selectedRow;
+ rows = this.element.find(".files-slider tr.tree-item");
+ selectedRow = this.element.find(".files-slider tr.tree-item.selected");
+ if (rows && rows.length > 0) {
+ if (selectedRow && selectedRow.length > 0) {
+ if (type === "UP") {
+ next = selectedRow.prev();
+ } else if (type === "DOWN") {
+ next = selectedRow.next();
+ }
+ if (next.length > 0) {
+ selectedRow.removeClass("selected");
+ selectedRow = next;
}
- return selectedRow.addClass("selected").focus();
+ } else {
+ selectedRow = rows.eq(0);
}
- };
-
- ProjectFindFile.prototype.selectRowUp = function() {
- return this.selectRow("UP");
- };
+ return selectedRow.addClass("selected").focus();
+ }
+ }
- ProjectFindFile.prototype.selectRowDown = function() {
- return this.selectRow("DOWN");
- };
+ selectRowUp() {
+ return this.selectRow("UP");
+ }
- ProjectFindFile.prototype.goToTree = function() {
- return location.href = this.options.treeUrl;
- };
+ selectRowDown() {
+ return this.selectRow("DOWN");
+ }
- ProjectFindFile.prototype.goToBlob = function() {
- var $link = this.element.find(".tree-item.selected .tree-item-file-name a");
+ goToTree() {
+ return location.href = this.options.treeUrl;
+ }
- if ($link.length) {
- $link.get(0).click();
- }
- };
+ goToBlob() {
+ var $link = this.element.find(".tree-item.selected .tree-item-file-name a");
- return ProjectFindFile;
- })();
-}).call(window);
+ if ($link.length) {
+ $link.get(0).click();
+ }
+ }
+}
diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js
index c91a0d9ba41..5482c55f8bb 100644
--- a/app/assets/javascripts/render_gfm.js
+++ b/app/assets/javascripts/render_gfm.js
@@ -1,12 +1,12 @@
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
-
+import syntaxHighlight from './syntax_highlight';
// Render Gitlab flavoured Markdown
//
// Delegates to syntax highlight and render math & mermaid diagrams.
//
$.fn.renderGFM = function renderGFM() {
- this.find('.js-syntax-highlight').syntaxHighlight();
+ syntaxHighlight(this.find('.js-syntax-highlight'));
renderMath(this.find('.js-render-math'));
renderMermaid(this.find('.js-render-mermaid'));
return this;
diff --git a/app/assets/javascripts/repo/components/new_dropdown/modal.vue b/app/assets/javascripts/repo/components/new_dropdown/modal.vue
index ac1f613bb71..c191af7dec3 100644
--- a/app/assets/javascripts/repo/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/repo/components/new_dropdown/modal.vue
@@ -1,7 +1,7 @@
<script>
import { mapActions } from 'vuex';
import { __ } from '../../../locale';
- import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
+ import modal from '../../../vue_shared/components/modal.vue';
export default {
props: {
@@ -20,7 +20,7 @@
};
},
components: {
- popupDialog,
+ modal,
},
methods: {
...mapActions([
@@ -68,7 +68,7 @@
</script>
<template>
- <popup-dialog
+ <modal
:title="modalTitle"
:primary-button-label="buttonLabel"
kind="success"
@@ -94,5 +94,5 @@
</div>
</fieldset>
</form>
- </popup-dialog>
+ </modal>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue
index d3344d0c8dc..4e0178072cb 100644
--- a/app/assets/javascripts/repo/components/repo_commit_section.vue
+++ b/app/assets/javascripts/repo/components/repo_commit_section.vue
@@ -2,12 +2,12 @@
import { mapGetters, mapState, mapActions } from 'vuex';
import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue';
-import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
+import modal from '../../vue_shared/components/modal.vue';
import commitFilesList from './commit_sidebar/list.vue';
export default {
components: {
- PopupDialog,
+ modal,
icon,
commitFilesList,
},
@@ -16,7 +16,7 @@ export default {
},
data() {
return {
- showNewBranchDialog: false,
+ showNewBranchModal: false,
submitCommitsLoading: false,
startNewMR: false,
commitMessage: '',
@@ -58,7 +58,7 @@ export default {
start_branch: createNewBranch ? this.currentBranch : undefined,
};
- this.showNewBranchDialog = false;
+ this.showNewBranchModal = false;
this.submitCommitsLoading = true;
this.commitChanges({ payload, newMr: this.startNewMR })
@@ -76,7 +76,7 @@ export default {
this.checkCommitStatus()
.then((branchChanged) => {
if (branchChanged) {
- this.showNewBranchDialog = true;
+ this.showNewBranchModal = true;
} else {
this.makeCommit();
}
@@ -99,13 +99,13 @@ export default {
'is-collapsed': collapsed,
}"
>
- <popup-dialog
- v-if="showNewBranchDialog"
+ <modal
+ v-if="showNewBranchModal"
:primary-button-label="__('Create new branch')"
kind="primary"
:title="__('Branch has changed')"
:text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
- @toggle="showNewBranchDialog = false"
+ @toggle="showNewBranchModal = false"
@submit="makeCommit(true)"
/>
<button
diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/repo/components/repo_edit_button.vue
index 6c1bb4b8566..37bd9003e96 100644
--- a/app/assets/javascripts/repo/components/repo_edit_button.vue
+++ b/app/assets/javascripts/repo/components/repo_edit_button.vue
@@ -1,10 +1,10 @@
<script>
import { mapGetters, mapActions, mapState } from 'vuex';
-import popupDialog from '../../vue_shared/components/popup_dialog.vue';
+import modal from '../../vue_shared/components/modal.vue';
export default {
components: {
- popupDialog,
+ modal,
},
computed: {
...mapState([
@@ -43,7 +43,7 @@ export default {
{{buttonLabel}}
</span>
</button>
- <popup-dialog
+ <modal
v-if="discardPopupOpen"
class="text-left"
:primary-button-label="__('Discard changes')"
diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/repo/components/repo_preview.vue
index 6ce9267f598..425c55fafb5 100644
--- a/app/assets/javascripts/repo/components/repo_preview.vue
+++ b/app/assets/javascripts/repo/components/repo_preview.vue
@@ -1,6 +1,7 @@
<script>
/* global LineHighlighter */
import { mapGetters } from 'vuex';
+import syntaxHighlight from '../../syntax_highlight';
export default {
computed: {
@@ -13,7 +14,7 @@ export default {
},
methods: {
highlightFile() {
- $(this.$el).find('.file-content').syntaxHighlight();
+ syntaxHighlight($(this.$el).find('.file-content'));
},
},
mounted() {
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index fa7f6825d7e..ec85b8b6529 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -42,11 +42,11 @@ import Cookies from 'js-cookie';
if ($thisIcon.hasClass('fa-angle-double-right')) {
$allGutterToggleIcons.removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
$('aside.right-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
- $('.page-with-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
+ $('.layout-page').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed');
} else {
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
- $('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+ $('.layout-page').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
}
@@ -173,7 +173,7 @@ import Cookies from 'js-cookie';
Sidebar.prototype.setCollapseAfterUpdate = function($block) {
$block.addClass('collapse-after-update');
- return $('.page-with-sidebar').addClass('with-overlay');
+ return $('.layout-page').addClass('with-overlay');
};
Sidebar.prototype.onSidebarDropdownHidden = function(e) {
@@ -187,7 +187,7 @@ import Cookies from 'js-cookie';
Sidebar.prototype.sidebarDropdownHidden = function($block) {
if ($block.hasClass('collapse-after-update')) {
$block.removeClass('collapse-after-update');
- $('.page-with-sidebar').removeClass('with-overlay');
+ $('.layout-page').removeClass('with-overlay');
return this.toggleSidebar('hide');
}
};
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index 07fee53d814..363322af47a 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -1,118 +1,113 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */
import Flash from './flash';
import Api from './api';
-(function() {
- this.Search = (function() {
- function Search() {
- var $groupDropdown, $projectDropdown;
- $groupDropdown = $('.js-search-group-dropdown');
- $projectDropdown = $('.js-search-project-dropdown');
- this.groupId = $groupDropdown.data('group-id');
- this.eventListeners();
- $groupDropdown.glDropdown({
- selectable: true,
- filterable: true,
- fieldName: 'group_id',
- search: {
- fields: ['full_name']
- },
- data: function(term, callback) {
- return Api.groups(term, {}, function(data) {
+export default class Search {
+ constructor() {
+ const $groupDropdown = $('.js-search-group-dropdown');
+ const $projectDropdown = $('.js-search-project-dropdown');
+
+ this.searchInput = '.js-search-input';
+ this.searchClear = '.js-search-clear';
+
+ this.groupId = $groupDropdown.data('group-id');
+ this.eventListeners();
+
+ $groupDropdown.glDropdown({
+ selectable: true,
+ filterable: true,
+ fieldName: 'group_id',
+ search: {
+ fields: ['full_name'],
+ },
+ data(term, callback) {
+ return Api.groups(term, {}, (data) => {
+ data.unshift({
+ full_name: 'Any',
+ });
+ data.splice(1, 0, 'divider');
+ return callback(data);
+ });
+ },
+ id(obj) {
+ return obj.id;
+ },
+ text(obj) {
+ return obj.full_name;
+ },
+ toggleLabel(obj) {
+ return `${($groupDropdown.data('default-label'))} ${obj.full_name}`;
+ },
+ clicked: () => Search.submitSearch(),
+ });
+
+ $projectDropdown.glDropdown({
+ selectable: true,
+ filterable: true,
+ fieldName: 'project_id',
+ search: {
+ fields: ['name'],
+ },
+ data: (term, callback) => {
+ this.getProjectsData(term)
+ .then((data) => {
data.unshift({
- full_name: 'Any'
+ name_with_namespace: 'Any',
});
data.splice(1, 0, 'divider');
- return callback(data);
- });
- },
- id: function(obj) {
- return obj.id;
- },
- text: function(obj) {
- return obj.full_name;
- },
- toggleLabel: function(obj) {
- return ($groupDropdown.data('default-label')) + " " + obj.full_name;
- },
- clicked: (function(_this) {
- return function() {
- return _this.submitSearch();
- };
- })(this)
- });
- $projectDropdown.glDropdown({
- selectable: true,
- filterable: true,
- fieldName: 'project_id',
- search: {
- fields: ['name']
- },
- data: (term, callback) => {
- this.getProjectsData(term)
- .then((data) => {
- data.unshift({
- name_with_namespace: 'Any'
- });
- data.splice(1, 0, 'divider');
- return data;
- })
- .then(data => callback(data))
- .catch(() => new Flash('Error fetching projects'));
- },
- id: function(obj) {
- return obj.id;
- },
- text: function(obj) {
- return obj.name_with_namespace;
- },
- toggleLabel: function(obj) {
- return ($projectDropdown.data('default-label')) + " " + obj.name_with_namespace;
- },
- clicked: (function(_this) {
- return function() {
- return _this.submitSearch();
- };
- })(this)
- });
- }
+ return data;
+ })
+ .then(data => callback(data))
+ .catch(() => new Flash('Error fetching projects'));
+ },
+ id(obj) {
+ return obj.id;
+ },
+ text(obj) {
+ return obj.name_with_namespace;
+ },
+ toggleLabel(obj) {
+ return `${($projectDropdown.data('default-label'))} ${obj.name_with_namespace}`;
+ },
+ clicked: () => Search.submitSearch(),
+ });
+ }
- Search.prototype.eventListeners = function() {
- $(document).off('keyup', '.js-search-input').on('keyup', '.js-search-input', this.searchKeyUp);
- return $(document).off('click', '.js-search-clear').on('click', '.js-search-clear', this.clearSearchField);
- };
+ eventListeners() {
+ $(document)
+ .off('keyup', this.searchInput)
+ .on('keyup', this.searchInput, this.searchKeyUp);
+ $(document)
+ .off('click', this.searchClear)
+ .on('click', this.searchClear, this.clearSearchField.bind(this));
+ }
- Search.prototype.submitSearch = function() {
- return $('.js-search-form').submit();
- };
+ static submitSearch() {
+ return $('.js-search-form').submit();
+ }
- Search.prototype.searchKeyUp = function() {
- var $input;
- $input = $(this);
- if ($input.val() === '') {
- return $('.js-search-clear').addClass('hidden');
- } else {
- return $('.js-search-clear').removeClass('hidden');
- }
- };
-
- Search.prototype.clearSearchField = function() {
- return $('.js-search-input').val('').trigger('keyup').focus();
- };
+ searchKeyUp() {
+ const $input = $(this);
+ if ($input.val() === '') {
+ $('.js-search-clear').addClass('hidden');
+ } else {
+ $('.js-search-clear').removeClass('hidden');
+ }
+ }
- Search.prototype.getProjectsData = function(term) {
- return new Promise((resolve) => {
- if (this.groupId) {
- Api.groupProjects(this.groupId, term, resolve);
- } else {
- Api.projects(term, {
- order_by: 'id',
- }, resolve);
- }
- });
- };
+ clearSearchField() {
+ return $(this.searchInput).val('').trigger('keyup').focus();
+ }
- return Search;
- })();
-}).call(window);
+ getProjectsData(term) {
+ return new Promise((resolve) => {
+ if (this.groupId) {
+ Api.groupProjects(this.groupId, term, resolve);
+ } else {
+ Api.projects(term, {
+ order_by: 'id',
+ }, resolve);
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index e40a3596200..98b524f7e3f 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -8,448 +8,445 @@ import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from '.
* When the user clicks `x` button it cleans the input and closes the dropdown.
*/
-((global) => {
- const KEYCODE = {
- ESCAPE: 27,
- BACKSPACE: 8,
- ENTER: 13,
- UP: 38,
- DOWN: 40,
- };
-
- class SearchAutocomplete {
- constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
- this.bindEventContext();
- this.wrap = wrap || $('.search');
- this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
- this.autocompletePath = autocompletePath || this.optsEl.data('autocomplete-path');
- this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || '');
- this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || '');
- this.dropdown = this.wrap.find('.dropdown');
- this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
- this.dropdownContent = this.dropdown.find('.dropdown-content');
- this.locationBadgeEl = this.getElement('.location-badge');
- this.scopeInputEl = this.getElement('#scope');
- this.searchInput = this.getElement('.search-input');
- this.projectInputEl = this.getElement('#search_project_id');
- this.groupInputEl = this.getElement('#group_id');
- this.searchCodeInputEl = this.getElement('#search_code');
- this.repositoryInputEl = this.getElement('#repository_ref');
- this.clearInput = this.getElement('.js-clear-input');
- this.saveOriginalState();
-
- // Only when user is logged in
- if (gon.current_user_id) {
- this.createAutocomplete();
- }
+const KEYCODE = {
+ ESCAPE: 27,
+ BACKSPACE: 8,
+ ENTER: 13,
+ UP: 38,
+ DOWN: 40,
+};
+
+function setSearchOptions() {
+ var $projectOptionsDataEl = $('.js-search-project-options');
+ var $groupOptionsDataEl = $('.js-search-group-options');
+ var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
+
+ if ($projectOptionsDataEl.length) {
+ gl.projectOptions = gl.projectOptions || {};
+
+ var projectPath = $projectOptionsDataEl.data('project-path');
+
+ gl.projectOptions[projectPath] = {
+ name: $projectOptionsDataEl.data('name'),
+ issuesPath: $projectOptionsDataEl.data('issues-path'),
+ issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
+ mrPath: $projectOptionsDataEl.data('mr-path'),
+ };
+ }
- this.searchInput.addClass('disabled');
- this.saveTextLength();
- this.bindEvents();
- this.dropdownToggle.dropdown();
- }
+ if ($groupOptionsDataEl.length) {
+ gl.groupOptions = gl.groupOptions || {};
- // Finds an element inside wrapper element
- bindEventContext() {
- this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
- this.onClearInputClick = this.onClearInputClick.bind(this);
- this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
- this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
- this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
- }
- getElement(selector) {
- return this.wrap.find(selector);
- }
+ var groupPath = $groupOptionsDataEl.data('group-path');
- saveOriginalState() {
- return this.originalState = this.serializeState();
- }
+ gl.groupOptions[groupPath] = {
+ name: $groupOptionsDataEl.data('name'),
+ issuesPath: $groupOptionsDataEl.data('issues-path'),
+ mrPath: $groupOptionsDataEl.data('mr-path'),
+ };
+ }
- saveTextLength() {
- return this.lastTextLength = this.searchInput.val().length;
+ if ($dashboardOptionsDataEl.length) {
+ gl.dashboardOptions = {
+ issuesPath: $dashboardOptionsDataEl.data('issues-path'),
+ mrPath: $dashboardOptionsDataEl.data('mr-path'),
+ };
+ }
+}
+
+export default class SearchAutocomplete {
+ constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
+ setSearchOptions();
+ this.bindEventContext();
+ this.wrap = wrap || $('.search');
+ this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
+ this.autocompletePath = autocompletePath || this.optsEl.data('autocomplete-path');
+ this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || '');
+ this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || '');
+ this.dropdown = this.wrap.find('.dropdown');
+ this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle');
+ this.dropdownContent = this.dropdown.find('.dropdown-content');
+ this.locationBadgeEl = this.getElement('.location-badge');
+ this.scopeInputEl = this.getElement('#scope');
+ this.searchInput = this.getElement('.search-input');
+ this.projectInputEl = this.getElement('#search_project_id');
+ this.groupInputEl = this.getElement('#group_id');
+ this.searchCodeInputEl = this.getElement('#search_code');
+ this.repositoryInputEl = this.getElement('#repository_ref');
+ this.clearInput = this.getElement('.js-clear-input');
+ this.saveOriginalState();
+
+ // Only when user is logged in
+ if (gon.current_user_id) {
+ this.createAutocomplete();
}
- createAutocomplete() {
- return this.searchInput.glDropdown({
- filterInputBlur: false,
- filterable: true,
- filterRemote: true,
- highlight: true,
- enterCallback: false,
- filterInput: 'input#search',
- search: {
- fields: ['text'],
- },
- id: this.getSearchText,
- data: this.getData.bind(this),
- selectable: true,
- clicked: this.onClick.bind(this),
- });
+ this.searchInput.addClass('disabled');
+ this.saveTextLength();
+ this.bindEvents();
+ this.dropdownToggle.dropdown();
+ }
+
+ // Finds an element inside wrapper element
+ bindEventContext() {
+ this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
+ this.onClearInputClick = this.onClearInputClick.bind(this);
+ this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
+ this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
+ this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
+ }
+ getElement(selector) {
+ return this.wrap.find(selector);
+ }
+
+ saveOriginalState() {
+ return this.originalState = this.serializeState();
+ }
+
+ saveTextLength() {
+ return this.lastTextLength = this.searchInput.val().length;
+ }
+
+ createAutocomplete() {
+ return this.searchInput.glDropdown({
+ filterInputBlur: false,
+ filterable: true,
+ filterRemote: true,
+ highlight: true,
+ enterCallback: false,
+ filterInput: 'input#search',
+ search: {
+ fields: ['text'],
+ },
+ id: this.getSearchText,
+ data: this.getData.bind(this),
+ selectable: true,
+ clicked: this.onClick.bind(this),
+ });
+ }
+
+ getSearchText(selectedObject, el) {
+ return selectedObject.id ? selectedObject.text : '';
+ }
+
+ getData(term, callback) {
+ if (!term) {
+ const contents = this.getCategoryContents();
+ if (contents) {
+ this.searchInput.data('glDropdown').filter.options.callback(contents);
+ this.enableAutocomplete();
+ }
+ return;
}
- getSearchText(selectedObject, el) {
- return selectedObject.id ? selectedObject.text : '';
+ // Prevent multiple ajax calls
+ if (this.loadingSuggestions) {
+ return;
}
- getData(term, callback) {
- if (!term) {
- const contents = this.getCategoryContents();
- if (contents) {
- this.searchInput.data('glDropdown').filter.options.callback(contents);
- this.enableAutocomplete();
- }
- return;
- }
+ this.loadingSuggestions = true;
- // Prevent multiple ajax calls
- if (this.loadingSuggestions) {
+ return $.get(this.autocompletePath, {
+ project_id: this.projectId,
+ project_ref: this.projectRef,
+ term: term,
+ }, (response) => {
+ var firstCategory, i, lastCategory, len, suggestion;
+ // Hide dropdown menu if no suggestions returns
+ if (!response.length) {
+ this.disableAutocomplete();
return;
}
- this.loadingSuggestions = true;
-
- return $.get(this.autocompletePath, {
- project_id: this.projectId,
- project_ref: this.projectRef,
- term: term,
- }, (response) => {
- var firstCategory, i, lastCategory, len, suggestion;
- // Hide dropdown menu if no suggestions returns
- if (!response.length) {
- this.disableAutocomplete();
- return;
- }
-
- const data = [];
- // List results
- firstCategory = true;
- for (i = 0, len = response.length; i < len; i += 1) {
- suggestion = response[i];
- // Add group header before list each group
- if (lastCategory !== suggestion.category) {
- if (!firstCategory) {
- data.push('separator');
- }
- if (firstCategory) {
- firstCategory = false;
- }
- data.push({
- header: suggestion.category,
- });
- lastCategory = suggestion.category;
+ const data = [];
+ // List results
+ firstCategory = true;
+ for (i = 0, len = response.length; i < len; i += 1) {
+ suggestion = response[i];
+ // Add group header before list each group
+ if (lastCategory !== suggestion.category) {
+ if (!firstCategory) {
+ data.push('separator');
+ }
+ if (firstCategory) {
+ firstCategory = false;
}
data.push({
- id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
- category: suggestion.category,
- text: suggestion.label,
- url: suggestion.url,
- });
- }
- // Add option to proceed with the search
- if (data.length) {
- data.push('separator');
- data.push({
- text: "Result name contains \"" + term + "\"",
- url: "/search?search=" + term + "&project_id=" + (this.projectInputEl.val()) + "&group_id=" + (this.groupInputEl.val()),
+ header: suggestion.category,
});
+ lastCategory = suggestion.category;
}
- return callback(data);
- })
- .always(() => { this.loadingSuggestions = false; });
- }
-
- getCategoryContents() {
- const userId = gon.current_user_id;
- const userName = gon.current_username;
- const { projectOptions, groupOptions, dashboardOptions } = gl;
-
- // Get options
- let options;
- if (isInGroupsPage() && groupOptions) {
- options = groupOptions[getGroupSlug()];
- } else if (isInProjectPage() && projectOptions) {
- options = projectOptions[getProjectSlug()];
- } else if (dashboardOptions) {
- options = dashboardOptions;
+ data.push({
+ id: (suggestion.category.toLowerCase()) + "-" + suggestion.id,
+ category: suggestion.category,
+ text: suggestion.label,
+ url: suggestion.url,
+ });
}
-
- const { issuesPath, mrPath, name, issuesDisabled } = options;
- const baseItems = [];
-
- if (name) {
- baseItems.push({
- header: `${name}`,
+ // Add option to proceed with the search
+ if (data.length) {
+ data.push('separator');
+ data.push({
+ text: "Result name contains \"" + term + "\"",
+ url: "/search?search=" + term + "&project_id=" + (this.projectInputEl.val()) + "&group_id=" + (this.groupInputEl.val()),
});
}
+ return callback(data);
+ })
+ .always(() => { this.loadingSuggestions = false; });
+ }
- const issueItems = [
- {
- text: 'Issues assigned to me',
- url: `${issuesPath}/?assignee_username=${userName}`,
- },
- {
- text: "Issues I've created",
- url: `${issuesPath}/?author_username=${userName}`,
- },
- ];
- const mergeRequestItems = [
- {
- text: 'Merge requests assigned to me',
- url: `${mrPath}/?assignee_username=${userName}`,
- },
- {
- text: "Merge requests I've created",
- url: `${mrPath}/?author_username=${userName}`,
- },
- ];
-
- let items;
- if (issuesDisabled) {
- items = baseItems.concat(mergeRequestItems);
- } else {
- items = baseItems.concat(...issueItems, 'separator', ...mergeRequestItems);
- }
- return items;
+ getCategoryContents() {
+ const userId = gon.current_user_id;
+ const userName = gon.current_username;
+ const { projectOptions, groupOptions, dashboardOptions } = gl;
+
+ // Get options
+ let options;
+ if (isInGroupsPage() && groupOptions) {
+ options = groupOptions[getGroupSlug()];
+ } else if (isInProjectPage() && projectOptions) {
+ options = projectOptions[getProjectSlug()];
+ } else if (dashboardOptions) {
+ options = dashboardOptions;
}
- serializeState() {
- return {
- // Search Criteria
- search_project_id: this.projectInputEl.val(),
- group_id: this.groupInputEl.val(),
- search_code: this.searchCodeInputEl.val(),
- repository_ref: this.repositoryInputEl.val(),
- scope: this.scopeInputEl.val(),
- // Location badge
- _location: this.locationBadgeEl.text(),
- };
+ const { issuesPath, mrPath, name, issuesDisabled } = options;
+ const baseItems = [];
+
+ if (name) {
+ baseItems.push({
+ header: `${name}`,
+ });
}
- bindEvents() {
- this.searchInput.on('keydown', this.onSearchInputKeyDown);
- this.searchInput.on('keyup', this.onSearchInputKeyUp);
- this.searchInput.on('focus', this.onSearchInputFocus);
- this.searchInput.on('blur', this.onSearchInputBlur);
- this.clearInput.on('click', this.onClearInputClick);
- this.locationBadgeEl.on('click', () => this.searchInput.focus());
+ const issueItems = [
+ {
+ text: 'Issues assigned to me',
+ url: `${issuesPath}/?assignee_username=${userName}`,
+ },
+ {
+ text: "Issues I've created",
+ url: `${issuesPath}/?author_username=${userName}`,
+ },
+ ];
+ const mergeRequestItems = [
+ {
+ text: 'Merge requests assigned to me',
+ url: `${mrPath}/?assignee_username=${userName}`,
+ },
+ {
+ text: "Merge requests I've created",
+ url: `${mrPath}/?author_username=${userName}`,
+ },
+ ];
+
+ let items;
+ if (issuesDisabled) {
+ items = baseItems.concat(mergeRequestItems);
+ } else {
+ items = baseItems.concat(...issueItems, 'separator', ...mergeRequestItems);
}
+ return items;
+ }
- enableAutocomplete() {
- // No need to enable anything if user is not logged in
- if (!gon.current_user_id) {
- return;
- }
+ serializeState() {
+ return {
+ // Search Criteria
+ search_project_id: this.projectInputEl.val(),
+ group_id: this.groupInputEl.val(),
+ search_code: this.searchCodeInputEl.val(),
+ repository_ref: this.repositoryInputEl.val(),
+ scope: this.scopeInputEl.val(),
+ // Location badge
+ _location: this.locationBadgeEl.text(),
+ };
+ }
- // If the dropdown is closed, we'll open it
- if (!this.dropdown.hasClass('open')) {
- this.loadingSuggestions = false;
- this.dropdownToggle.dropdown('toggle');
- return this.searchInput.removeClass('disabled');
- }
+ bindEvents() {
+ this.searchInput.on('keydown', this.onSearchInputKeyDown);
+ this.searchInput.on('keyup', this.onSearchInputKeyUp);
+ this.searchInput.on('focus', this.onSearchInputFocus);
+ this.searchInput.on('blur', this.onSearchInputBlur);
+ this.clearInput.on('click', this.onClearInputClick);
+ this.locationBadgeEl.on('click', () => this.searchInput.focus());
+ }
+
+ enableAutocomplete() {
+ // No need to enable anything if user is not logged in
+ if (!gon.current_user_id) {
+ return;
}
- // Saves last length of the entered text
- onSearchInputKeyDown() {
- return this.saveTextLength();
+ // If the dropdown is closed, we'll open it
+ if (!this.dropdown.hasClass('open')) {
+ this.loadingSuggestions = false;
+ this.dropdownToggle.dropdown('toggle');
+ return this.searchInput.removeClass('disabled');
}
+ }
- onSearchInputKeyUp(e) {
- switch (e.keyCode) {
- case KEYCODE.BACKSPACE:
- // when trying to remove the location badge
- if (this.lastTextLength === 0 && this.badgePresent()) {
- this.removeLocationBadge();
- }
- // When removing the last character and no badge is present
- if (this.lastTextLength === 1) {
- this.disableAutocomplete();
- }
- // When removing any character from existin value
- if (this.lastTextLength > 1) {
- this.enableAutocomplete();
- }
- break;
- case KEYCODE.ESCAPE:
- this.restoreOriginalState();
- break;
- case KEYCODE.ENTER:
+ // Saves last length of the entered text
+ onSearchInputKeyDown() {
+ return this.saveTextLength();
+ }
+
+ onSearchInputKeyUp(e) {
+ switch (e.keyCode) {
+ case KEYCODE.BACKSPACE:
+ // when trying to remove the location badge
+ if (this.lastTextLength === 0 && this.badgePresent()) {
+ this.removeLocationBadge();
+ }
+ // When removing the last character and no badge is present
+ if (this.lastTextLength === 1) {
+ this.disableAutocomplete();
+ }
+ // When removing any character from existin value
+ if (this.lastTextLength > 1) {
+ this.enableAutocomplete();
+ }
+ break;
+ case KEYCODE.ESCAPE:
+ this.restoreOriginalState();
+ break;
+ case KEYCODE.ENTER:
+ this.disableAutocomplete();
+ break;
+ case KEYCODE.UP:
+ case KEYCODE.DOWN:
+ return;
+ default:
+ // Handle the case when deleting the input value other than backspace
+ // e.g. Pressing ctrl + backspace or ctrl + x
+ if (this.searchInput.val() === '') {
this.disableAutocomplete();
- break;
- case KEYCODE.UP:
- case KEYCODE.DOWN:
- return;
- default:
- // Handle the case when deleting the input value other than backspace
- // e.g. Pressing ctrl + backspace or ctrl + x
- if (this.searchInput.val() === '') {
- this.disableAutocomplete();
- } else {
- // We should display the menu only when input is not empty
- if (e.keyCode !== KEYCODE.ENTER) {
- this.enableAutocomplete();
- }
+ } else {
+ // We should display the menu only when input is not empty
+ if (e.keyCode !== KEYCODE.ENTER) {
+ this.enableAutocomplete();
}
- }
- this.wrap.toggleClass('has-value', !!e.target.value);
+ }
}
+ this.wrap.toggleClass('has-value', !!e.target.value);
+ }
- onSearchInputFocus() {
- this.isFocused = true;
- this.wrap.addClass('search-active');
- if (this.getValue() === '') {
- return this.getData();
- }
+ onSearchInputFocus() {
+ this.isFocused = true;
+ this.wrap.addClass('search-active');
+ if (this.getValue() === '') {
+ return this.getData();
}
+ }
- getValue() {
- return this.searchInput.val();
- }
+ getValue() {
+ return this.searchInput.val();
+ }
- onClearInputClick(e) {
- e.preventDefault();
- this.wrap.toggleClass('has-value', !!e.target.value);
- return this.searchInput.val('').focus();
- }
+ onClearInputClick(e) {
+ e.preventDefault();
+ this.wrap.toggleClass('has-value', !!e.target.value);
+ return this.searchInput.val('').focus();
+ }
- onSearchInputBlur(e) {
- this.isFocused = false;
- this.wrap.removeClass('search-active');
- // If input is blank then restore state
- if (this.searchInput.val() === '') {
- return this.restoreOriginalState();
- }
+ onSearchInputBlur(e) {
+ this.isFocused = false;
+ this.wrap.removeClass('search-active');
+ // If input is blank then restore state
+ if (this.searchInput.val() === '') {
+ return this.restoreOriginalState();
}
+ }
- addLocationBadge(item) {
- var badgeText, category, value;
- category = item.category != null ? item.category + ": " : '';
- value = item.value != null ? item.value : '';
- badgeText = "" + category + value;
- this.locationBadgeEl.text(badgeText).show();
- return this.wrap.addClass('has-location-badge');
- }
+ addLocationBadge(item) {
+ var badgeText, category, value;
+ category = item.category != null ? item.category + ": " : '';
+ value = item.value != null ? item.value : '';
+ badgeText = "" + category + value;
+ this.locationBadgeEl.text(badgeText).show();
+ return this.wrap.addClass('has-location-badge');
+ }
- hasLocationBadge() {
- return this.wrap.is('.has-location-badge');
- }
+ hasLocationBadge() {
+ return this.wrap.is('.has-location-badge');
+ }
- restoreOriginalState() {
- var i, input, inputs, len;
- inputs = Object.keys(this.originalState);
- for (i = 0, len = inputs.length; i < len; i += 1) {
- input = inputs[i];
- this.getElement("#" + input).val(this.originalState[input]);
- }
- if (this.originalState._location === '') {
- return this.locationBadgeEl.hide();
- } else {
- return this.addLocationBadge({
- value: this.originalState._location,
- });
- }
+ restoreOriginalState() {
+ var i, input, inputs, len;
+ inputs = Object.keys(this.originalState);
+ for (i = 0, len = inputs.length; i < len; i += 1) {
+ input = inputs[i];
+ this.getElement("#" + input).val(this.originalState[input]);
}
-
- badgePresent() {
- return this.locationBadgeEl.length;
+ if (this.originalState._location === '') {
+ return this.locationBadgeEl.hide();
+ } else {
+ return this.addLocationBadge({
+ value: this.originalState._location,
+ });
}
+ }
- resetSearchState() {
- var i, input, inputs, len, results;
- inputs = Object.keys(this.originalState);
- results = [];
- for (i = 0, len = inputs.length; i < len; i += 1) {
- input = inputs[i];
- // _location isnt a input
- if (input === '_location') {
- break;
- }
- results.push(this.getElement("#" + input).val(''));
+ badgePresent() {
+ return this.locationBadgeEl.length;
+ }
+
+ resetSearchState() {
+ var i, input, inputs, len, results;
+ inputs = Object.keys(this.originalState);
+ results = [];
+ for (i = 0, len = inputs.length; i < len; i += 1) {
+ input = inputs[i];
+ // _location isnt a input
+ if (input === '_location') {
+ break;
}
- return results;
+ results.push(this.getElement("#" + input).val(''));
}
+ return results;
+ }
- removeLocationBadge() {
- this.locationBadgeEl.hide();
- this.resetSearchState();
- this.wrap.removeClass('has-location-badge');
- return this.disableAutocomplete();
- }
+ removeLocationBadge() {
+ this.locationBadgeEl.hide();
+ this.resetSearchState();
+ this.wrap.removeClass('has-location-badge');
+ return this.disableAutocomplete();
+ }
- disableAutocomplete() {
- if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
- this.searchInput.addClass('disabled');
- this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
- this.restoreMenu();
- }
+ disableAutocomplete() {
+ if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
+ this.searchInput.addClass('disabled');
+ this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
+ this.restoreMenu();
}
+ }
- restoreMenu() {
- var html;
- html = '<ul><li class="dropdown-menu-empty-item"><a>Loading...</a></li></ul>';
- return this.dropdownContent.html(html);
- }
+ restoreMenu() {
+ var html;
+ html = '<ul><li class="dropdown-menu-empty-item"><a>Loading...</a></li></ul>';
+ return this.dropdownContent.html(html);
+ }
- onClick(item, $el, e) {
- if (location.pathname.indexOf(item.url) !== -1) {
- if (!e.metaKey) e.preventDefault();
- if (!this.badgePresent) {
- if (item.category === 'Projects') {
- this.projectInputEl.val(item.id);
- this.addLocationBadge({
- value: 'This project',
- });
- }
- if (item.category === 'Groups') {
- this.groupInputEl.val(item.id);
- this.addLocationBadge({
- value: 'This group',
- });
- }
+ onClick(item, $el, e) {
+ if (location.pathname.indexOf(item.url) !== -1) {
+ if (!e.metaKey) e.preventDefault();
+ if (!this.badgePresent) {
+ if (item.category === 'Projects') {
+ this.projectInputEl.val(item.id);
+ this.addLocationBadge({
+ value: 'This project',
+ });
+ }
+ if (item.category === 'Groups') {
+ this.groupInputEl.val(item.id);
+ this.addLocationBadge({
+ value: 'This group',
+ });
}
- $el.removeClass('is-active');
- this.disableAutocomplete();
- return this.searchInput.val('').focus();
}
+ $el.removeClass('is-active');
+ this.disableAutocomplete();
+ return this.searchInput.val('').focus();
}
}
-
- global.SearchAutocomplete = SearchAutocomplete;
-
- $(function() {
- var $projectOptionsDataEl = $('.js-search-project-options');
- var $groupOptionsDataEl = $('.js-search-group-options');
- var $dashboardOptionsDataEl = $('.js-search-dashboard-options');
-
- if ($projectOptionsDataEl.length) {
- gl.projectOptions = gl.projectOptions || {};
-
- var projectPath = $projectOptionsDataEl.data('project-path');
-
- gl.projectOptions[projectPath] = {
- name: $projectOptionsDataEl.data('name'),
- issuesPath: $projectOptionsDataEl.data('issues-path'),
- issuesDisabled: $projectOptionsDataEl.data('issues-disabled'),
- mrPath: $projectOptionsDataEl.data('mr-path'),
- };
- }
-
- if ($groupOptionsDataEl.length) {
- gl.groupOptions = gl.groupOptions || {};
-
- var groupPath = $groupOptionsDataEl.data('group-path');
-
- gl.groupOptions[groupPath] = {
- name: $groupOptionsDataEl.data('name'),
- issuesPath: $groupOptionsDataEl.data('issues-path'),
- mrPath: $groupOptionsDataEl.data('mr-path'),
- };
- }
-
- if ($dashboardOptionsDataEl.length) {
- gl.dashboardOptions = {
- issuesPath: $dashboardOptionsDataEl.data('issues-path'),
- mrPath: $dashboardOptionsDataEl.data('mr-path'),
- };
- }
- });
-})(window.gl || (window.gl = {}));
+}
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 3f811c59cb9..95e51bc4e7a 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -2,6 +2,7 @@
import FilesCommentButton from './files_comment_button';
import imageDiffHelper from './image_diff/helpers/index';
+import syntaxHighlight from './syntax_highlight';
const WRAPPER = '<div class="diff-content"></div>';
const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
@@ -64,7 +65,7 @@ export default class SingleFileDiff {
_this.loadingContent.hide();
if (data.html) {
_this.content = $(data.html);
- _this.content.syntaxHighlight();
+ syntaxHighlight(_this.content);
} else {
_this.hasError = true;
_this.content = $(ERROR_HTML);
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index 662d6b36c16..62bdef76c55 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -10,17 +10,15 @@
// <div class="js-syntax-highlight"></div>
//
-$.fn.syntaxHighlight = function() {
- var $children;
-
- if ($(this).hasClass('js-syntax-highlight')) {
+export default function syntaxHighlight(el) {
+ if ($(el).hasClass('js-syntax-highlight')) {
// Given the element itself, apply highlighting
- return $(this).addClass(gon.user_color_scheme);
+ return $(el).addClass(gon.user_color_scheme);
} else {
// Given a parent element, recurse to any of its applicable children
- $children = $(this).find('.js-syntax-highlight');
+ const $children = $(el).find('.js-syntax-highlight');
if ($children.length) {
- return $children.syntaxHighlight();
+ return syntaxHighlight($children);
}
}
-};
+}
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js
index 5e947769f8a..4fa8c680580 100644
--- a/app/assets/javascripts/users/activity_calendar.js
+++ b/app/assets/javascripts/users/activity_calendar.js
@@ -1,5 +1,6 @@
import _ from 'underscore';
import d3 from 'd3';
+import { getDayName, getDayDifference } from '../lib/utils/datetime_utility';
const LOADING_HTML = `
<div class="text-center">
@@ -17,7 +18,7 @@ function getSystemDate(systemUtcOffsetSeconds) {
function formatTooltipText({ date, count }) {
const dateObject = new Date(date);
- const dateDayName = gl.utils.getDayName(dateObject);
+ const dateDayName = getDayName(dateObject);
const dateText = dateObject.format('mmm d, yyyy');
let contribText = 'No contributions';
@@ -51,7 +52,7 @@ export default class ActivityCalendar {
const oneYearAgo = new Date(today);
oneYearAgo.setFullYear(today.getFullYear() - 1);
- const days = gl.utils.getDayDifference(oneYearAgo, today);
+ const days = getDayDifference(oneYearAgo, today);
for (let i = 0; i <= days; i += 1) {
const date = new Date(oneYearAgo);
diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/users/user_tabs.js
index 1215b265e28..992baa9a1ef 100644
--- a/app/assets/javascripts/users/user_tabs.js
+++ b/app/assets/javascripts/users/user_tabs.js
@@ -1,4 +1,6 @@
+import Activities from '../activities';
import ActivityCalendar from './activity_calendar';
+import { localTimeAgo } from '../lib/utils/datetime_utility';
/**
* UserTabs
@@ -138,7 +140,7 @@ export default class UserTabs {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true;
- gl.utils.localTimeAgo($('.js-timeago', tabSelector));
+ localTimeAgo($('.js-timeago', tabSelector));
},
});
}
@@ -169,7 +171,7 @@ export default class UserTabs {
});
// eslint-disable-next-line no-new
- new gl.Activities();
+ new Activities();
this.loaded.activity = true;
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
index 32028a4a609..ee1a45cc754 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
@@ -1,4 +1,4 @@
-import '~/lib/utils/datetime_utility';
+import { getTimeago } from '~/lib/utils/datetime_utility';
import { visitUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import MemoryUsage from './mr_widget_memory_usage';
@@ -17,7 +17,7 @@ export default {
},
methods: {
formatDate(date) {
- return gl.utils.getTimeago().format(date);
+ return getTimeago().format(date);
},
hasExternalUrls(deployment = {}) {
return deployment.external_url && deployment.external_url_formatted;
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index c1f7e64f580..707766e08e4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -1,5 +1,6 @@
import Timeago from 'timeago.js';
import { getStateKey } from '../dependencies';
+import { formatDate } from '../../lib/utils/datetime_utility';
export default class MergeRequestStore {
@@ -122,7 +123,7 @@ export default class MergeRequestStore {
static getEventObject(event) {
return {
author: MergeRequestStore.getAuthorObject(event),
- updatedAt: gl.utils.formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
+ updatedAt: formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
formattedUpdatedAt: MergeRequestStore.getEventDate(event),
};
}
diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.js b/app/assets/javascripts/vue_shared/components/memory_graph.js
index 643b77e04c7..f37ef1a5ca3 100644
--- a/app/assets/javascripts/vue_shared/components/memory_graph.js
+++ b/app/assets/javascripts/vue_shared/components/memory_graph.js
@@ -1,3 +1,5 @@
+import { getTimeago } from '../../lib/utils/datetime_utility';
+
export default {
name: 'MemoryGraph',
props: {
@@ -16,7 +18,7 @@ export default {
},
computed: {
getFormattedMedian() {
- const deployedSince = gl.utils.getTimeago().format(this.deploymentTime * 1000);
+ const deployedSince = getTimeago().format(this.deploymentTime * 1000);
return `Deployed ${deployedSince}`;
},
},
diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/modal.vue
index 6d15bbd84ba..55f466b7b41 100644
--- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue
+++ b/app/assets/javascripts/vue_shared/components/modal.vue
@@ -1,6 +1,6 @@
<script>
export default {
- name: 'popup-dialog',
+ name: 'modal',
props: {
title: {
@@ -75,7 +75,7 @@ export default {
<template>
<div class="modal-open">
<div
- class="modal popup-dialog"
+ class="modal show"
role="dialog"
tabindex="-1"
>
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
index 3ec50f14eb4..8053c65d498 100644
--- a/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -1,8 +1,8 @@
<script>
-import PopupDialog from './popup_dialog.vue';
+import modal from './modal.vue';
export default {
- name: 'recaptcha-dialog',
+ name: 'recaptcha-modal',
props: {
html: {
@@ -20,7 +20,7 @@ export default {
},
components: {
- PopupDialog,
+ modal,
},
methods: {
@@ -65,9 +65,9 @@ export default {
</script>
<template>
-<popup-dialog
+<modal
kind="warning"
- class="recaptcha-dialog js-recaptcha-dialog"
+ class="recaptcha-modal js-recaptcha-modal"
:hide-footer="true"
:title="__('Please solve the reCAPTCHA')"
@toggle="close"
@@ -81,5 +81,5 @@ export default {
v-html="html"
></div>
</div>
-</popup-dialog>
+</modal>
</template>
diff --git a/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js b/app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js
index ef70f9432e3..ff1f565e79a 100644
--- a/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js
+++ b/app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js
@@ -1,4 +1,4 @@
-import RecaptchaDialog from '../components/recaptcha_dialog.vue';
+import recaptchaModal from '../components/recaptcha_modal.vue';
export default {
data() {
@@ -9,7 +9,7 @@ export default {
},
components: {
- RecaptchaDialog,
+ recaptchaModal,
},
methods: {
diff --git a/app/assets/javascripts/vue_shared/mixins/timeago.js b/app/assets/javascripts/vue_shared/mixins/timeago.js
index 20f63ab663c..4e3b9d7b767 100644
--- a/app/assets/javascripts/vue_shared/mixins/timeago.js
+++ b/app/assets/javascripts/vue_shared/mixins/timeago.js
@@ -1,4 +1,4 @@
-import '../../lib/utils/datetime_utility';
+import { formatDate, getTimeago } from '../../lib/utils/datetime_utility';
/**
* Mixin with time ago methods used in some vue components
@@ -6,13 +6,13 @@ import '../../lib/utils/datetime_utility';
export default {
methods: {
timeFormated(time) {
- const timeago = gl.utils.getTimeago();
+ const timeago = getTimeago();
return timeago.format(time);
},
tooltipTitle(time) {
- return gl.utils.formatDate(time);
+ return formatDate(time);
},
},
};
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 8d83554d813..478269f3fcf 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -143,20 +143,48 @@
}
}
+@mixin dropdown-item-hover {
+ background-color: $dropdown-item-hover-bg;
+ color: $gl-text-color;
+ outline: 0;
+
+ // make sure the text color is not overriden
+ &.text-danger {
+ color: $brand-danger;
+ }
+
+ .avatar {
+ border-color: $white-light;
+ }
+}
+
@mixin dropdown-link {
+ background: transparent;
+ border: 0;
+ border-radius: 0;
+ box-shadow: none;
display: block;
+ font-weight: $gl-font-weight-normal;
position: relative;
- padding: 5px 8px;
+ padding: 8px 16px;
color: $gl-text-color;
- line-height: initial;
- border-radius: 2px;
- white-space: nowrap;
+ line-height: normal;
+ white-space: normal;
overflow: hidden;
+ text-align: left;
+ width: 100%;
+
+ // make sure the text color is not overriden
+ &.text-danger {
+ color: $brand-danger;
+ }
&:hover,
+ &:active,
&:focus,
&.is-focused {
- background-color: $dropdown-link-hover-bg;
+ @include dropdown-item-hover;
+
text-decoration: none;
.badge {
@@ -166,6 +194,13 @@
&.dropdown-menu-user-link {
line-height: 16px;
+ padding-top: 10px;
+ padding-bottom: 7px;
+ white-space: nowrap;
+
+ .dropdown-menu-user-username {
+ display: block;
+ }
}
.icon-play {
@@ -187,8 +222,8 @@
z-index: 300;
min-width: 240px;
max-width: 500px;
- margin-top: 2px;
- margin-bottom: 2px;
+ margin-top: $dropdown-vertical-offset;
+ margin-bottom: 24px;
font-size: 14px;
font-weight: $gl-font-weight-normal;
padding: 8px 0;
@@ -197,6 +232,10 @@
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
+ &.dropdown-open-top {
+ margin-bottom: $dropdown-vertical-offset;
+ }
+
&.dropdown-open-left {
right: 0;
left: auto;
@@ -227,16 +266,27 @@
}
li {
+ display: block;
text-align: left;
list-style: none;
- padding: 0 10px;
+ padding: 0 1px;
+
+ a,
+ button,
+ .menu-item {
+ @include dropdown-link;
+ }
}
.divider {
height: 1px;
- margin: 6px 10px;
+ margin: 6px 0;
padding: 0;
background-color: $dropdown-divider-color;
+
+ &:hover {
+ background-color: $dropdown-divider-color;
+ }
}
.separator {
@@ -247,10 +297,6 @@
background-color: $dropdown-divider-color;
}
- a {
- @include dropdown-link;
- }
-
.dropdown-menu-empty-item a {
&:hover,
&:focus {
@@ -262,7 +308,7 @@
color: $gl-text-color-secondary;
font-size: 13px;
line-height: 22px;
- padding: 0 16px;
+ padding: 8px 16px;
}
&.capitalize-header .dropdown-header {
@@ -277,7 +323,7 @@
.separator + .dropdown-header,
.separator + .dropdown-bold-header {
- padding-top: 2px;
+ padding-top: 10px;
}
.unclickable {
@@ -298,48 +344,28 @@
}
.dropdown-menu li {
- padding: $gl-btn-padding;
cursor: pointer;
+ &.droplab-item-active button {
+ @include dropdown-item-hover;
+ }
+
> a,
> button {
display: flex;
margin: 0;
- padding: 0;
- border-radius: 0;
text-overflow: inherit;
- background-color: inherit;
- color: inherit;
- border: inherit;
text-align: left;
- &:hover,
- &:focus {
- background-color: inherit;
- color: inherit;
- }
-
&.btn .fa:not(:last-child) {
margin-left: 5px;
}
}
- &:hover,
- &:focus {
- background-color: $dropdown-hover-color;
- color: $white-light;
- }
-
&.droplab-item-selected i {
visibility: visible;
}
- &.divider {
- margin: 0 8px;
- padding: 0;
- border-top: $gray-darkest;
- }
-
.icon {
visibility: hidden;
}
@@ -431,11 +457,6 @@
}
}
-.dropdown-menu-user-link {
- padding-top: 10px;
- padding-bottom: 7px;
-}
-
.dropdown-menu-user-full-name {
display: block;
font-weight: $gl-font-weight-normal;
@@ -464,41 +485,44 @@
.dropdown-menu-align-right {
left: auto;
right: 0;
- margin-top: -5px;
}
.dropdown-menu-selectable {
- a {
- padding-left: 26px;
- position: relative;
+ li {
+ a {
+ padding: 8px 40px;
+ position: relative;
+
+ &.is-indeterminate,
+ &.is-active {
+ color: $gl-text-color;
+
+ &::before {
+ position: absolute;
+ left: 16px;
+ top: 16px;
+ transform: translateY(-50%);
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
- &.is-indeterminate,
- &.is-active {
- font-weight: $gl-font-weight-bold;
- color: $gl-text-color;
-
- &::before {
- position: absolute;
- left: 6px;
- top: 50%;
- transform: translateY(-50%);
- font: normal normal normal 14px/1 FontAwesome;
- font-size: inherit;
- text-rendering: auto;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ &.dropdown-menu-user-link {
+ &::before {
+ top: 50%;
+ }
+ }
}
- }
- &.is-indeterminate::before {
- content: "\f068";
- }
+ &.is-indeterminate::before {
+ content: "\f068";
+ }
- &.is-active::before {
- content: "\f00c";
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
+ &.is-active::before {
+ content: "\f00c";
+ }
}
}
}
@@ -735,136 +759,6 @@
}
}
-@mixin dropdown-item-hover {
- background-color: $dropdown-item-hover-bg;
- color: $gl-text-color;
-}
-
-// TODO: change global style and remove mixin
-@mixin new-style-dropdown($selector: '') {
- #{$selector}.dropdown-menu,
- #{$selector}.dropdown-menu-nav {
- margin-bottom: 24px;
-
- &.dropdown-open-top {
- margin-bottom: $dropdown-vertical-offset;
- }
-
- li {
- display: block;
- padding: 0 1px;
-
- &:hover {
- background-color: transparent;
- }
-
- &.divider {
- margin: 6px 0;
-
- &:hover {
- background-color: $dropdown-divider-color;
- }
- }
-
- &.dropdown-header {
- padding: 8px 16px;
- }
-
- &.droplab-item-active button {
- @include dropdown-item-hover;
- }
-
- a,
- button,
- .menu-item {
- margin-bottom: 0;
- border-radius: 0;
- box-shadow: none;
- padding: 8px 16px;
- text-align: left;
- white-space: normal;
- width: 100%;
- font-weight: $gl-font-weight-normal;
- line-height: normal;
-
- &.dropdown-menu-user-link {
- white-space: nowrap;
-
- .dropdown-menu-user-username {
- display: block;
- }
- }
-
- // make sure the text color is not overriden
- &.text-danger {
- color: $brand-danger;
- }
-
- &.is-focused,
- &:hover,
- &:active,
- &:focus {
- @include dropdown-item-hover;
-
- background-color: $dropdown-item-hover-bg;
- color: $gl-text-color;
-
- // make sure the text color is not overriden
- &.text-danger {
- color: $brand-danger;
- }
- }
-
- &.is-active {
- font-weight: inherit;
-
- &::before {
- top: 16px;
- }
-
- &.dropdown-menu-user-link::before {
- top: 50%;
- transform: translateY(-50%);
- }
- }
- }
-
- &.dropdown-menu-empty-item a {
- &:hover,
- &:focus {
- background-color: transparent;
- }
- }
- }
-
- &.dropdown-menu-selectable {
- li {
- a {
- padding: 8px 40px;
-
- &.is-indeterminate::before,
- &.is-active::before {
- left: 16px;
- }
- }
- }
- }
- }
-
- #{$selector}.dropdown-menu-align-right {
- margin-top: 2px;
- }
-
- .open {
- #{$selector}.dropdown-menu,
- #{$selector}.dropdown-menu-nav {
- @media (max-width: $screen-xs-max) {
- max-width: 100%;
- }
- }
- }
-}
-
@media (max-width: $screen-xs-max) {
.navbar-gitlab {
li.header-projects,
@@ -891,9 +785,6 @@
}
}
-@include new-style-dropdown('.breadcrumbs-list .dropdown ');
-@include new-style-dropdown('.js-namespace-select + ');
-
header.header-content .dropdown-menu.projects-dropdown-menu {
padding: 0;
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index cec38eea464..2d7465401f1 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -50,8 +50,6 @@
}
.filtered-search-wrapper {
- @include new-style-dropdown;
-
display: -webkit-flex;
display: flex;
@@ -165,16 +163,6 @@
}
}
-.droplab-dropdown li.filtered-search-token {
- padding: 0;
-
- &:hover,
- &:focus {
- background-color: inherit;
- color: inherit;
- }
-}
-
.filtered-search-term {
.name {
background-color: inherit;
@@ -336,21 +324,12 @@
.filtered-search-history-dropdown-content {
max-height: none;
-}
-
-.filtered-search-history-dropdown-item,
-.filtered-search-history-clear-button {
- @include dropdown-link;
-
- overflow: hidden;
- width: 100%;
- margin: 0.5em 0;
- background-color: transparent;
- border: 0;
- text-align: left;
- white-space: nowrap;
- text-overflow: ellipsis;
+ .filtered-search-history-dropdown-item,
+ .filtered-search-history-clear-button {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
}
.filtered-search-history-dropdown-token {
@@ -402,24 +381,9 @@
}
}
-%filter-dropdown-item-btn-hover {
- text-decoration: none;
- outline: 0;
-
- .avatar {
- border-color: $white-light;
- }
-}
-
.droplab-dropdown .dropdown-menu .filter-dropdown-item {
.btn {
- border: 0;
- width: 100%;
- text-align: left;
- padding: 8px 16px;
text-overflow: ellipsis;
- overflow: hidden;
- border-radius: 0;
.fa {
width: 15px;
@@ -434,11 +398,6 @@
height: 17px;
top: 0;
}
-
- &:hover,
- &:focus {
- @extend %filter-dropdown-item-btn-hover;
- }
}
.dropdown-light-content {
@@ -459,17 +418,9 @@
word-break: break-all;
}
}
-
- &.droplab-item-active .btn {
- @extend %filter-dropdown-item-btn-hover;
- }
}
.filter-dropdown-loading {
padding: 8px 16px;
text-align: center;
}
-
-.issues-details-filters {
- @include new-style-dropdown;
-}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index f985a3aea5c..29714e348a0 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -1,10 +1,4 @@
-.content-wrapper.page-with-new-nav {
- margin-top: $header-height;
-}
-
.navbar-gitlab {
- @include new-style-dropdown;
-
&.navbar-gitlab {
padding: 0 16px;
z-index: 1000;
diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss
index 78a8e57ddbb..aa2d30a3cef 100644
--- a/app/assets/stylesheets/framework/images.scss
+++ b/app/assets/stylesheets/framework/images.scss
@@ -19,6 +19,13 @@
max-width: 425px;
width: 100%;
}
+
+ &.svg-250 {
+ img,
+ svg {
+ width: 250px;
+ }
+ }
}
@mixin svg-size($size) {
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 1537b0744cc..1d8bd26cf1a 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -24,10 +24,14 @@
font-size: $gl-font-size;
line-height: 25px;
- &.status-box-closed {
+ &.status-box-mr-closed {
background-color: $gl-danger;
}
+ &.status-box-issue-closed {
+ background-color: $gl-primary;
+ }
+
&.status-box-merged {
background-color: $gl-primary;
}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index cb324ccc440..3f0268541a4 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -24,6 +24,7 @@ body {
}
.content-wrapper {
+ margin-top: $header-height;
padding-bottom: 100px;
}
@@ -105,11 +106,11 @@ body {
}
}
-.page-with-sidebar > .content-wrapper {
+.layout-page > .content-wrapper {
min-height: calc(100vh - #{$header-height});
}
-.with-performance-bar .page-with-sidebar {
+.with-performance-bar .layout-page {
margin-top: $header-height + $performance-bar-height;
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index e6e6c4c3963..f79a71221c4 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -132,8 +132,6 @@ ul.content-list {
}
.controls {
- @include new-style-dropdown;
-
float: right;
> .control-text {
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 600a1f53b58..a12f28efce6 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -111,21 +111,4 @@
aside:not(.right-sidebar) {
display: none;
}
-
- .show-aside {
- display: block !important;
- }
-}
-
-.show-aside {
- display: none;
- position: fixed;
- right: 0;
- top: 30%;
- padding: 5px 15px;
- background: $show-aside-bg;
- font-size: 20px;
- color: $show-aside-color;
- z-index: 100;
- box-shadow: 0 1px 2px $show-aside-shadow;
}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index ce551e6b7ce..1be66d0ab21 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -44,11 +44,18 @@ body.modal-open {
}
}
-.modal.popup-dialog {
- display: block;
+.modal {
+ background-color: $black-transparent;
+ z-index: 2100;
+
+ @media (min-width: $screen-md-min) {
+ .modal-dialog {
+ margin: 30px auto;
+ }
+ }
}
-.recaptcha-dialog .recaptcha-form {
+.recaptcha-modal .recaptcha-form {
display: inline-block;
.recaptcha {
diff --git a/app/assets/stylesheets/framework/secondary-navigation-elements.scss b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
index 8498b37abe4..5f67126bafa 100644
--- a/app/assets/stylesheets/framework/secondary-navigation-elements.scss
+++ b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
@@ -86,8 +86,6 @@
}
.nav-controls {
- @include new-style-dropdown;
-
display: inline-block;
float: right;
text-align: right;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 3ebba4f9efb..0742c0a2a09 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -144,10 +144,6 @@
}
}
-.issuable-sidebar {
- @include new-style-dropdown;
-}
-
.pikaday-container {
.pika-single {
margin-top: 2px;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 0817cce114c..11c1aeea871 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -343,8 +343,6 @@ a > code {
@extend .ref-name;
}
-@include new-style-dropdown('.git-revision-dropdown');
-
/**
* Apply Markdown typography
*
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 5de5403916f..b84d6c140be 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -245,9 +245,6 @@ $btn-sm-side-margin: 7px;
$btn-xs-side-margin: 5px;
$issue-status-expired: $orange-500;
$issuable-sidebar-color: $gl-text-color-secondary;
-$show-aside-bg: #eee;
-$show-aside-color: #777;
-$show-aside-shadow: #ddd;
$group-path-color: #999;
$namespace-kind-color: #aaa;
$panel-heading-link-color: #777;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 862ea379cbc..2803144ef1d 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -415,7 +415,7 @@
margin: 5px;
}
-.page-with-contextual-sidebar.page-with-sidebar .issue-boards-sidebar {
+.page-with-contextual-sidebar.layout-page .issue-boards-sidebar {
.issuable-sidebar-header {
position: relative;
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index f139f4ab650..98d460339cd 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -323,8 +323,6 @@
}
.build-dropdown {
- @include new-style-dropdown;
-
margin: $gl-padding 0;
padding: 0;
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index c303f016ff9..88d44131d5b 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -13,8 +13,6 @@
max-width: 100%;
}
-@include new-style-dropdown('.clusters-dropdown ');
-
.clusters-container {
.nav-bar-right {
padding: $gl-padding-top $gl-padding;
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 292e0ad394b..3b35beb7695 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -1,6 +1,4 @@
#cycle-analytics {
- @include new-style-dropdown;
-
max-width: 1000px;
margin: 24px auto 0;
position: relative;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 52e4d904b9b..2f2c04206e2 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -32,8 +32,6 @@
}
.detail-page-header-actions {
- @include new-style-dropdown;
-
align-self: center;
flex-shrink: 0;
flex: 0 0 auto;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 71a6c7a2bf9..60b07537799 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -581,8 +581,6 @@
}
.commit-stat-summary {
- @include new-style-dropdown;
-
@media (min-width: $screen-sm-min) {
margin-left: -$gl-padding;
padding-left: $gl-padding;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index c586dab4cf2..8ecda50602d 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -204,8 +204,6 @@
.gitlab-ci-yml-selector,
.dockerfile-selector,
.template-type-selector {
- @include new-style-dropdown;
-
display: inline-block;
vertical-align: top;
font-family: $regular_font;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index a5a6b7461a3..f4882305c57 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -12,8 +12,6 @@
.environments-container {
.ci-table {
- @include new-style-dropdown;
-
.deployment-column {
> span {
word-break: break-all;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 2e26f2b5bbd..e19196e0c41 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -488,12 +488,6 @@
}
}
- .dropdown-content {
- a:hover {
- color: inherit;
- }
- }
-
.dropdown-menu-toggle {
width: 100%;
padding-top: 6px;
@@ -512,10 +506,6 @@
}
}
-.sidebar-move-issue-dropdown {
- @include new-style-dropdown;
-}
-
.sidebar-move-issue-confirmation-button {
width: 100%;
@@ -620,11 +610,19 @@
}
.issuable-status-box {
- float: none;
- display: inline-block;
+ align-self: stretch;
+ display: flex;
+ justify-content: center;
+ align-items: center;
margin-top: 0;
- height: auto;
- align-self: center;
+ padding-left: 9px;
+ padding-right: 9px;
+
+ @media (min-width: $screen-sm-min) {
+ display: inline-block;
+ height: auto;
+ align-self: center;
+ }
}
.issuable-gutter-toggle {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index af1df8b8802..c48e58af691 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -142,8 +142,6 @@ ul.related-merge-requests > li {
}
.issue-form {
- @include new-style-dropdown;
-
.select2-container {
width: 250px !important;
}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 92abe82df4c..e8cd8a4905c 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -116,8 +116,6 @@
}
.manage-labels-list {
- @include new-style-dropdown;
-
> li:not(.empty-message):not(.is-not-draggable) {
background-color: $white-light;
cursor: move;
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 18c48405ecd..3422829de58 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -58,8 +58,6 @@
}
.member-form-control {
- @include new-style-dropdown;
-
@media (max-width: $screen-xs-max) {
padding-bottom: 5px;
margin-left: 0;
@@ -73,8 +71,6 @@
}
.member-search-form {
- @include new-style-dropdown;
-
position: relative;
@media (min-width: $screen-sm-min) {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 2afb17334e3..e75a35d78ad 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -485,8 +485,6 @@
}
.mr-source-target {
- @include new-style-dropdown;
-
display: flex;
flex-wrap: wrap;
justify-content: space-between;
@@ -608,8 +606,6 @@
}
.mr-version-controls {
- @include new-style-dropdown;
-
position: relative;
background: $gray-light;
color: $gl-text-color;
@@ -727,7 +723,3 @@
font-size: 16px;
}
}
-
-.merge-request-form {
- @include new-style-dropdown;
-}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index ebb5d121433..6d4ccd53e12 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -23,8 +23,6 @@
.new-note,
.note-edit-form {
.note-form-actions {
- @include new-style-dropdown;
-
position: relative;
margin: $gl-padding 0 0;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 4d5613c292b..26e6e8688b6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -490,8 +490,6 @@ ul.notes {
}
.note-actions {
- @include new-style-dropdown;
-
align-self: flex-start;
flex-shrink: 0;
display: inline-flex;
diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss
index c28b1e68008..bdf07a99daf 100644
--- a/app/assets/stylesheets/pages/notifications.scss
+++ b/app/assets/stylesheets/pages/notifications.scss
@@ -14,7 +14,3 @@
font-size: 18px;
}
}
-
-.notification-form {
- @include new-style-dropdown;
-}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index cb24274c612..9805fc4f882 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -286,8 +286,6 @@
// Pipeline visualization
.pipeline-actions {
- @include new-style-dropdown;
-
border-bottom: 0;
}
@@ -703,9 +701,6 @@ button.mini-pipeline-graph-dropdown-toggle {
}
}
-@include new-style-dropdown('.big-pipeline-graph-dropdown-menu');
-@include new-style-dropdown('.mini-pipeline-graph-dropdown-menu');
-
// dropdown content for big and mini pipeline
.big-pipeline-graph-dropdown-menu,
.mini-pipeline-graph-dropdown-menu {
@@ -804,7 +799,6 @@ button.mini-pipeline-graph-dropdown-toggle {
font-weight: normal;
line-height: $line-height-base;
white-space: nowrap;
- border-radius: 3px;
.ci-job-name-component {
align-items: center;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 674588752d2..6f4c678c4b8 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -323,8 +323,6 @@
}
.project-repo-buttons {
- @include new-style-dropdown;
-
.project-action-button .dropdown-menu {
max-height: 250px;
overflow-y: auto;
@@ -898,8 +896,6 @@ pre.light-well {
.new-protected-branch,
.new-protected-tag {
- @include new-style-dropdown;
-
label {
margin-top: 6px;
font-weight: $gl-font-weight-normal;
@@ -919,8 +915,6 @@ pre.light-well {
.protected-branches-list,
.protected-tags-list {
- @include new-style-dropdown;
-
margin-bottom: 30px;
.settings-message {
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 402412eae71..6eb92c7baee 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -1,16 +1,3 @@
-.modal.popup-dialog {
- display: block;
- background-color: $black-transparent;
- z-index: 2100;
-
- @media (min-width: $screen-md-min) {
- .modal-dialog {
- width: 600px;
- margin: 30px auto;
- }
- }
-}
-
.project-refs-form,
.project-refs-target-form {
display: inline-block;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index fe455a04960..49c8e546bf2 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -116,11 +116,6 @@ input[type="checkbox"]:hover {
opacity: 0;
display: block;
left: -5px;
- padding: 0;
-
- ul {
- padding: 10px 0;
- }
}
.dropdown-content {
@@ -185,8 +180,6 @@ input[type="checkbox"]:hover {
}
.search-holder {
- @include new-style-dropdown;
-
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
display: flex;
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 2139a029fc7..a79772ea37b 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -265,7 +265,3 @@
font-weight: $gl-font-weight-bold;
}
}
-
-.todos-filters {
- @include new-style-dropdown;
-}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 5d14323e4bc..e0ee7e9aa3d 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,6 +1,4 @@
.tree-holder {
- @include new-style-dropdown;
-
.nav-block {
margin: 10px 0;
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 2ce26de1768..a94726887d9 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -1,4 +1,6 @@
class Admin::GroupsController < Admin::ApplicationController
+ include MembersPresentation
+
before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
def index
@@ -10,8 +12,10 @@ class Admin::GroupsController < Admin::ApplicationController
def show
@group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id])
- @members = @group.members.order("access_level DESC").page(params[:members_page])
- @requesters = AccessRequestsFinder.new(@group).execute(current_user)
+ @members = present_members(
+ @group.members.order("access_level DESC").page(params[:members_page]))
+ @requesters = present_members(
+ AccessRequestsFinder.new(@group).execute(current_user))
@projects = @group.projects.with_statistics.page(params[:projects_page])
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 50cf2643390..3afe66c3566 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -1,4 +1,6 @@
class Admin::ProjectsController < Admin::ApplicationController
+ include MembersPresentation
+
before_action :project, only: [:show, :transfer, :repository_check]
before_action :group, only: [:show, :transfer]
@@ -19,11 +21,14 @@ class Admin::ProjectsController < Admin::ApplicationController
def show
if @group
- @group_members = @group.members.order("access_level DESC").page(params[:group_members_page])
+ @group_members = present_members(
+ @group.members.order("access_level DESC").page(params[:group_members_page]))
end
- @project_members = @project.members.page(params[:project_members_page])
- @requesters = AccessRequestsFinder.new(@project).execute(current_user)
+ @project_members = present_members(
+ @project.members.page(params[:project_members_page]))
+ @requesters = present_members(
+ AccessRequestsFinder.new(@project).execute(current_user))
end
def transfer
diff --git a/app/controllers/concerns/members_presentation.rb b/app/controllers/concerns/members_presentation.rb
new file mode 100644
index 00000000000..c0622516fd3
--- /dev/null
+++ b/app/controllers/concerns/members_presentation.rb
@@ -0,0 +1,11 @@
+module MembersPresentation
+ extend ActiveSupport::Concern
+
+ def present_members(members)
+ Gitlab::View::Presenter::Factory.new(
+ members,
+ current_user: current_user,
+ presenter_class: MembersPresenter
+ ).fabricate!
+ end
+end
diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb
index ed253042701..230bbe4b1aa 100644
--- a/app/controllers/concerns/with_performance_bar.rb
+++ b/app/controllers/concerns/with_performance_bar.rb
@@ -6,6 +6,7 @@ module WithPerformanceBar
end
def peek_enabled?
+ return true if Rails.env.development?
return false unless Gitlab::PerformanceBar.enabled?(current_user)
if RequestStore.active?
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 5919bf54468..21e77431176 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -1,5 +1,6 @@
class Groups::GroupMembersController < Groups::ApplicationController
include MembershipActions
+ include MembersPresentation
include SortingHelper
# Authorize
@@ -14,15 +15,17 @@ class Groups::GroupMembersController < Groups::ApplicationController
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort)
@members = @members.page(params[:page]).per(50)
- @members.includes(:user)
+ @members = present_members(@members.includes(:user))
- @requesters = AccessRequestsFinder.new(@group).execute(current_user)
+ @requesters = present_members(
+ AccessRequestsFinder.new(@group).execute(current_user))
@group_member = @group.group_members.new
end
def update
@group_member = @group.members_and_requesters.find(params[:id])
+ .present(current_user: current_user)
return render_403 unless can?(current_user, :update_group_member, @group_member)
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 5a01a59481b..d7372beb9d3 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -1,5 +1,6 @@
class Projects::ProjectMembersController < Projects::ApplicationController
include MembershipActions
+ include MembersPresentation
include SortingHelper
# Authorize
@@ -20,13 +21,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end
- @project_members = @project_members.sort(@sort).page(params[:page])
- @requesters = AccessRequestsFinder.new(@project).execute(current_user)
+ @project_members = present_members(@project_members.sort(@sort).page(params[:page]))
+ @requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
@project_member = @project.project_members.new
end
def update
@project_member = @project.members_and_requesters.find(params[:id])
+ .present(current_user: current_user)
return render_403 unless can?(current_user, :update_project_member, @project_member)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 212cdbb8157..0f110bd25c5 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -74,7 +74,7 @@ module IssuesHelper
elsif item.try(:merged?)
'status-box-merged'
elsif item.closed?
- 'status-box-closed'
+ 'status-box-mr-closed'
elsif item.try(:upcoming?)
'status-box-upcoming'
else
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index e1ba7898ee6..c1c19062c91 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -1,6 +1,13 @@
module LabelsHelper
include ActionView::Helpers::TagHelper
+ def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
+ return true if label.is_a?(GroupLabel)
+ return true unless project
+
+ project.feature_available?(issuables_type, current_user)
+ end
+
# Link to a Label
#
# label - Label object to link to
diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb
index 41d471cc92f..a3129cac2b1 100644
--- a/app/helpers/members_helper.rb
+++ b/app/helpers/members_helper.rb
@@ -1,11 +1,4 @@
module MembersHelper
- # Returns a `<action>_<source>_member` association, e.g.:
- # - admin_project_member, update_project_member, destroy_project_member
- # - admin_group_member, update_group_member, destroy_group_member
- def action_member_permission(action, member)
- "#{action}_#{member.type.underscore}".to_sym
- end
-
def remove_member_message(member, user: nil)
user = current_user if defined?(current_user)
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 77a82b895ce..50e17fe7717 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -5,7 +5,7 @@ module Emails
@commit = @note.noteable
@target_url = project_commit_url(*note_target_url_options)
- mail_answer_thread(@commit, note_thread_options(recipient_id))
+ mail_answer_note_thread(@commit, @note, note_thread_options(recipient_id))
end
def note_issue_email(recipient_id, note_id)
@@ -13,7 +13,7 @@ module Emails
@issue = @note.noteable
@target_url = project_issue_url(*note_target_url_options)
- mail_answer_thread(@issue, note_thread_options(recipient_id))
+ mail_answer_note_thread(@issue, @note, note_thread_options(recipient_id))
end
def note_merge_request_email(recipient_id, note_id)
@@ -21,7 +21,7 @@ module Emails
@merge_request = @note.noteable
@target_url = project_merge_request_url(*note_target_url_options)
- mail_answer_thread(@merge_request, note_thread_options(recipient_id))
+ mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id))
end
def note_snippet_email(recipient_id, note_id)
@@ -29,7 +29,7 @@ module Emails
@snippet = @note.noteable
@target_url = project_snippet_url(*note_target_url_options)
- mail_answer_thread(@snippet, note_thread_options(recipient_id))
+ mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id))
end
def note_personal_snippet_email(recipient_id, note_id)
@@ -37,7 +37,7 @@ module Emails
@snippet = @note.noteable
@target_url = snippet_url(@note.noteable)
- mail_answer_thread(@snippet, note_thread_options(recipient_id))
+ mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id))
end
private
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 9efabe3f44e..ec886e993c3 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -119,8 +119,8 @@ class Notify < BaseMailer
headers['Reply-To'] = address
fallback_reply_message_id = "<reply-#{reply_key}@#{Gitlab.config.gitlab.host}>".freeze
- headers['References'] ||= ''
- headers['References'] << ' ' << fallback_reply_message_id
+ headers['References'] ||= []
+ headers['References'] << fallback_reply_message_id
@reply_by_email = true
end
@@ -156,6 +156,18 @@ class Notify < BaseMailer
mail_thread(model, headers)
end
+ def mail_answer_note_thread(model, note, headers = {})
+ headers['Message-ID'] = message_id(note)
+ headers['In-Reply-To'] = message_id(note.references.last)
+ headers['References'] = note.references.map { |ref| message_id(ref) }
+
+ headers['X-GitLab-Discussion-ID'] = note.discussion.id if note.part_of_discussion?
+
+ headers[:subject]&.prepend('Re: ')
+
+ mail_thread(model, headers)
+ end
+
def reply_key
@reply_key ||= SentNotification.reply_key
end
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 29e762724e3..19ad110db58 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -77,9 +77,15 @@ class Blob < SimpleDelegator
end
def self.lazy(project, commit_id, path)
- BatchLoader.for(commit_id: commit_id, path: path).batch do |items, loader|
- project.repository.blobs_at(items.map(&:values)).each do |blob|
- loader.call({ commit_id: blob.commit_id, path: blob.path }, blob) if blob
+ BatchLoader.for({ project: project, commit_id: commit_id, path: path }).batch do |items, loader|
+ items_by_project = items.group_by { |i| i[:project] }
+
+ items_by_project.each do |project, items|
+ items = items.map { |i| i.values_at(:commit_id, :path) }
+
+ project.repository.blobs_at(items).each do |blob|
+ loader.call({ project: blob.project, commit_id: blob.commit_id, path: blob.path }, blob) if blob
+ end
end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 85960f1b6bb..83fe23606d1 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -491,7 +491,6 @@ module Ci
end
def valid_dependency?
- return false unless complete?
return false if artifacts_expired?
return false if erased?
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 307e4fcedfe..13c31111134 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -52,6 +52,20 @@ class Commit
diffs.reduce(0) { |sum, d| sum + Gitlab::Git::Util.count_lines(d.diff) }
end
+ def order_by(collection:, order_by:, sort:)
+ return collection unless %w[email name commits].include?(order_by)
+ return collection unless %w[asc desc].include?(sort)
+
+ collection.sort do |a, b|
+ operands = [a, b].tap { |o| o.reverse! if sort == 'desc' }
+
+ attr1, attr2 = operands.first.public_send(order_by), operands.second.public_send(order_by) # rubocop:disable PublicSend
+
+ # use case insensitive comparison for string values
+ order_by.in?(%w[email name]) ? attr1.casecmp(attr2) : attr1 <=> attr2
+ end
+ end
+
# Truncate sha to 8 characters
def truncate_sha(sha)
sha[0..MIN_SHA_LENGTH]
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 98776eab424..90ad644ce34 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -85,8 +85,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field)
- cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend
- return false unless cached
+ return false if cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? # rubocop:disable GitlabSecurity/PublicSend
markdown_changed = attribute_changed?(markdown_field) || false
html_changed = attribute_changed?(html_field) || false
diff --git a/app/models/member.rb b/app/models/member.rb
index 2fe5fda985f..c47145667b5 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -4,6 +4,7 @@ class Member < ActiveRecord::Base
include Importable
include Expirable
include Gitlab::Access
+ include Presentable
attr_accessor :raw_invite_token
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 422f138c4ea..c39789b047d 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base
include ManualInverseAssociation
include EachBatch
include ThrottledTouch
+ include Gitlab::Utils::StrongMemoize
ignore_column :locked_at,
:ref_fetched
@@ -52,6 +53,7 @@ class MergeRequest < ActiveRecord::Base
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
after_create :ensure_merge_request_diff, unless: :importing?
+ after_update :clear_memoized_shas
after_update :reload_diff_if_branch_changed
# When this attribute is true some MR validation is ignored
@@ -83,6 +85,14 @@ class MergeRequest < ActiveRecord::Base
transition locked: :opened
end
+ before_transition any => :opened do |merge_request|
+ merge_request.merge_jid = nil
+
+ merge_request.run_after_commit do
+ UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
+ end
+ end
+
state :opened
state :closed
state :merged
@@ -387,13 +397,17 @@ class MergeRequest < ActiveRecord::Base
end
def source_branch_head
- return unless source_project
-
- source_project.repository.commit(source_branch_ref) if source_branch_ref
+ strong_memoize(:source_branch_head) do
+ if source_project && source_branch_ref
+ source_project.repository.commit(source_branch_ref)
+ end
+ end
end
def target_branch_head
- target_project.repository.commit(target_branch_ref)
+ strong_memoize(:target_branch_head) do
+ target_project.repository.commit(target_branch_ref)
+ end
end
def branch_merge_base_commit
@@ -525,6 +539,13 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def clear_memoized_shas
+ @target_branch_sha = @source_branch_sha = nil
+
+ clear_memoization(:source_branch_head)
+ clear_memoization(:target_branch_head)
+ end
+
def reload_diff_if_branch_changed
if (source_branch_changed? || target_branch_changed?) &&
(source_branch_head && target_branch_head)
@@ -866,11 +887,11 @@ class MergeRequest < ActiveRecord::Base
def state_icon_name
if merged?
- "check"
+ "git-merge"
elsif closed?
- "times"
+ "close"
else
- "circle-o"
+ "issue-open-m"
end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index c37aa0a594b..e35de9b97ee 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -104,19 +104,19 @@ class MergeRequestDiff < ActiveRecord::Base
def base_commit
return unless base_commit_sha
- project.commit(base_commit_sha)
+ project.commit_by(oid: base_commit_sha)
end
def start_commit
return unless start_commit_sha
- project.commit(start_commit_sha)
+ project.commit_by(oid: start_commit_sha)
end
def head_commit
return unless head_commit_sha
- project.commit(head_commit_sha)
+ project.commit_by(oid: head_commit_sha)
end
def commit_shas
diff --git a/app/models/note.rb b/app/models/note.rb
index c4c2ab8e67d..184fbd5f5ae 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -360,6 +360,16 @@ class Note < ActiveRecord::Base
end
end
+ def references
+ refs = [noteable]
+
+ if part_of_discussion?
+ refs += discussion.notes.take_while { |n| n.id < id }
+ end
+
+ refs
+ end
+
def expire_etag_cache
return unless noteable&.discussions_rendered_on_frontend?
@@ -401,6 +411,9 @@ class Note < ActiveRecord::Base
end
noteable_object&.touch
+
+ # We return the noteable object so we can re-use it in EE for ElasticSearch.
+ noteable_object
end
def banzai_render_context(field)
diff --git a/app/models/project.rb b/app/models/project.rb
index 6ae15a0a50f..5183a216c53 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -659,7 +659,8 @@ class Project < ActiveRecord::Base
end
def import_started?
- import? && import_status == 'started'
+ # import? does SQL work so only run it if it looks like there's an import running
+ import_status == 'started' && import?
end
def import_scheduled?
@@ -1147,7 +1148,7 @@ class Project < ActiveRecord::Base
def change_head(branch)
if repository.branch_exists?(branch)
repository.before_change_head
- repository.write_ref('HEAD', "refs/heads/#{branch}")
+ repository.write_ref('HEAD', "refs/heads/#{branch}", force: true)
repository.copy_gitattributes(branch)
repository.after_change_head
reload_default_branch
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d1ae3304e4a..0c50d05bd96 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -19,6 +19,7 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository
+ delegate :write_ref, to: :raw_repository
CreateTreeError = Class.new(StandardError)
@@ -237,11 +238,10 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
begin
- write_ref(keep_around_ref_name(sha), sha)
- rescue Rugged::ReferenceError => ex
- Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
- rescue Rugged::OSError => ex
- raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
+ write_ref(keep_around_ref_name(sha), sha, force: true)
+ rescue Gitlab::Git::Repository::GitError => ex
+ # Necessary because https://gitlab.com/gitlab-org/gitlab-ce/issues/20156
+ return true if ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
end
@@ -251,10 +251,6 @@ class Repository
ref_exists?(keep_around_ref_name(sha))
end
- def write_ref(ref_path, sha)
- rugged.references.create(ref_path, sha, force: true)
- end
-
def diverging_commit_counts(branch)
root_ref_hash = raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
@@ -690,7 +686,9 @@ class Repository
def tags_sorted_by(value)
case value
- when 'name'
+ when 'name_asc'
+ VersionSorter.sort(tags) { |tag| tag.name }
+ when 'name_desc'
VersionSorter.rsort(tags) { |tag| tag.name }
when 'updated_desc'
tags_sorted_by_committed_date.reverse
@@ -701,10 +699,14 @@ class Repository
end
end
- def contributors
+ # Params:
+ #
+ # order_by: name|email|commits
+ # sort: asc|desc default: 'asc'
+ def contributors(order_by: nil, sort: 'asc')
commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
- commits.group_by(&:author_email).map do |email, commits|
+ commits = commits.group_by(&:author_email).map do |email, commits|
contributor = Gitlab::Contributor.new
contributor.email = email
@@ -718,6 +720,7 @@ class Repository
contributor
end
+ Commit.order_by(collection: commits, order_by: order_by, sort: sort)
end
def refs_contains_sha(ref_type, sha)
@@ -994,7 +997,7 @@ class Repository
end
def create_ref(ref, ref_path)
- raw_repository.write_ref(ref_path, ref)
+ write_ref(ref_path, ref)
end
def ls_files(ref)
diff --git a/app/presenters/group_member_presenter.rb b/app/presenters/group_member_presenter.rb
new file mode 100644
index 00000000000..8f53dfa105e
--- /dev/null
+++ b/app/presenters/group_member_presenter.rb
@@ -0,0 +1,15 @@
+class GroupMemberPresenter < MemberPresenter
+ private
+
+ def admin_member_permission
+ :admin_group_member
+ end
+
+ def update_member_permission
+ :update_group_member
+ end
+
+ def destroy_member_permission
+ :destroy_group_member
+ end
+end
diff --git a/app/presenters/member_presenter.rb b/app/presenters/member_presenter.rb
new file mode 100644
index 00000000000..7d2f9303b8f
--- /dev/null
+++ b/app/presenters/member_presenter.rb
@@ -0,0 +1,38 @@
+class MemberPresenter < Gitlab::View::Presenter::Delegated
+ presents :member
+
+ def access_level_roles
+ member.class.access_level_roles
+ end
+
+ def can_resend_invite?
+ invite? &&
+ can?(current_user, admin_member_permission, source)
+ end
+
+ def can_update?
+ can?(current_user, update_member_permission, member)
+ end
+
+ def can_remove?
+ can?(current_user, destroy_member_permission, member)
+ end
+
+ def can_approve?
+ request? && can_update?
+ end
+
+ private
+
+ def admin_member_permission
+ raise NotImplementedError
+ end
+
+ def update_member_permission
+ raise NotImplementedError
+ end
+
+ def destroy_member_permission
+ raise NotImplementedError
+ end
+end
diff --git a/app/presenters/members_presenter.rb b/app/presenters/members_presenter.rb
new file mode 100644
index 00000000000..e4aba37b69e
--- /dev/null
+++ b/app/presenters/members_presenter.rb
@@ -0,0 +1,15 @@
+class MembersPresenter < Gitlab::View::Presenter::Delegated
+ include Enumerable
+
+ presents :members
+
+ def to_ary
+ to_a
+ end
+
+ def each
+ members.each do |member|
+ yield member.present(current_user: current_user)
+ end
+ end
+end
diff --git a/app/presenters/project_member_presenter.rb b/app/presenters/project_member_presenter.rb
new file mode 100644
index 00000000000..7f42d2b70df
--- /dev/null
+++ b/app/presenters/project_member_presenter.rb
@@ -0,0 +1,15 @@
+class ProjectMemberPresenter < MemberPresenter
+ private
+
+ def admin_member_permission
+ :admin_project_member
+ end
+
+ def update_member_permission
+ :update_project_member
+ end
+
+ def destroy_member_permission
+ :destroy_project_member
+ end
+end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 85db2760e23..c8b112132b3 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -81,7 +81,7 @@ module Ci
end
def related_merge_requests
- MergeRequest.where(source_project: pipeline.project, source_branch: pipeline.ref)
+ MergeRequest.opened.where(source_project: pipeline.project, source_branch: pipeline.ref)
end
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 2c51ac13815..e7463e6e25c 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -106,12 +106,14 @@ class IssuableBaseService < BaseService
end
def merge_quick_actions_into_params!(issuable)
+ original_description = params.fetch(:description, issuable.description)
+
description, command_params =
QuickActions::InterpretService.new(project, current_user)
- .execute(params[:description], issuable)
+ .execute(original_description, issuable)
# Avoid a description already set on an issuable to be overwritten by a nil
- params[:description] = description if params.key?(:description)
+ params[:description] = description if description
params.merge!(command_params)
end
diff --git a/app/services/members/approve_access_request_service.rb b/app/services/members/approve_access_request_service.rb
index c13f289f61e..2a2bb0cae5b 100644
--- a/app/services/members/approve_access_request_service.rb
+++ b/app/services/members/approve_access_request_service.rb
@@ -35,8 +35,17 @@ module Members
def can_update_access_requester?(access_requester, opts = {})
access_requester && (
opts[:force] ||
- can?(current_user, action_member_permission(:update, access_requester), access_requester)
+ can?(current_user, update_member_permission(access_requester), access_requester)
)
end
+
+ def update_member_permission(member)
+ case member
+ when GroupMember
+ :update_group_member
+ when ProjectMember
+ :update_project_member
+ end
+ end
end
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 46c505baf8b..05b93ac8fdb 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -36,7 +36,16 @@ module Members
end
def can_destroy_member?(member)
- member && can?(current_user, action_member_permission(:destroy, member), member)
+ member && can?(current_user, destroy_member_permission(member), member)
+ end
+
+ def destroy_member_permission(member)
+ case member
+ when GroupMember
+ :destroy_group_member
+ when ProjectMember
+ :destroy_project_member
+ end
end
end
end
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index a5686002328..20ca6ec969a 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -83,12 +83,12 @@
You're all done!
- elsif current_user.todos.any?
.todos-all-done
- .svg-content
+ .svg-content.svg-250
= image_tag 'illustrations/todos_all_done.svg'
- if todos_filter_empty?
%h4.text-center
= Gitlab.config.gitlab.no_todos_messages.sample
- %p.text-center
+ %p
Are you looking for things to do? Take a look at
= succeed "," do
= link_to "the opened issues", issues_dashboard_path
@@ -104,7 +104,7 @@
= image_tag 'illustrations/todos_empty.svg'
.todos-empty-content
%h4
- Todos let you see what you should do next.
+ Todos let you see what you should do next
%p
When an issue or merge request is assigned to you, or when you
%strong
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 25ed610466a..eba9cd253bb 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,7 +1,7 @@
-.page-with-sidebar{ class: page_with_sidebar_class }
+.layout-page{ class: page_with_sidebar_class }
- if defined?(nav) && nav
= render "layouts/nav/sidebar/#{nav}"
- .content-wrapper.page-with-new-nav
+ .content-wrapper
= render 'shared/outdated_browser'
.mobile-overlay
.alert-wrapper
diff --git a/app/views/layouts/nav/projects_dropdown/_show.html.haml b/app/views/layouts/nav/projects_dropdown/_show.html.haml
index a7370180bf6..32a24c101fc 100644
--- a/app/views/layouts/nav/projects_dropdown/_show.html.haml
+++ b/app/views/layouts/nav/projects_dropdown/_show.html.haml
@@ -1,4 +1,4 @@
-- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: @project.web_url, avatar_url: @project.avatar_url } if @project&.persisted?
+- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted?
.projects-dropdown-container
.project-dropdown-sidebar
%ul
diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml
index 2a178325041..5b092427496 100644
--- a/app/views/projects/blob/_template_selectors.html.haml
+++ b/app/views/projects/blob/_template_selectors.html.haml
@@ -3,15 +3,15 @@
Template
.template-selector-dropdowns-wrap
.template-type-selector.js-template-type-selector-wrap.hidden
- = dropdown_tag("Choose type", options: { toggle_class: 'btn js-template-type-selector', title: "Choose a template type" } )
+ = dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector', title: "Choose a template type" } )
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a license template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
+ = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+ = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
+ = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
.dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'btn js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
+ = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
.template-selectors-undo-menu.hidden
%span.text-info Template applied
%button.btn.btn-sm.btn-info Undo
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 2baaaf6ac5b..e9d8fc75142 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -20,7 +20,7 @@
.col-sm-10.create-from
.dropdown
= hidden_field_tag :ref, default_ref
- = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide form-control js-branch-select git-revision-dropdown-toggle', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
+ = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select git-revision-dropdown-toggle', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref
= icon('chevron-down')
= render 'shared/ref_dropdown', dropdown_class: 'wide'
diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/projects/clusters/_empty_state.html.haml
index e629cc58b06..b525f4efc83 100644
--- a/app/views/projects/clusters/_empty_state.html.haml
+++ b/app/views/projects/clusters/_empty_state.html.haml
@@ -1,12 +1,12 @@
.row.empty-state
.col-xs-12
.svg-content= image_tag 'illustrations/clusters_empty.svg'
- .col-xs-12.text-center
+ .col-xs-12
.text-content
- %h4= s_('ClusterIntegration|Integrate cluster automation')
+ %h4.text-center= s_('ClusterIntegration|Integrate cluster automation')
- link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
%p= s_('ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
- %p
+ .text-center
= link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 618a6355d23..d66066a6d0b 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -38,8 +38,8 @@
.commiter
- commit_author_link = commit_author_link(commit, avatar: false, size: 24)
- - commit_timeago = time_ago_with_tooltip(commit.committed_date, placement: 'bottom')
- - commit_text = _('%{commit_author_link} committed %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
+ - commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom')
+ - commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe }
.commit-actions.flex-row.hidden-xs
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index 56cf16c3778..ad94113fffd 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -15,9 +15,9 @@
#prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
- "empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started'),
- "empty-loading-svg-path": image_path('illustrations/monitoring/loading'),
- "empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect'),
+ "empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
+ "empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
+ "empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect.svg'),
"additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json),
"project-path": project_path(@project),
"tags-path": project_tags_path(@project),
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index d260aaee2d3..eab7879c7bf 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -14,12 +14,12 @@
.detail-page-header
.detail-page-header-body
- .issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
- = icon('check', class: "hidden-sm hidden-md hidden-lg")
+ .issuable-status-box.status-box.status-box-issue-closed{ class: issue_button_visibility(@issue, false) }
+ = sprite_icon('mobile-issue-close', size: 16, css_class: 'hidden-sm hidden-md hidden-lg')
%span.hidden-xs
Closed
.issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
- = icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
+ = sprite_icon('issue-open-m', size: 16, css_class: 'hidden-sm hidden-md hidden-lg')
%span.hidden-xs Open
.issuable-meta
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index 9df14265737..22c8b6b513d 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -7,7 +7,7 @@
.detail-page-header
.detail-page-header-body
.issuable-status-box.status-box{ class: status_box_class(@merge_request) }
- = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
+ = sprite_icon(@merge_request.state_icon_name, size: 16, css_class: 'hidden-sm hidden-md hidden-lg')
%span.hidden-xs
= @merge_request.state_human_name
@@ -27,7 +27,7 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can_update_merge_request
- %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'js-issuable-edit'
+ %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- unless current_user == @merge_request.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
- if can_update_merge_request
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index e71d58ec26d..16bcf671c25 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,11 +1,13 @@
+- project = local_assigns.fetch(:project)
+- members = local_assigns.fetch(:members)
+
.panel.panel-default
.panel-heading.flex-project-members-panel
%span.flex-project-title
Members of
- %strong
- #{@project.name}
- %span.badge= @project_members.total_count
- = form_tag project_project_members_path(@project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
+ %strong= project.name
+ %span.badge= members.total_count
+ = form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index fd5d3ec56da..d81103c3a92 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -37,5 +37,5 @@
- if @group_links.any?
= render 'projects/project_members/groups', group_links: @group_links
- = render 'projects/project_members/team', members: @project_members
+ = render 'projects/project_members/team', project: @project, members: @project_members
= paginate @project_members, theme: "gitlab"
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 031efa903c5..6e105a5521a 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -14,13 +14,13 @@
.form-group
= label_tag :tag_name, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :tag_name, params[:tag_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
+ = text_field_tag :tag_name, params[:tag_name], required: true, autofocus: true, class: 'form-control'
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10.create-from
.dropdown
= hidden_field_tag :ref, default_ref
- = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide form-control js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
+ = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref
= render 'shared/ref_dropdown', dropdown_class: 'wide'
.help-block
@@ -28,7 +28,7 @@
.form-group
= label_tag :message, nil, class: 'control-label'
.col-sm-10
- = text_area_tag :message, @message, required: false, tabindex: 3, class: 'form-control', rows: 5
+ = text_area_tag :message, @message, required: false, class: 'form-control', rows: 5
.help-block
= s_('TagsPage|Optionally, add a message to the tag.')
%hr
@@ -41,6 +41,6 @@
.help-block
= s_('TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.')
.form-actions
- = button_tag s_('TagsPage|Create tag'), class: 'btn btn-create', tabindex: 3
+ = button_tag s_('TagsPage|Create tag'), class: 'btn btn-create'
= link_to s_('TagsPage|Cancel'), project_tags_path(@project), class: 'btn btn-cancel'
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 3fcc33044e9..81d07074325 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -2,6 +2,8 @@
- status = label_subscription_status(label, @project).inquiry if current_user
- subject = local_assigns[:subject]
- toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user
+- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project)
+- show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project)
%li{ id: label_css_id, data: { id: label.id } }
= render "shared/label_row", label: label
@@ -12,12 +14,14 @@
= icon('caret-down')
.dropdown-menu.dropdown-menu-align-right
%ul
- %li
- = link_to_label(label, subject: subject, type: :merge_request) do
- View merge requests
- %li
- = link_to_label(label, subject: subject) do
- View open issues
+ - if show_label_merge_requests_link
+ %li
+ = link_to_label(label, subject: subject, type: :merge_request) do
+ View merge requests
+ - if show_label_issues_link
+ %li
+ = link_to_label(label, subject: subject) do
+ View open issues
- if current_user
%li.label-subscription
- if can_subscribe_to_label_in_different_levels?(label)
@@ -35,13 +39,20 @@
%li
= link_to 'Edit', edit_label_path(label)
%li
- = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'}
+ = link_to 'Delete',
+ destroy_label_path(label),
+ title: 'Delete',
+ method: :delete,
+ data: {confirm: 'Remove this label? Are you sure?'},
+ class: 'text-danger'
.pull-right.hidden-xs.hidden-sm.hidden-md
- = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do
- view merge requests
- = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do
- view open issues
+ - if show_label_merge_requests_link
+ = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do
+ view merge requests
+ - if show_label_issues_link
+ = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do
+ view open issues
- if current_user
.label-subscription.inline
diff --git a/app/views/shared/_show_aside.html.haml b/app/views/shared/_show_aside.html.haml
deleted file mode 100644
index 3ac9b11b4fa..00000000000
--- a/app/views/shared/_show_aside.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-= link_to '#aside', class: 'show-aside' do
- %i.fa.fa-angle-left
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index de26fa8bbf3..e039a73cd3b 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -6,18 +6,21 @@
.col-xs-12
.svg-content
= image_tag 'illustrations/issues.svg'
- .col-xs-12.text-center
+ .col-xs-12
.text-content
- if has_button && current_user
%h4
- The Issue Tracker is the place to add things that need to be improved or solved in a project
+ = _("The Issue Tracker is the place to add things that need to be improved or solved in a project")
%p
- Issues can be bugs, tasks or ideas to be discussed.
- Also, issues are searchable and filterable.
- - if project_select_button
- = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
- - else
- = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
+ = _("Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.")
+ .text-center
+ - if project_select_button
+ = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue', type: :issues
+ - else
+ = link_to 'New issue', button_path, class: 'btn btn-success', title: 'New issue', id: 'new_issue_link'
- else
+ %h4.text-center= _("There are no issues to show")
+ %p
+ = _("The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.")
.text-center
- %h4 There are no issues to show.
+ = link_to _('Register / Sign In'), new_user_session_path, class: 'btn btn-success'
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
index a65634dce53..04db9de3606 100644
--- a/app/views/shared/empty_states/_labels.html.haml
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -2,10 +2,10 @@
.col-xs-12
.svg-content
= image_tag 'illustrations/labels.svg'
- .col-xs-12.text-center
+ .col-xs-12
.text-content
- %h4 Labels can be applied to issues and merge requests to categorize them.
- %p You can also star a label to make it a priority label.
+ %h4= _("Labels can be applied to issues and merge requests to categorize them.")
+ %p= _("You can also star a label to make it a priority label.")
- if can?(current_user, :admin_label, @project)
- = link_to 'New label', new_project_label_path(@project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
- = link_to 'Generate a default set of labels', generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
+ = link_to _('New label'), new_project_label_path(@project), class: 'btn btn-new', title: _('New label'), id: 'new_label_link'
+ = link_to _('Generate a default set of labels'), generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: _('Generate a default set of labels'), id: 'generate_labels_link'
diff --git a/app/views/shared/empty_states/_merge_requests.html.haml b/app/views/shared/empty_states/_merge_requests.html.haml
index 67f906903e9..2edf3557df4 100644
--- a/app/views/shared/empty_states/_merge_requests.html.haml
+++ b/app/views/shared/empty_states/_merge_requests.html.haml
@@ -6,17 +6,18 @@
.col-xs-12
.svg-content
= image_tag 'illustrations/merge_requests.svg'
- .col-xs-12.text-center
+ .col-xs-12
.text-content
- if has_button
%h4
- Merge requests are a place to propose changes you've made to a project and discuss those changes with others.
+ = _("Merge requests are a place to propose changes you've made to a project and discuss those changes with others")
%p
- Interested parties can even contribute by pushing commits if they want to.
- - if project_select_button
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: 'New merge request', type: :merge_requests
- - else
- = link_to 'New merge request', button_path, class: 'btn btn-new', title: 'New merge request', id: 'new_merge_request_link'
+ = _("Interested parties can even contribute by pushing commits if they want to.")
+ .text-center
+ - if project_select_button
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: _('New merge request'), type: :merge_requests
+ - else
+ = link_to _('New merge request'), button_path, class: 'btn btn-new', title: _('New merge request'), id: 'new_merge_request_link'
- else
%h4.text-center
- There are no merge requests to show.
+ = _("There are no merge requests to show")
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 2c27dd638a7..71878e93255 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -1,9 +1,9 @@
- show_roles = local_assigns.fetch(:show_roles, true)
- show_controls = local_assigns.fetch(:show_controls, true)
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
+- member = local_assigns.fetch(:member)
- user = local_assigns.fetch(:user, member.user)
- source = member.source
-- can_admin_member = can?(current_user, action_member_permission(:update, member), member)
%li.member{ class: dom_class(member), id: dom_id(member) }
%span.list-item-name
@@ -50,18 +50,17 @@
.controls.member-controls
- if show_controls && member.source == current_resource
- - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
+ - if member.can_resend_invite?
= link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
method: :post,
class: 'btn btn-default prepend-left-10 hidden-xs',
title: 'Resend invite'
- - if user != current_user && can_admin_member
+ - if user != current_user && member.can_update?
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level
.member-form-control.dropdown.append-right-5
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
- disabled: !can_admin_member,
data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } }
%span.dropdown-toggle-text
= member.human_access
@@ -70,23 +69,22 @@
= dropdown_title("Change permissions")
.dropdown-content
%ul
- - member.class.access_level_roles.each do |role, role_id|
+ - member.access_level_roles.each do |role, role_id|
%li
= link_to role, "javascript:void(0)",
class: ("is-active" if member.access_level == role_id),
data: { id: role_id, el_id: dom_id(member) }
.prepend-left-5.clearable-input.member-form-control
- = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member, data: { el_id: dom_id(member) }
+ = f.text_field :expires_at,
+ class: 'form-control js-access-expiration-date js-member-update-control',
+ placeholder: 'Expiration date',
+ id: "member_expires_at_#{member.id}",
+ data: { el_id: dom_id(member) }
%i.clear-icon.js-clear-input
- else
%span.member-access-text= member.human_access
- - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
- = link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
- method: :post,
- class: 'btn btn-default prepend-left-10 visible-xs-block'
-
- - elsif member.request? && can_admin_member
+ - if member.can_approve?
= link_to polymorphic_path([:approve_access_request, member]),
method: :post,
class: 'btn btn-success prepend-left-10',
@@ -96,7 +94,7 @@
- unless force_mobile_view
= icon('check inverse', class: 'hidden-xs')
- - if can?(current_user, action_member_permission(:destroy, member), member)
+ - if member.can_remove?
- if current_user == user
= link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]),
method: :delete,
diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml
index 09b9944082f..1fbd6bcc4cb 100644
--- a/app/views/shared/members/_requests.html.haml
+++ b/app/views/shared/members/_requests.html.haml
@@ -1,10 +1,13 @@
+- membership_source = local_assigns.fetch(:membership_source)
+- requesters = local_assigns.fetch(:requesters)
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
-- if requesters.any?
- .panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
- .panel-heading
- Users requesting access to
- %strong= membership_source.name
- %span.badge= requesters.size
- %ul.content-list.members-list
- = render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
+- return if requesters.empty?
+
+.panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
+ .panel-heading
+ Users requesting access to
+ %strong= membership_source.name
+ %span.badge= requesters.size
+ %ul.content-list.members-list
+ = render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
new file mode 100644
index 00000000000..ba31a5aa9c2
--- /dev/null
+++ b/app/workers/all_queues.yml
@@ -0,0 +1,98 @@
+---
+- cronjob:admin_email
+- cronjob:expire_build_artifacts
+- cronjob:gitlab_usage_ping
+- cronjob:import_export_project_cleanup
+- cronjob:pipeline_schedule
+- cronjob:prune_old_events
+- cronjob:remove_expired_group_links
+- cronjob:remove_expired_members
+- cronjob:remove_old_web_hook_logs
+- cronjob:remove_unreferenced_lfs_objects
+- cronjob:repository_archive_cache
+- cronjob:repository_check_batch
+- cronjob:requests_profiles
+- cronjob:schedule_update_user_activity
+- cronjob:stuck_ci_jobs
+- cronjob:stuck_import_jobs
+- cronjob:stuck_merge_jobs
+- cronjob:trending_projects
+
+- gcp_cluster:cluster_install_app
+- gcp_cluster:cluster_provision
+- gcp_cluster:cluster_wait_for_app_installation
+- gcp_cluster:wait_for_cluster_creation
+
+- github_import_advance_stage
+- github_importer:github_import_import_diff_note
+- github_importer:github_import_import_issue
+- github_importer:github_import_import_note
+- github_importer:github_import_import_pull_request
+- github_importer:github_import_refresh_import_jid
+- github_importer:github_import_stage_finish_import
+- github_importer:github_import_stage_import_base_data
+- github_importer:github_import_stage_import_issues_and_diff_notes
+- github_importer:github_import_stage_import_notes
+- github_importer:github_import_stage_import_pull_requests
+- github_importer:github_import_stage_import_repository
+
+- pipeline_cache:expire_job_cache
+- pipeline_cache:expire_pipeline_cache
+- pipeline_creation:create_pipeline
+- pipeline_default:build_coverage
+- pipeline_default:build_trace_sections
+- pipeline_default:pipeline_metrics
+- pipeline_default:pipeline_notification
+- pipeline_default:update_head_pipeline_for_merge_request
+- pipeline_hooks:build_hooks
+- pipeline_hooks:pipeline_hooks
+- pipeline_processing:build_finished
+- pipeline_processing:build_queue
+- pipeline_processing:build_success
+- pipeline_processing:pipeline_process
+- pipeline_processing:pipeline_success
+- pipeline_processing:pipeline_update
+- pipeline_processing:stage_update
+
+- repository_check:repository_check_clear
+- repository_check:repository_check_single_repository
+
+- default
+- mailers # ActionMailer::DeliveryJob.queue_name
+
+- authorized_projects
+- background_migration
+- create_gpg_signature
+- delete_merged_branches
+- delete_user
+- email_receiver
+- emails_on_push
+- expire_build_instance_artifacts
+- git_garbage_collect
+- gitlab_shell
+- group_destroy
+- invalid_gpg_signature_update
+- irker
+- merge
+- namespaceless_project_destroy
+- new_issue
+- new_merge_request
+- new_note
+- pages
+- post_receive
+- process_commit
+- project_cache
+- project_destroy
+- project_export
+- project_migrate_hashed_storage
+- project_service
+- propagate_service_template
+- reactive_caching
+- repository_fork
+- repository_import
+- storage_migrator
+- system_hook_push
+- update_merge_requests
+- update_user_activity
+- upload_checksum
+- web_hook
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index 5efa9180f5e..97d80305bec 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -2,7 +2,7 @@ class BuildFinishedWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb
index 6705a1c2709..cbfca8c342c 100644
--- a/app/workers/build_hooks_worker.rb
+++ b/app/workers/build_hooks_worker.rb
@@ -2,7 +2,7 @@ class BuildHooksWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :hooks
+ queue_namespace :pipeline_hooks
def perform(build_id)
Ci::Build.find_by(id: build_id)
diff --git a/app/workers/build_queue_worker.rb b/app/workers/build_queue_worker.rb
index fc775a84dc0..e4f4e6c1d9e 100644
--- a/app/workers/build_queue_worker.rb
+++ b/app/workers/build_queue_worker.rb
@@ -2,7 +2,7 @@ class BuildQueueWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb
index ec049821ad7..4b9097bc5e4 100644
--- a/app/workers/build_success_worker.rb
+++ b/app/workers/build_success_worker.rb
@@ -2,7 +2,7 @@ class BuildSuccessWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb
index 9c3bdabc49e..37586e161c9 100644
--- a/app/workers/concerns/application_worker.rb
+++ b/app/workers/concerns/application_worker.rb
@@ -3,13 +3,23 @@ Sidekiq::Worker.extend ActiveSupport::Concern
module ApplicationWorker
extend ActiveSupport::Concern
- include Sidekiq::Worker
+ include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker
included do
- sidekiq_options queue: base_queue_name
+ set_queue
end
module ClassMethods
+ def inherited(subclass)
+ subclass.set_queue
+ end
+
+ def set_queue
+ queue_name = [queue_namespace, base_queue_name].compact.join(':')
+
+ sidekiq_options queue: queue_name # rubocop:disable Cop/SidekiqOptionsQueue
+ end
+
def base_queue_name
name
.sub(/\AGitlab::/, '')
@@ -18,6 +28,16 @@ module ApplicationWorker
.tr('/', '_')
end
+ def queue_namespace(new_namespace = nil)
+ if new_namespace
+ sidekiq_options queue_namespace: new_namespace
+
+ set_queue
+ else
+ get_sidekiq_options['queue_namespace']&.to_s
+ end
+ end
+
def queue
get_sidekiq_options['queue'].to_s
end
diff --git a/app/workers/concerns/cluster_queue.rb b/app/workers/concerns/cluster_queue.rb
index a5074d13220..24b9f145220 100644
--- a/app/workers/concerns/cluster_queue.rb
+++ b/app/workers/concerns/cluster_queue.rb
@@ -5,6 +5,6 @@ module ClusterQueue
extend ActiveSupport::Concern
included do
- sidekiq_options queue: :gcp_cluster
+ queue_namespace :gcp_cluster
end
end
diff --git a/app/workers/concerns/cronjob_queue.rb b/app/workers/concerns/cronjob_queue.rb
index e918bb011e0..b6581779f6a 100644
--- a/app/workers/concerns/cronjob_queue.rb
+++ b/app/workers/concerns/cronjob_queue.rb
@@ -4,6 +4,7 @@ module CronjobQueue
extend ActiveSupport::Concern
included do
- sidekiq_options queue: :cronjob, retry: false
+ queue_namespace :cronjob
+ sidekiq_options retry: false
end
end
diff --git a/app/workers/concerns/gitlab/github_import/queue.rb b/app/workers/concerns/gitlab/github_import/queue.rb
index a2bee361b86..22c2ce458e8 100644
--- a/app/workers/concerns/gitlab/github_import/queue.rb
+++ b/app/workers/concerns/gitlab/github_import/queue.rb
@@ -4,12 +4,14 @@ module Gitlab
extend ActiveSupport::Concern
included do
+ queue_namespace :github_importer
+
# If a job produces an error it may block a stage from advancing
# forever. To prevent this from happening we prevent jobs from going to
# the dead queue. This does mean some resources may not be imported, but
# this is better than a project being stuck in the "import" state
# forever.
- sidekiq_options queue: 'github_importer', dead: false, retry: 5
+ sidekiq_options dead: false, retry: 5
end
end
end
diff --git a/app/workers/concerns/pipeline_queue.rb b/app/workers/concerns/pipeline_queue.rb
index ddf45b91345..e77093a6902 100644
--- a/app/workers/concerns/pipeline_queue.rb
+++ b/app/workers/concerns/pipeline_queue.rb
@@ -5,14 +5,6 @@ module PipelineQueue
extend ActiveSupport::Concern
included do
- sidekiq_options queue: 'pipeline_default'
- end
-
- class_methods do
- def enqueue_in(group:)
- raise ArgumentError, 'Unspecified queue group!' if group.empty?
-
- sidekiq_options queue: "pipeline_#{group}"
- end
+ queue_namespace :pipeline_default
end
end
diff --git a/app/workers/concerns/repository_check_queue.rb b/app/workers/concerns/repository_check_queue.rb
index a597321ccf4..43fb66c31b0 100644
--- a/app/workers/concerns/repository_check_queue.rb
+++ b/app/workers/concerns/repository_check_queue.rb
@@ -3,6 +3,8 @@ module RepositoryCheckQueue
extend ActiveSupport::Concern
included do
- sidekiq_options queue: :repository_check, retry: false
+ queue_namespace :repository_check
+
+ sidekiq_options retry: false
end
end
diff --git a/app/workers/create_pipeline_worker.rb b/app/workers/create_pipeline_worker.rb
index 00cd7b85b9f..c3ac35e54f5 100644
--- a/app/workers/create_pipeline_worker.rb
+++ b/app/workers/create_pipeline_worker.rb
@@ -2,7 +2,7 @@ class CreatePipelineWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :creation
+ queue_namespace :pipeline_creation
def perform(project_id, user_id, ref, source, params = {})
project = Project.find(project_id)
diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb
index a591e2da519..7217364a9f2 100644
--- a/app/workers/expire_job_cache_worker.rb
+++ b/app/workers/expire_job_cache_worker.rb
@@ -2,7 +2,7 @@ class ExpireJobCacheWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :cache
+ queue_namespace :pipeline_cache
def perform(job_id)
job = CommitStatus.joins(:pipeline, :project).find_by(id: job_id)
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index a3ac32b437d..3e34de22c19 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -2,7 +2,7 @@ class ExpirePipelineCacheWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :cache
+ queue_namespace :pipeline_cache
def perform(pipeline_id)
pipeline = Ci::Pipeline.find_by(id: pipeline_id)
diff --git a/app/workers/gitlab/github_import/advance_stage_worker.rb b/app/workers/gitlab/github_import/advance_stage_worker.rb
index 400396d5755..f7f498af840 100644
--- a/app/workers/gitlab/github_import/advance_stage_worker.rb
+++ b/app/workers/gitlab/github_import/advance_stage_worker.rb
@@ -9,7 +9,7 @@ module Gitlab
class AdvanceStageWorker
include ApplicationWorker
- sidekiq_options queue: 'github_importer_advance_stage', dead: false
+ sidekiq_options dead: false
INTERVAL = 30.seconds.to_i
diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb
index 62f733c02fc..3ec81d040b4 100644
--- a/app/workers/pages_worker.rb
+++ b/app/workers/pages_worker.rb
@@ -1,7 +1,7 @@
class PagesWorker
include ApplicationWorker
- sidekiq_options queue: :pages, retry: false
+ sidekiq_options retry: false
def perform(action, *arg)
send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb
index 661c29efe88..c94918ff4ee 100644
--- a/app/workers/pipeline_hooks_worker.rb
+++ b/app/workers/pipeline_hooks_worker.rb
@@ -2,7 +2,7 @@ class PipelineHooksWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :hooks
+ queue_namespace :pipeline_hooks
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id)
diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb
index 07dbf6a971e..24424b3f472 100644
--- a/app/workers/pipeline_process_worker.rb
+++ b/app/workers/pipeline_process_worker.rb
@@ -2,7 +2,7 @@ class PipelineProcessWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id)
diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb
index 68c40a259e1..2ab0739a17f 100644
--- a/app/workers/pipeline_success_worker.rb
+++ b/app/workers/pipeline_success_worker.rb
@@ -2,7 +2,7 @@ class PipelineSuccessWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb
index 24a8a9fbed5..fc9da2d45b1 100644
--- a/app/workers/pipeline_update_worker.rb
+++ b/app/workers/pipeline_update_worker.rb
@@ -2,7 +2,7 @@ class PipelineUpdateWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id)
diff --git a/app/workers/stage_update_worker.rb b/app/workers/stage_update_worker.rb
index 69f2318d83b..e4b683fca33 100644
--- a/app/workers/stage_update_worker.rb
+++ b/app/workers/stage_update_worker.rb
@@ -2,7 +2,7 @@ class StageUpdateWorker
include ApplicationWorker
include PipelineQueue
- enqueue_in group: :processing
+ queue_namespace :pipeline_processing
def perform(stage_id)
Ci::Stage.find_by(id: stage_id).try do |stage|
diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb
index 36d2a2e6466..16394293c79 100644
--- a/app/workers/stuck_merge_jobs_worker.rb
+++ b/app/workers/stuck_merge_jobs_worker.rb
@@ -23,7 +23,12 @@ class StuckMergeJobsWorker
merge_requests = MergeRequest.where(id: completed_ids)
merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged)
- merge_requests.where(merge_commit_sha: nil).update_all(state: :opened, merge_jid: nil)
+
+ merge_requests_to_reopen = merge_requests.where(merge_commit_sha: nil)
+
+ # Do not reopen merge requests using direct queries.
+ # We rely on state machine callbacks to update head_pipeline_id
+ merge_requests_to_reopen.each(&:unlock_mr)
Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
end
diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb
index 0a2e9b63578..f09d89aa170 100644
--- a/app/workers/update_head_pipeline_for_merge_request_worker.rb
+++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb
@@ -1,15 +1,25 @@
class UpdateHeadPipelineForMergeRequestWorker
include ApplicationWorker
-
- sidekiq_options queue: 'pipeline_default'
+ include PipelineQueue
def perform(merge_request_id)
merge_request = MergeRequest.find(merge_request_id)
pipeline = Ci::Pipeline.where(project: merge_request.source_project, ref: merge_request.source_branch).last
return unless pipeline && pipeline.latest?
- raise ArgumentError, 'merge request sha does not equal pipeline sha' if merge_request.diff_head_sha != pipeline.sha
+
+ if merge_request.diff_head_sha != pipeline.sha
+ log_error_message_for(merge_request)
+
+ return
+ end
merge_request.update_attribute(:head_pipeline_id, pipeline.id)
end
+
+ def log_error_message_for(merge_request)
+ Rails.logger.error(
+ "Outdated head pipeline for active merge request: id=#{merge_request.id}, source_branch=#{merge_request.source_branch}, diff_head_sha=#{merge_request.diff_head_sha}"
+ )
+ end
end
diff --git a/changelogs/unreleased/13695-order-contributors-in-api.yml b/changelogs/unreleased/13695-order-contributors-in-api.yml
new file mode 100644
index 00000000000..26bf8650a4a
--- /dev/null
+++ b/changelogs/unreleased/13695-order-contributors-in-api.yml
@@ -0,0 +1,5 @@
+---
+title: Adds ordering to projects contributors in API
+merge_request: 15469
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/25317-prioritize-author-date-over-commit.yml b/changelogs/unreleased/25317-prioritize-author-date-over-commit.yml
new file mode 100644
index 00000000000..a5f6d316a7d
--- /dev/null
+++ b/changelogs/unreleased/25317-prioritize-author-date-over-commit.yml
@@ -0,0 +1,5 @@
+---
+title: Show authored date rather than committed date on the commit list
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/28004-consider-refactoring-member-view-by-using-presenter.yml b/changelogs/unreleased/28004-consider-refactoring-member-view-by-using-presenter.yml
new file mode 100644
index 00000000000..0e91d4ae403
--- /dev/null
+++ b/changelogs/unreleased/28004-consider-refactoring-member-view-by-using-presenter.yml
@@ -0,0 +1,4 @@
+---
+title: Refactor member view using a Presenter
+merge_request: 9645
+author: TM Lee
diff --git a/changelogs/unreleased/33926-update-issuable-icons.yml b/changelogs/unreleased/33926-update-issuable-icons.yml
new file mode 100644
index 00000000000..87076dde545
--- /dev/null
+++ b/changelogs/unreleased/33926-update-issuable-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Update issuable status icons
+merge_request: 15898
+author:
+type: changed
diff --git a/changelogs/unreleased/40285-prometheus-loading-screen-no-longer-seems-to-appear.yml b/changelogs/unreleased/40285-prometheus-loading-screen-no-longer-seems-to-appear.yml
new file mode 100644
index 00000000000..978930a5b8c
--- /dev/null
+++ b/changelogs/unreleased/40285-prometheus-loading-screen-no-longer-seems-to-appear.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken illustration images for monitoring page empty states
+merge_request: 15889
+author:
+type: fixed
diff --git a/changelogs/unreleased/40509_sorting_tags_api.yml b/changelogs/unreleased/40509_sorting_tags_api.yml
new file mode 100644
index 00000000000..38b198d0fe3
--- /dev/null
+++ b/changelogs/unreleased/40509_sorting_tags_api.yml
@@ -0,0 +1,5 @@
+---
+title: add support for sorting in tags api
+merge_request: 15772
+author: haseebeqx
+type: added
diff --git a/changelogs/unreleased/40895-fix-frequent-projects-stale-path.yml b/changelogs/unreleased/40895-fix-frequent-projects-stale-path.yml
new file mode 100644
index 00000000000..485133b46a7
--- /dev/null
+++ b/changelogs/unreleased/40895-fix-frequent-projects-stale-path.yml
@@ -0,0 +1,5 @@
+---
+title: Use relative URL for projects to avoid storing domains
+merge_request: 15876
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-tcp-check-rake-task.yml b/changelogs/unreleased/add-tcp-check-rake-task.yml
new file mode 100644
index 00000000000..a7c04bd0d55
--- /dev/null
+++ b/changelogs/unreleased/add-tcp-check-rake-task.yml
@@ -0,0 +1,5 @@
+---
+title: Add a gitlab:tcp_check rake task
+merge_request: 15759
+author:
+type: added
diff --git a/changelogs/unreleased/fix-create-mr-from-issue-with-template.yml b/changelogs/unreleased/fix-create-mr-from-issue-with-template.yml
new file mode 100644
index 00000000000..8668aa18669
--- /dev/null
+++ b/changelogs/unreleased/fix-create-mr-from-issue-with-template.yml
@@ -0,0 +1,5 @@
+---
+title: Execute quick actions (if present) when creating MR from issue
+merge_request: 15810
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue-description-field-typo.yml b/changelogs/unreleased/issue-description-field-typo.yml
new file mode 100644
index 00000000000..9c4c179876d
--- /dev/null
+++ b/changelogs/unreleased/issue-description-field-typo.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed typo for issue description field declaration
+merge_request:
+author: Marcus Amargi
+type: fixed
diff --git a/changelogs/unreleased/optimize-issues-avoid-noop-empty-cache-updates2.yml b/changelogs/unreleased/optimize-issues-avoid-noop-empty-cache-updates2.yml
new file mode 100644
index 00000000000..e0c3136be69
--- /dev/null
+++ b/changelogs/unreleased/optimize-issues-avoid-noop-empty-cache-updates2.yml
@@ -0,0 +1,6 @@
+---
+title: Treat empty markdown and html strings as valid cached text, not missing cache
+ that needs to be updated
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/optimize-projects-for-imported-projects.yml b/changelogs/unreleased/optimize-projects-for-imported-projects.yml
new file mode 100644
index 00000000000..13186fa36d5
--- /dev/null
+++ b/changelogs/unreleased/optimize-projects-for-imported-projects.yml
@@ -0,0 +1,6 @@
+---
+title: check the import_status field before doing SQL operations to check the import
+ url
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/remove-tabindexes-from-tag-form.yml b/changelogs/unreleased/remove-tabindexes-from-tag-form.yml
new file mode 100644
index 00000000000..a15bf2a7a4f
--- /dev/null
+++ b/changelogs/unreleased/remove-tabindexes-from-tag-form.yml
@@ -0,0 +1,5 @@
+---
+title: removed tabindexes from tag form
+merge_request:
+author: Marcus Amargi
+type: changed
diff --git a/changelogs/unreleased/sophie-h-gitlab-ce-patch-15.yml b/changelogs/unreleased/sophie-h-gitlab-ce-patch-15.yml
new file mode 100644
index 00000000000..b5e3210c737
--- /dev/null
+++ b/changelogs/unreleased/sophie-h-gitlab-ce-patch-15.yml
@@ -0,0 +1,5 @@
+---
+title: Hide link to issues/MRs from labels list if issues/MRs are disabled.
+merge_request: 15863
+author: Sophie Herold
+type: fixed
diff --git a/changelogs/unreleased/tc-correct-email-in-reply-to.yml b/changelogs/unreleased/tc-correct-email-in-reply-to.yml
new file mode 100644
index 00000000000..1c8043f6a5c
--- /dev/null
+++ b/changelogs/unreleased/tc-correct-email-in-reply-to.yml
@@ -0,0 +1,5 @@
+---
+title: Make mail notifications of discussion notes In-Reply-To of each other
+merge_request: 14289
+author:
+type: changed
diff --git a/changelogs/unreleased/zj-memoization-mr-commits.yml b/changelogs/unreleased/zj-memoization-mr-commits.yml
new file mode 100644
index 00000000000..59dfc6d6049
--- /dev/null
+++ b/changelogs/unreleased/zj-memoization-mr-commits.yml
@@ -0,0 +1,5 @@
+---
+title: Cache commits for MergeRequest diffs
+merge_request:
+author:
+type: performance
diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb
index bfab8c77a4b..cc9167d29b9 100644
--- a/config/initializers/flipper.rb
+++ b/config/initializers/flipper.rb
@@ -1,8 +1,22 @@
-require 'flipper/middleware/memoizer'
+require 'flipper/adapters/active_record'
+require 'flipper/adapters/active_support_cache_store'
-unless Rails.env.test?
- Rails.application.config.middleware.use Flipper::Middleware::Memoizer,
- lambda { Feature.flipper }
+Flipper.configure do |config|
+ config.default do
+ adapter = Flipper::Adapters::ActiveRecord.new(
+ feature_class: Feature::FlipperFeature, gate_class: Feature::FlipperGate)
+ cached_adapter = Flipper::Adapters::ActiveSupportCacheStore.new(
+ adapter,
+ Rails.cache,
+ expires_in: 10.seconds)
+
+ Flipper.new(cached_adapter)
+ end
+end
- Feature.register_feature_groups
+Feature.register_feature_groups
+
+unless Rails.env.test?
+ require 'flipper/middleware/memoizer'
+ Rails.application.config.middleware.use Flipper::Middleware::Memoizer
end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index ba4481ae602..0f164e628f9 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -42,6 +42,8 @@ Sidekiq.configure_server do |config|
Gitlab::SidekiqThrottler.execute!
+ Gitlab::SidekiqVersioning.install!
+
config = Gitlab::Database.config ||
Rails.application.config.database_configuration[Rails.env]
config['pool'] = Sidekiq.options[:concurrency]
@@ -60,19 +62,3 @@ Sidekiq.configure_client do |config|
chain.add Gitlab::SidekiqStatus::ClientMiddleware
end
end
-
-# The Sidekiq client API always adds the queue to the Sidekiq queue
-# list, but mail_room and gitlab-shell do not. This is only necessary
-# for monitoring.
-begin
- queues = Gitlab::SidekiqConfig.worker_queues
-
- Sidekiq.redis do |conn|
- conn.pipelined do
- queues.each do |queue|
- conn.sadd('queues', queue)
- end
- end
- end
-rescue Redis::BaseError, SocketError, Errno::ENOENT, Errno::EADDRNOTAVAIL, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED
-end
diff --git a/config/no_todos_messages.yml b/config/no_todos_messages.yml
index 264a975b614..da721a9b6e6 100644
--- a/config/no_todos_messages.yml
+++ b/config/no_todos_messages.yml
@@ -3,9 +3,9 @@
#
# If you come up with a fun one, please feel free to contribute it to GitLab!
# https://about.gitlab.com/contributing/
----
-- Good job! Looks like you don't have any todos left.
+---
+- Good job! Looks like you don't have any todos left
- Isn't an empty todo list beautiful?
- Give yourself a pat on the back!
- Nothing left to do, high five!
-- Henceforth you shall be known as "Todo Destroyer".
+- Henceforth you shall be known as "Todo Destroyer"
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index bc7c431731a..31a38f2b508 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -25,8 +25,6 @@
- [new_note, 2]
- [new_issue, 2]
- [new_merge_request, 2]
- - [build, 2]
- - [pipeline, 2]
- [pipeline_processing, 5]
- [pipeline_creation, 4]
- [pipeline_default, 3]
@@ -38,11 +36,12 @@
- [mailers, 2]
- [invalid_gpg_signature_update, 2]
- [create_gpg_signature, 2]
+ - [rebase, 2]
- [upload_checksum, 1]
- [repository_fork, 1]
- [repository_import, 1]
- [github_importer, 1]
- - [github_importer_advance_stage, 1]
+ - [github_import_advance_stage, 1]
- [project_service, 1]
- [delete_user, 1]
- [delete_merged_branches, 1]
diff --git a/db/post_migrate/20171213160445_migrate_github_importer_advance_stage_sidekiq_queue.rb b/db/post_migrate/20171213160445_migrate_github_importer_advance_stage_sidekiq_queue.rb
new file mode 100644
index 00000000000..149c28f1946
--- /dev/null
+++ b/db/post_migrate/20171213160445_migrate_github_importer_advance_stage_sidekiq_queue.rb
@@ -0,0 +1,16 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MigrateGithubImporterAdvanceStageSidekiqQueue < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ sidekiq_queue_migrate 'github_importer_advance_stage', to: 'github_import_advance_stage'
+ end
+
+ def down
+ sidekiq_queue_migrate 'github_import_advance_stage', to: 'github_importer_advance_stage'
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f0b1da16d53..2048c50f892 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: 20171206221519) do
+ActiveRecord::Schema.define(version: 20171213160445) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md
index ee9b9a9466a..373d4239f71 100644
--- a/doc/administration/auth/README.md
+++ b/doc/administration/auth/README.md
@@ -14,3 +14,4 @@ providers.
- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS
- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [Okta](okta.md) Configure GitLab to sign in using Okta
+- [Authentiq](authentiq.md): Enable the Authentiq OmniAuth provider for passwordless authentication
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index a88e67bfeb5..ea8077f0623 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -37,6 +37,7 @@ Follow the steps below to configure an active/active setup:
1. [Configure the database](database.md)
1. [Configure Redis](redis.md)
+ 1. [Configure Redis for GitLab source installations](redis_source.md)
1. [Configure NFS](nfs.md)
1. [Configure the GitLab application servers](gitlab.md)
1. [Configure the load balancers](load_balancer.md)
diff --git a/doc/administration/index.md b/doc/administration/index.md
index e6986a2b07f..58922b71ae7 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -16,21 +16,27 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Install](../install/README.md): Requirements, directory structures, and installation methods.
- [High Availability](high_availability/README.md): Configure multiple servers for scaling or high availability.
+ - [High Availability on AWS](../university/high-availability/aws/README.md): Set up GitLab HA on Amazon AWS.
### Configuring GitLab
- [Adjust your instance's timezone](../workflow/timezone.md): Customize the default time zone of GitLab.
-- [Header logo](../customization/branded_page_and_email_header.md): Change the logo on all pages and email headers.
-- [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page.
- [System hooks](../system_hooks/system_hooks.md): Notifications when users, projects and keys are changed.
- [Security](../security/README.md): Learn what you can do to further secure your GitLab instance.
- [Usage statistics, version check, and usage ping](../user/admin_area/settings/usage_statistics.md): Enable or disable information about your instance to be sent to GitLab, Inc.
- [Polling](polling.md): Configure how often the GitLab UI polls for updates.
- [GitLab Pages configuration](pages/index.md): Enable and configure GitLab Pages.
-- [GitLab Pages configuration for installations from the source](pages/source.md): Enable and configure GitLab Pages on
+- [GitLab Pages configuration for GitLab source installations](pages/source.md): Enable and configure GitLab Pages on
[source installations](../install/installation.md#installation-from-source).
- [Environment variables](environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab.
+#### Customizing GitLab's appearance
+
+- [Header logo](../customization/branded_page_and_email_header.md): Change the logo on all pages and email headers.
+- [Branded login page](../customization/branded_login_page.md): Customize the login page with your own logo, title, and description.
+- [Welcome message](../customization/welcome_message.md): Add a custom welcome message to the sign-in page.
+- ["New Project" page](../customization/new_project_page.md): Customize the text to be displayed on the page that opens whenever your users create a new project.
+
### Maintaining GitLab
- [Raketasks](../raketasks/README.md): Perform various tasks for maintenance, backups, automatic webhooks setup, etc.
@@ -74,6 +80,7 @@ server with IMAP authentication on Ubuntu, to be used with Reply by email.
- [Issue closing pattern](issue_closing_pattern.md): Customize how to close an issue from commit messages.
- [Gitaly](gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service.
- [Default labels](../user/admin_area/labels.html): Create labels that will be automatically added to every new project.
+- [Restrict the use of public or internal projects](../public_access/public_access.md#restricting-the-use-of-public-or-internal-projects): Restrict the use of visibility levels for users when they create a project or a snippet.
### Repository settings
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index 136192191f9..ecf92c379fd 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -221,3 +221,22 @@ sudo gitlab-rake gitlab:shell:create_hooks
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:shell:create_hooks RAILS_ENV=production
```
+
+## Check TCP connectivity to a remote site
+
+Sometimes you need to know if your GitLab installation can connect to a TCP
+service on another machine - perhaps a PostgreSQL or HTTPS server. A rake task
+is included to help you with this:
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:tcp_check[example.com,80]
+```
+
+**Source Installation**
+
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:tcp_check[example.com,80] RAILS_ENV=production
+```
diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md
index 1304476e678..3a2cced37bf 100644
--- a/doc/administration/reply_by_email.md
+++ b/doc/administration/reply_by_email.md
@@ -89,9 +89,11 @@ email address in order to sign up.
If you also host a public-facing GitLab instance at `hooli.com` and set your
incoming email domain to `hooli.com`, an attacker could abuse the "Create new
-issue by email" feature by using a project's unique address as the email when
-signing up for Slack, which would send a confirmation email, which would create
-a new issue on the project owned by the attacker, allowing them to click the
+issue by email" or
+"[Create new merge request by email](../user/project/merge_requests/index.md#create-new-merge-requests-by-email)"
+features by using a project's unique address as the email when signing up for
+Slack, which would send a confirmation email, which would create a new issue or
+merge request on the project owned by the attacker, allowing them to click the
confirmation link and validate their account on your company's private Slack
instance.
diff --git a/doc/api/issues.md b/doc/api/issues.md
index ec8ff3cd3f3..d2fefbe68aa 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -514,9 +514,9 @@ PUT /projects/:id/issues/:issue_iid
| `title` | string | no | The title of an issue |
| `description` | string | no | The description of an issue |
| `confidential` | boolean | no | Updates an issue to be confidential |
-| `assignee_ids` | Array[integer] | no | The ID of the users to assign the issue to |
-| `milestone_id` | integer | no | The ID of a milestone to assign the issue to |
-| `labels` | string | no | Comma-separated label names for an issue |
+| `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. |
+| `milestone_id` | integer | no | The ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.|
+| `labels` | string | no | Comma-separated label names for an issue. Set to an empty string to unassign all labels. |
| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index b2e4b6d0955..880b0ed2c65 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -524,15 +524,15 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `merge_request_iid` | integer | yes | The ID of a merge request |
| `target_branch` | string | no | The target branch |
| `title` | string | no | Title of MR |
-| `assignee_id` | integer | no | Assignee user ID |
+| `assignee_id` | integer | no | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees. |
+| `milestone_id` | integer | no | The ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
+| `labels` | string | no | Comma-separated label names for an merge request. Set to an empty string to unassign all labels. |
| `description` | string | no | Description of MR |
| `state_event` | string | no | New state (close/reopen) |
-| `labels` | string | no | Labels for MR as a comma-separated list |
-| `milestone_id` | integer | no | The ID of a milestone |
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
| `discussion_locked` | boolean | no | Flag indicating if the merge request's discussion is locked. If the discussion is locked only project members can add, edit or resolve comments. |
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 594babc74be..03b32577872 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -182,6 +182,8 @@ GET /projects/:id/repository/contributors
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+- `order_by` (optional) - Return contributors ordered by `name`, `email`, or `commits` fields. If not given contributors are ordered by commit date.
+- `sort` (optional) - Return contributors sorted in `asc` or `desc` order. Default is `asc`
Response:
diff --git a/doc/api/tags.md b/doc/api/tags.md
index bebe6536b6e..fa25dc76452 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -12,7 +12,11 @@ GET /projects/:id/repository/tags
Parameters:
-- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string| yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user|
+| `order_by` | string | no | Return tags ordered by `name` or `updated` fields. Default is `updated` |
+| `sort` | string | no | Return tags sorted in `asc` or `desc` order. Default is `desc` |
```json
[
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index ecb8f15c851..fb5bfe26bb0 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -319,45 +319,62 @@ As you can see, the syntax of `command` is similar to [Dockerfile's `CMD`][cmd].
> Introduced in GitLab and GitLab Runner 9.4. Read more about the [extended
configuration options](#extended-docker-configuration-options).
+Before showing the available entrypoint override methods, let's describe shortly
+how the Runner starts and uses a Docker image for the containers used in the
+CI jobs:
+
+1. The Runner starts a Docker container using the defined entrypoint (default
+ from `Dockerfile` that may be overridden in `.gitlab-ci.yml`)
+1. The Runner attaches itself to a running container.
+1. The Runner prepares a script (the combination of
+ [`before_script`](../yaml/README.md#before_script),
+ [`script`](../yaml/README.md#script),
+ and [`after_script`](../yaml/README.md#after_script)).
+1. The Runner sends the script to the container's shell STDIN and receives the
+ output.
+
+To override the entrypoint of a Docker image, the recommended solution is to
+define an empty `entrypoint` in `.gitlab-ci.yml`, so the Runner doesn't start
+a useless shell layer. However, that will not work for all Docker versions, and
+you should check which one your Runner is using. Specifically:
+
+- If Docker 17.06 or later is used, the `entrypoint` can be set to an empty value.
+- If Docker 17.03 or previous versions are used, the `entrypoint` can be set to
+ `/bin/sh -c`, `/bin/bash -c` or an equivalent shell available in the image.
+
+The syntax of `image:entrypoint` is similar to [Dockerfile's `ENTRYPOINT`][entrypoint].
+
+----
+
Let's assume you have a `super/sql:experimental` image with some SQL database
inside it and you would like to use it as a base image for your job because you
want to execute some tests with this database binary. Let's also assume that
this image is configured with `/usr/bin/super-sql run` as an entrypoint. That
-means, that when starting the container without additional options, it will run
+means that when starting the container without additional options, it will run
the database's process, while Runner expects that the image will have no
-entrypoint or at least will start with a shell as its entrypoint.
-
-Before the new extended Docker configuration options, you would need to create
-your own image based on the `super/sql:experimental` image, set the entrypoint
-to a shell and then use it in job's configuration, like:
+entrypoint or that the entrypoint is prepared to start a shell command.
-```Dockerfile
-# my-super-sql:experimental image's Dockerfile
+With the extended Docker configuration options, instead of creating your
+own image based on `super/sql:experimental`, setting the `ENTRYPOINT`
+to a shell, and then using the new image in your CI job, you can now simply
+define an `entrypoint` in `.gitlab-ci.yml`.
-FROM super/sql:experimental
-ENTRYPOINT ["/bin/sh"]
-```
+**For Docker 17.06+:**
```yaml
-# .gitlab-ci.yml
-
-image: my-super-sql:experimental
+image:
+ name: super/sql:experimental
+ entrypoint: [""]
```
-After the new extended Docker configuration options, you can now simply
-set an `entrypoint` in `.gitlab-ci.yml`, like:
+**For Docker =< 17.03:**
```yaml
-# .gitlab-ci.yml
-
image:
name: super/sql:experimental
- entrypoint: ["/bin/sh"]
+ entrypoint: ["/bin/sh", "-c"]
```
-As you can see the syntax of `entrypoint` is similar to
-[Dockerfile's `ENTRYPOINT`][entrypoint].
-
## Define image and services in `config.toml`
Look for the `[runners.docker]` section:
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index e5a2bbd1773..df0e1521150 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -1,84 +1,106 @@
-# Using SSH keys
+---
+last_updated: 2017-12-13
+---
+
+# Using SSH keys with GitLab CI/CD
GitLab currently doesn't have built-in support for managing SSH keys in a build
-environment.
+environment (where the GitLab Runner runs).
The SSH keys can be useful when:
1. You want to checkout internal submodules
-2. You want to download private packages using your package manager (eg. bundler)
-3. You want to deploy your application to eg. Heroku or your own server
-4. You want to execute SSH commands from the build server to the remote server
-5. You want to rsync files from your build server to the remote server
+1. You want to download private packages using your package manager (e.g., Bundler)
+1. You want to deploy your application to your own server, or, for example, Heroku
+1. You want to execute SSH commands from the build environment to a remote server
+1. You want to rsync files from the build environment to a remote server
If anything of the above rings a bell, then you most likely need an SSH key.
-## Inject keys in your build server
-
The most widely supported method is to inject an SSH key into your build
-environment by extending your `.gitlab-ci.yml`.
-
-This is the universal solution which works with any type of executor
-(docker, shell, etc.).
-
-### How it works
-
-1. Create a new SSH key pair with [ssh-keygen][]
-2. Add the private key as a **Secret Variable** to the project
-3. Run the [ssh-agent][] during job to load the private key.
+environment by extending your `.gitlab-ci.yml`, and it's a solution which works
+with any type of [executor](https://docs.gitlab.com/runner/executors/)
+(Docker, shell, etc.).
+
+## How it works
+
+1. Create a new SSH key pair locally with [ssh-keygen](http://linux.die.net/man/1/ssh-keygen)
+1. Add the private key as a [secret variable](../variables/README.md) to
+ your project
+1. Run the [ssh-agent](http://linux.die.net/man/1/ssh-agent) during job to load
+ the private key.
+1. Copy the public key to the servers you want to have access to (usually in
+ `~/.ssh/authorized_keys`) or add it as a [deploy key](../../ssh/README.md#deploy-keys)
+ if you are accessing a private GitLab repository.
+
+NOTE: **Note:**
+The private key will not be displayed in the job trace, unless you enable
+[debug tracing](../variables/README.md#debug-tracing). You might also want to
+check the [visibility of your pipelines](../../user/project/pipelines/settings.md#visibility-of-pipelines).
## SSH keys when using the Docker executor
-You will first need to create an SSH key pair. For more information, follow the
-instructions to [generate an SSH key](../../ssh/README.md). Do not add a
-passphrase to the SSH key, or the `before_script` will prompt for it.
-
-Then, create a new **Secret Variable** in your project settings on GitLab
-following **Settings > CI/CD** and look for the "Secret Variables" section.
-As **Key** add the name `SSH_PRIVATE_KEY` and in the **Value** field paste the
-content of your _private_ key that you created earlier.
-
-It is also good practice to check the server's own public key to make sure you
-are not being targeted by a man-in-the-middle attack. To do this, add another
-variable named `SSH_SERVER_HOSTKEYS`. To find out the hostkeys of your server, run
-the `ssh-keyscan YOUR_SERVER` command from a trusted network (ideally, from the
-server itself), and paste its output into the `SSH_SERVER_HOSTKEYS` variable. If
-you need to connect to multiple servers, concatenate all the server public keys
-that you collected into the **Value** of the variable. There must be one key per
-line.
-
-Next you need to modify your `.gitlab-ci.yml` with a `before_script` action.
-Add it to the top:
-
-```
-before_script:
- # Install ssh-agent if not already installed, it is required by Docker.
- # (change apt-get to yum if you use a CentOS-based image)
- - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
-
- # Run ssh-agent (inside the build environment)
- - eval $(ssh-agent -s)
-
- # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
- - ssh-add <(echo "$SSH_PRIVATE_KEY")
-
- # For Docker builds disable host key checking. Be aware that by adding that
- # you are suspectible to man-in-the-middle attacks.
- # WARNING: Use this only with the Docker executor, if you use it with shell
- # you will overwrite your user's SSH config.
- - mkdir -p ~/.ssh
- - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
- # In order to properly check the server's host key, assuming you created the
- # SSH_SERVER_HOSTKEYS variable previously, uncomment the following two lines
- # instead.
- # - mkdir -p ~/.ssh
- # - '[[ -f /.dockerenv ]] && echo "$SSH_SERVER_HOSTKEYS" > ~/.ssh/known_hosts'
-```
-
-As a final step, add the _public_ key from the one you created earlier to the
-services that you want to have an access to from within the build environment.
-If you are accessing a private GitLab repository you need to add it as a
-[deploy key](../../ssh/README.md#deploy-keys).
+When your CI/CD jobs run inside Docker containers (meaning the environment is
+contained) and you want to deploy your code in a private server, you need a way
+to access it. This is where an SSH key pair comes in handy.
+
+1. You will first need to create an SSH key pair. For more information, follow
+ the instructions to [generate an SSH key](../../ssh/README.md#generating-a-new-ssh-key-pair).
+ **Do not** add a passphrase to the SSH key, or the `before_script` will\
+ prompt for it.
+
+1. Create a new [secret variable](../variables/README.md#secret-variables).
+ As **Key** enter the name `SSH_PRIVATE_KEY` and in the **Value** field paste
+ the content of your _private_ key that you created earlier.
+
+1. Modify your `.gitlab-ci.yml` with a `before_script` action. In the following
+ example, a Debian based image is assumed. Edit to your needs:
+
+ ```yaml
+ before_script:
+ ##
+ ## Install ssh-agent if not already installed, it is required by Docker.
+ ## (change apt-get to yum if you use an RPM-based image)
+ ##
+ - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
+
+ ##
+ ## Run ssh-agent (inside the build environment)
+ ##
+ - eval $(ssh-agent -s)
+
+ ##
+ ## Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
+ ## We're using tr to fix line endings which makes ed25519 keys work
+ ## without extra base64 encoding.
+ ## https://gitlab.com/gitlab-examples/ssh-private-key/issues/1#note_48526556
+ ##
+ - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
+
+ ##
+ ## Create the SSH directory and give it the right permissions
+ ##
+ - mkdir -p ~/.ssh
+ - chmod 700 ~/.ssh
+
+ ##
+ ## Optionally, if you will be using any Git commands, set the user name and
+ ## and email.
+ ##
+ #- git config --global user.email "user@example.com"
+ #- git config --global user.name "User name"
+ ```
+
+ NOTE: **Note:**
+ The [`before_script`](../yaml/README.md#before-script) can be set globally
+ or per-job.
+
+1. Make sure the private server's [SSH host keys are verified](#verifying-the-ssh-host-keys).
+
+1. As a final step, add the _public_ key from the one you created in the first
+ step to the services that you want to have an access to from within the build
+ environment. If you are accessing a private GitLab repository you need to add
+ it as a [deploy key](../../ssh/README.md#deploy-keys).
That's it! You can now have access to private servers or repositories in your
build environment.
@@ -91,24 +113,93 @@ SSH key.
You can generate the SSH key from the machine that GitLab Runner is installed
on, and use that key for all projects that are run on this machine.
-First, you need to login to the server that runs your jobs.
+1. First, you need to login to the server that runs your jobs.
+
+1. Then from the terminal login as the `gitlab-runner` user:
-Then from the terminal login as the `gitlab-runner` user and generate the SSH
-key pair as described in the [SSH keys documentation](../../ssh/README.md).
+ ```
+ sudo su - gitlab-runner
+ ```
-As a final step, add the _public_ key from the one you created earlier to the
-services that you want to have an access to from within the build environment.
-If you are accessing a private GitLab repository you need to add it as a
-[deploy key](../../ssh/README.md#deploy-keys).
+1. Generate the SSH key pair as described in the instructions to
+ [generate an SSH key](../../ssh/README.md#generating-a-new-ssh-key-pair).
+ **Do not** add a passphrase to the SSH key, or the `before_script` will
+ prompt for it.
+
+1. As a final step, add the _public_ key from the one you created earlier to the
+ services that you want to have an access to from within the build environment.
+ If you are accessing a private GitLab repository you need to add it as a
+ [deploy key](../../ssh/README.md#deploy-keys).
Once done, try to login to the remote server in order to accept the fingerprint:
```bash
-ssh <address-of-my-server>
+ssh example.com
+```
+
+For accessing repositories on GitLab.com, you would use `git@gitlab.com`.
+
+## Verifying the SSH host keys
+
+It is a good practice to check the private server's own public key to make sure
+you are not being targeted by a man-in-the-middle attack. In case anything
+suspicious happens, you will notice it since the job would fail (the SSH
+connection would fail if the public keys would not match).
+
+To find out the host keys of your server, run the `ssh-keyscan` command from a
+trusted network (ideally, from the private server itself):
+
+```sh
+## Use the domain name
+ssh-keyscan example.com
+
+## Or use an IP
+ssh-keyscan 1.2.3.4
```
-For accessing repositories on GitLab.com, the `<address-of-my-server>` would be
-`git@gitlab.com`.
+Create a new [secret variable](../variables/README.md#secret-variables) with
+`SSH_KNOWN_HOSTS` as "Key", and as a "Value" add the output of `ssh-keyscan`.
+
+NOTE: **Note:**
+If you need to connect to multiple servers, all the server host keys
+need to be collected in the **Value** of the variable, one key per line.
+
+TIP: **Tip:**
+By using a secret variable instead of `ssh-keyscan` directly inside
+`.gitlab-ci.yml`, it has the benefit that you don't have to change `.gitlab-ci.yml`
+if the host domain name changes for some reason. Also, the values are predefined
+by you, meaning that if the host keys suddenly change, the CI/CD job will fail,
+and you'll know there's something wrong with the server or the network.
+
+Now that the `SSH_KNOWN_HOSTS` variable is created, in addition to the
+[content of `.gitlab-ci.yml`](#ssh-keys-when-using-the-docker-executor)
+above, here's what more you need to add:
+
+ ```yaml
+before_script:
+ ##
+ ## Assuming you created the SSH_KNOWN_HOSTS variable, uncomment the
+ ## following two lines.
+ ##
+ - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts'
+ - chmod 644 ~/.ssh/known_hosts
+
+ ##
+ ## Alternatively, use ssh-keyscan to scan the keys of your private server.
+ ## Replace example.com with your private server's domain name. Repeat that
+ ## command if you have more than one server to connect to.
+ ##
+ #- ssh-keyscan example.com >> ~/.ssh/known_hosts
+ #- chmod 644 ~/.ssh/known_hosts
+
+ ##
+ ## You can optionally disable host key checking. Be aware that by adding that
+ ## you are suspectible to man-in-the-middle attacks.
+ ## WARNING: Use this only with the Docker executor, if you use it with shell
+ ## you will overwrite your user's SSH config.
+ ##
+ #- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
+```
## Example project
@@ -119,6 +210,4 @@ that runs on [GitLab.com](https://gitlab.com) using our publicly available
Want to hack on it? Simply fork it, commit and push your changes. Within a few
moments the changes will be picked by a public runner and the job will begin.
-[ssh-keygen]: http://linux.die.net/man/1/ssh-keygen
-[ssh-agent]: http://linux.die.net/man/1/ssh-agent
[ssh-example-repo]: https://gitlab.com/gitlab-examples/ssh-private-key/
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index a9e6bda9916..b9d4a2098ed 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -213,14 +213,15 @@ An example project service that defines deployment variables is
## Debug tracing
> Introduced in GitLab Runner 1.7.
->
-> **WARNING:** Enabling debug tracing can have severe security implications. The
- output **will** contain the content of all your secret variables and any other
- secrets! The output **will** be uploaded to the GitLab server and made visible
- in job traces!
+
+CAUTION: **Warning:**
+Enabling debug tracing can have severe security implications. The
+output **will** contain the content of all your secret variables and any other
+secrets! The output **will** be uploaded to the GitLab server and made visible
+in job traces!
By default, GitLab Runner hides most of the details of what it is doing when
-processing a job. This behaviour keeps job traces short, and prevents secrets
+processing a job. This behavior keeps job traces short, and prevents secrets
from being leaked into the trace unless your script writes them to the screen.
If a job isn't working as expected, this can make the problem difficult to
diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md
index 31164ccd465..d14ba6ad522 100644
--- a/doc/customization/issue_closing.md
+++ b/doc/customization/issue_closing.md
@@ -1,3 +1,7 @@
+---
+comments: false
+---
+
This document was split into:
- [administration/issue_closing_pattern.md](../administration/issue_closing_pattern.md).
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
index a0cb234bea0..0aef0bf5abb 100644
--- a/doc/customization/welcome_message.md
+++ b/doc/customization/welcome_message.md
@@ -8,5 +8,5 @@ It is possible to add a markdown-formatted welcome message to your GitLab
sign-in page. Users of GitLab Enterprise Edition should use the [branded login
page feature](branded_login_page.md) instead.
-The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI.
+The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI.
Admin area > Settings
diff --git a/doc/development/fe_guide/dropdowns.md b/doc/development/fe_guide/dropdowns.md
index e1660ac5caa..6314f8f38d2 100644
--- a/doc/development/fe_guide/dropdowns.md
+++ b/doc/development/fe_guide/dropdowns.md
@@ -4,15 +4,15 @@
## How to style a bootstrap dropdown
1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
1. Add a specific class to the top level `.dropdown` element
-
-
+
+
```Haml
.dropdown.my-dropdown
%button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
%span.dropdown-toggle-text
Toggle Dropdown
= icon('chevron-down')
-
+
%ul.dropdown-menu
%li
%a
@@ -29,10 +29,4 @@
item!
```
-1. Include the mixin in CSS
-
- ```SCSS
- @include new-style-dropdown('.my-dropdown ');
- ```
-
[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index 085fb8f902c..59ebf41e09f 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -9,25 +9,54 @@ All workers should include `ApplicationWorker` instead of `Sidekiq::Worker`,
which adds some convenience methods and automatically sets the queue based on
the worker's name.
-## Default Queue
+## Dedicated Queues
-Use of the "default" queue is not allowed. Every worker should use a queue that
-matches the worker's purpose the closest. For example, workers that are to be
-executed periodically should use the "cronjob" queue.
+All workers should use their own queue, which is automatically set based on the
+worker class name. For a worker named `ProcessSomethingWorker`, the queue name
+would be `process_something`. If you're not sure what queue a worker uses,
+you can find it using `SomeWorker.queue`. There is almost never a reason to
+manually override the queue name using `sidekiq_options queue: :some_queue`.
-A list of all available queues can be found in `config/sidekiq_queues.yml`.
+## Queue Namespaces
-## Dedicated Queues
+While different workers cannot share a queue, they can share a queue namespace.
-Most workers should use their own queue, which is automatically set based on the
-worker class name. For a worker named `ProcessSomethingWorker`, the queue name
-would be `process_something`. If you're not sure what a worker's queue name is,
-you can find it using `SomeWorker.queue`.
+Defining a queue namespace for a worker makes it possible to start a Sidekiq
+process that automatically handles jobs for all workers in that namespace,
+without needing to explicitly list all their queue names. If, for example, all
+workers that are managed by sidekiq-cron use the `cronjob` queue namespace, we
+can spin up a Sidekiq process specifically for these kinds of scheduled jobs.
+If a new worker using the `cronjob` namespace is added later on, the Sidekiq
+process will automatically pick up jobs for that worker too (after having been
+restarted), without the need to change any configuration.
+
+A queue namespace can be set using the `queue_namespace` DSL class method:
+
+```ruby
+class SomeScheduledTaskWorker
+ include ApplicationWorker
+
+ queue_namespace :cronjob
+
+ # ...
+end
+```
+
+Behind the scenes, this will set `SomeScheduledTaskWorker.queue` to
+`cronjob:some_scheduled_task`. Commonly used namespaces will have their own
+concern module that can easily be included into the worker class, and that may
+set other Sidekiq options besides the queue namespace. `CronjobQueue`, for
+example, sets the namespace, but also disables retries.
+
+`bundle exec sidekiq` is namespace-aware, and will automatically listen on all
+queues in a namespace (technically: all queues prefixed with the namespace name)
+when a namespace is provided instead of a simple queue name in the `--queue`
+(`-q`) option, or in the `:queues:` section in `config/sidekiq_queues.yml`.
-In some cases multiple workers do use the same queue. For example, the various
-workers for updating CI pipelines all use the `pipeline` queue. Adding workers
-to existing queues should be done with care, as adding more workers can lead to
-slow jobs blocking work (even for different jobs) on the shared queue.
+Note that adding a worker to an existing namespace should be done with care, as
+the extra jobs will take resources away from jobs from workers that were already
+there, if the resources available to the Sidekiq process handling the namespace
+are not adjusted appropriately.
## Tests
@@ -36,7 +65,7 @@ tests should be placed in `spec/workers`.
## Removing or renaming queues
-Try to avoid renaming or removing queues in minor and patch releases.
+Try to avoid renaming or removing workers and their queues in minor and patch releases.
During online update instance can have pending jobs and removing the queue can
lead to those jobs being stuck forever. If you can't write migration for those
Sidekiq jobs, please consider doing rename or remove queue in major release only.
diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md
index 16a811dbc74..d396964e7c1 100644
--- a/doc/development/ux_guide/components.md
+++ b/doc/development/ux_guide/components.md
@@ -10,7 +10,7 @@
* [Tables](#tables)
* [Blocks](#blocks)
* [Panels](#panels)
-* [Dialog modals](#dialog-modals)
+* [Modals](#modals)
* [Alerts](#alerts)
* [Forms](#forms)
* [Search box](#search-box)
@@ -255,18 +255,18 @@ Skeleton loading can replace any existing UI elements for the period in which th
---
-## Dialog modals
+## Modals
-Dialog modals are only used for having a conversation and confirmation with the user. The user is not able to access the features on the main page until closing the modal.
+Modals are only used for having a conversation and confirmation with the user. The user is not able to access the features on the main page until closing the modal.
### Usage
-* When the action is irreversible, dialog modals provide the details and confirm with the user before they take an advanced action.
-* When the action will affect privacy or authorization, dialog modals provide advanced information and confirm with the user.
+* When the action is irreversible, modals provide the details and confirm with the user before they take an advanced action.
+* When the action will affect privacy or authorization, modals provide advanced information and confirm with the user.
### Style
-* Dialog modals contain the header, body, and actions.
+* Modals contain the header, body, and actions.
* **Header(1):** The header title is a question instead of a descriptive phrase.
* **Body(2):** The content in body should never be ambiguous and unclear. It provides specific information.
* **Actions(3):** Contains a affirmative action, a dismissive action, and an extra action. The order of actions from left to right: Dismissive action → Extra action → Affirmative action
@@ -277,13 +277,13 @@ Dialog modals are only used for having a conversation and confirmation with the
### Placement
-* Dialog modals should always be the center of the screen horizontally and be positioned **72px** from the top.
+* Modals should always be the center of the screen horizontally and be positioned **72px** from the top.
-| Dialog with 2 actions | Dialog with 3 actions | Special confirmation |
+| Modal with 2 actions | Modal with 3 actions | Special confirmation |
| --------------------- | --------------------- | -------------------- |
| ![two-actions](img/modals-general-confimation-dialog.png) | ![three-actions](img/modals-three-buttons.png) | ![spcial-confirmation](img/modals-special-confimation-dialog.png) |
-> TODO: Special case for dialog modal.
+> TODO: Special case for modal.
---
diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md
index 12e8d0a31bb..af842da7f62 100644
--- a/doc/development/ux_guide/copy.md
+++ b/doc/development/ux_guide/copy.md
@@ -46,11 +46,11 @@ Avoid using periods in solitary sentences in these elements:
* Labels
* Hover text
* Bulleted lists
-* Dialog body text
+* Modal body text
Periods should be used for:
-* Lists or dialogs with multiple sentences
+* Lists or modals with multiple sentences
* Any sentence followed by a link
| :white_check_mark: **Do** place periods after sentences followed by a link | :no_entry_sign: **Don’t** place periods after a link if it‘s not followed by a sentence |
@@ -80,7 +80,7 @@ Omit punctuation after phrases and labels to create a cleaner and more readable
| Punctuation mark | Copy and paste | HTML entity | Unicode | Mac shortcut | Windows shortcut | Description |
|---|---|---|---|---|---|---|
-| Period | **.** | | | | | Omit for single sentences in affordances like labels, hover text, bulleted lists, and dialog body text.<br><br>Use in lists or dialogs with multiple sentences, and any sentence followed by a link or inline code.<br><br>Place inside quotation marks unless you’re telling the reader what to enter and it’s ambiguous whether to include the period. |
+| Period | **.** | | | | | Omit for single sentences in affordances like labels, hover text, bulleted lists, and modal body text.<br><br>Use in lists or modals with multiple sentences, and any sentence followed by a link or inline code.<br><br>Place inside quotation marks unless you’re telling the reader what to enter and it’s ambiguous whether to include the period. |
| Comma | **,** | | | | | Place inside quotation marks.<br><br>Use a [serial comma][serial comma] in lists of three or more terms. |
| Exclamation point | **!** | | | | | Avoid exclamation points as they tend to come across as shouting. Some exceptions include greetings or congratulatory messages. |
| Colon | **:** | `&#58;` | `\u003A` | | | Omit from labels, for example, in the labels for fields in a form. |
@@ -88,7 +88,7 @@ Omit punctuation after phrases and labels to create a cleaner and more readable
| Quotation marks | **“**<br><br>**”**<br><br>**‘**<br><br>**’** | `&ldquo;`<br><br>`&rdquo;`<br><br>`&lsquo;`<br><br>`&rsquo;` | `\u201C`<br><br>`\u201D`<br><br>`\u2018`<br><br>`\u2019` | <kbd>⌥ Option</kbd>+<kbd>[</kbd><br><br><kbd>⌥ Option</kbd>+<kbd>⇧ Shift</kbd>+<kbd>[</kbd><br><br><kbd>⌥ Option</kbd>+<kbd>]</kbd><br><br><kbd>⌥ Option</kbd>+<kbd>⇧ Shift</kbd>+<kbd>]</kbd> | <kbd>Alt</kbd>+<kbd>0 1 4 7</kbd><br><br><kbd>Alt</kbd>+<kbd>0 1 4 8</kbd><br><br><kbd>Alt</kbd>+<kbd>0 1 4 5</kbd><br><br><kbd>Alt</kbd>+<kbd>0 1 4 6</kbd> | Use proper quotation marks (also known as smart quotes, curly quotes, or typographer’s quotes) for quotes. Single quotation marks are used for quotes inside of quotes.<br><br>The right single quotation mark symbol is also used for apostrophes.<br><br>Don’t use primes, straight quotes, or free-standing accents for quotation marks. |
| Primes | **′**<br><br>**″** | `&prime;`<br><br>`&Prime;` | `\u2032`<br><br>`\u2033` | | <kbd>Alt</kbd>+<kbd>8 2 4 2</kbd><br><br><kbd>Alt</kbd>+<kbd>8 2 4 3</kbd> | Use prime (′) only in abbreviations for feet, arcminutes, and minutes: 3° 15′<br><br>Use double-prime (″) only in abbreviations for inches, arcseconds, and seconds: 3° 15′ 35″<br><br>Don’t use quotation marks, straight quotes, or free-standing accents for primes. |
| Straight quotes and accents | **"**<br><br>**'**<br><br>**`**<br><br>**´** | `&quot;`<br><br>`&#39;`<br><br>`&#96;`<br><br>`&acute;` | `\u0022`<br><br>`\u0027`<br><br>`\u0060`<br><br>`\u00B4` | | | Don’t use straight quotes or free-standing accents for primes or quotation marks.<br><br>Proper typography never uses straight quotes. They are left over from the age of typewriters and their only modern use is for code. |
-| Ellipsis | **…** | `&hellip;` | | <kbd>⌥ Option</kbd>+<kbd>;</kbd> | <kbd>Alt</kbd>+<kbd>0 1 3 3</kbd> | Use to indicate an action in progress (“Downloading…”) or incomplete or truncated text. No space before the ellipsis.<br><br>Omit from menu items or buttons that open a dialog or start some other process. |
+| Ellipsis | **…** | `&hellip;` | | <kbd>⌥ Option</kbd>+<kbd>;</kbd> | <kbd>Alt</kbd>+<kbd>0 1 3 3</kbd> | Use to indicate an action in progress (“Downloading…”) or incomplete or truncated text. No space before the ellipsis.<br><br>Omit from menu items or buttons that open a modal or start some other process. |
| Chevrons | **«**<br><br>**»**<br><br>**‹**<br><br>**›**<br><br>**<**<br><br>**>** | `&#171;`<br><br>`&#187;`<br><br>`&#8249;`<br><br>`&#8250;`<br><br>`&lt;`<br><br>`&gt;` | `\u00AB`<br><br>`\u00BB`<br><br>`\u2039`<br><br>`\u203A`<br><br>`\u003C`<br><br>`\u003E`<br><br> | | | Omit from links or buttons that open another page or move to the next or previous step in a process. Also known as angle brackets, angular quote brackets, or guillemets. |
| Em dash | **—** | `&mdash;` | `\u2014` | <kbd>⌥ Option</kbd>+<kbd>⇧ Shift</kbd>+<kbd>-</kbd> | <kbd>Alt</kbd>+<kbd>0 1 5 1</kbd> | Avoid using dashes to separate text. If you must use dashes for this purpose — like this — use an em dash surrounded by spaces. |
| En dash | **–** | `&ndash;` | `\u2013` | <kbd>⌥ Option</kbd>+<kbd>-</kbd> | <kbd>Alt</kbd>+<kbd>0 1 5 0</kbd> | Use an en dash without spaces instead of a hyphen to indicate a range of values, such as numbers, times, and dates: “3–5 kg”, “8:00 AM–12:30 PM”, “10–17 Jan” |
@@ -175,7 +175,7 @@ A **comment** is a written piece of text that users of GitLab can create. Commen
#### Discussion
A **discussion** is a group of 1 or more comments. A discussion can include subdiscussions. Some discussions have the special capability of being able to be **resolved**. Both the comments in the discussion and the discussion itself can be resolved.
-## Confirmation dialogs
+## Modals
- Destruction buttons should be clear and always say what they are destroying.
E.g., `Delete page` instead of just `Delete`.
@@ -184,6 +184,8 @@ A **discussion** is a group of 1 or more comments. A discussion can include subd
- Avoid the word `cancel` or `canceled` in the descriptive copy. It can be
confusing when you then see the `Cancel` button.
+see also: guidelines for [modal components](components.md#modals)
+
---
Portions of this page are modifications based on work created and shared by the [Android Open Source Project][android project] and used according to terms described in the [Creative Commons 2.5 Attribution License][creative commons].
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 570b0d5b22f..6c6e5db4cd9 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -299,9 +299,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-2-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 10-3-stable gitlab
-**Note:** You can change `10-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `10-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md
index df56f031970..588f4fa369f 100644
--- a/doc/topics/git/index.md
+++ b/doc/topics/git/index.md
@@ -61,6 +61,10 @@ We've gathered some resources to help you to get the best from Git with GitLab.
- [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/)
- [Towards a production quality open source Git LFS server](https://about.gitlab.com/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
+## Troubleshooting
+
+- Learn a few [Git troubleshooting](troubleshooting_git.md) techniques to help you out.
+
## General information
- **Articles:**
diff --git a/doc/topics/git/troubleshooting_git.md b/doc/topics/git/troubleshooting_git.md
new file mode 100644
index 00000000000..8555c5e91ea
--- /dev/null
+++ b/doc/topics/git/troubleshooting_git.md
@@ -0,0 +1,82 @@
+# Troubleshooting Git
+
+Sometimes things don't work the way they should or as you might expect when
+you're using Git. Here are some tips on troubleshooting and resolving issues
+with Git.
+
+## Broken pipe errors on git push
+
+'Broken pipe' errors can occur when attempting to push to a remote repository.
+When pushing you will usually see:
+
+```
+Write failed: Broken pipe
+fatal: The remote end hung up unexpectedly
+```
+
+To fix this issue, here are some possible solutions.
+
+### Increase the POST buffer size in Git
+
+**If pushing over HTTP**, you can try increasing the POST buffer size in Git's
+configuration. Open a terminal and enter:
+
+```sh
+git config http.postBuffer 52428800
+```
+
+The value is specified in bytes, so in the above case the buffer size has been
+set to 50MB. The default is 1MB.
+
+### Check your SSH configuration
+
+**If pushing over SSH**, first check your SSH configuration as 'Broken pipe'
+errors can sometimes be caused by underlying issues with SSH (such as
+authentication). Make sure that SSH is correctly configured by following the
+instructions in the [SSH troubleshooting] docs.
+
+There's another option where you can prevent session timeouts by configuring
+SSH 'keep alive' either on the client or on the server (if you are a GitLab
+admin and have access to the server).
+
+NOTE: **Note:** configuring *both* the client and the server is unnecessary.
+
+**To configure SSH on the client side**:
+
+- On UNIX, edit `~/.ssh/config` (create the file if it doesn’t exist) and
+ add or edit:
+
+ ```
+ Host your-gitlab-instance-url.com
+ ServerAliveInterval 60
+ ServerAliveCountMax 5
+ ```
+
+- On Windows, if you are using PuTTY, go to your session properties, then
+ navigate to "Connection" and under "Sending of null packets to keep
+ session active", set "Seconds between keepalives (0 to turn off)" to `60`.
+
+**To configure SSH on the server side**, edit `/etc/ssh/sshd_config` and add:
+
+```
+ClientAliveInterval 60
+ClientAliveCountMax 5
+```
+
+### Running a git repack
+
+**If 'pack-objects' type errors are also being displayed**, you can try to
+run a `git repack` before attempting to push to the remote repository again:
+
+```sh
+git repack
+git push
+```
+
+### Upgrade your Git client
+
+In case you're running an older version of Git (< 2.9), consider upgrading
+to >= 2.9 (see [Broken pipe when pushing to Git repository][Broken-Pipe]).
+
+[SSH troubleshooting]: ../../ssh/README.md#troubleshooting "SSH Troubleshooting"
+[Broken-Pipe]: https://stackoverflow.com/questions/19120120/broken-pipe-when-pushing-to-git-repository/36971469#36971469 "StackOverflow: 'Broken pipe when pushing to Git repository'"
diff --git a/doc/update/10.2-to-10.3.md b/doc/update/10.2-to-10.3.md
new file mode 100644
index 00000000000..07f9ee965f0
--- /dev/null
+++ b/doc/update/10.2-to-10.3.md
@@ -0,0 +1,360 @@
+---
+comments: false
+---
+
+# From 10.2 to 10.3
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: GitLab 9.0 and higher only support Ruby 2.3.x and dropped support for Ruby 2.1.x. Be
+sure to upgrade your interpreter if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download and compile Ruby:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.5.tar.gz
+echo '3247e217d6745c27ef23bdc77b6abdb4b57a118f ruby-2.3.5.tar.gz' | shasum -c - && tar xzf ruby-2.3.5.tar.gz
+cd ruby-2.3.5
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Update Node
+
+GitLab now runs [webpack](http://webpack.js.org) to compile frontend assets and
+it has a minimum requirement of node v4.3.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v4.3.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+
+Since 8.17, GitLab requires the use of yarn `>= v0.17.0` to manage
+JavaScript dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 5. Update Go
+
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
+echo '1862f4c3d3907e59b04a757cfda0ea7aa9ef39274af99a784f5be843c80c6772 go1.8.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.8.3.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.8.3.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-3-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 10-3-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### New Gitaly configuration options required
+
+In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell`.
+
+```shell
+echo '
+[gitaly-ruby]
+dir = "/home/git/gitaly/ruby"
+
+[gitlab-shell]
+dir = "/home/git/gitlab-shell"
+' | sudo -u git tee -a /home/git/gitaly/config.toml
+```
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 10. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 11. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-2-stable:config/gitlab.yml.example origin/10-3-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/10-2-stable:lib/support/nginx/gitlab-ssl origin/10-3-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/10-2-stable:lib/support/nginx/gitlab origin/10-3-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-3-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-3-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/10-2-stable:lib/support/init.d/gitlab.default.example origin/10-3-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 12. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+```
+
+**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
+
+### 13. Start application
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 14. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (10.0)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 9.5 to 10.0](9.5-to-10.0.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-3-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/10-3-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index a1671f9dd91..1733017cbc0 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -197,11 +197,11 @@ username, you can create a new group and transfer projects to it.
Changing a group's path can have unintended side effects.
* Existing web URLs for the group and anything under it (i.e. projects) will
-redirect to the new URLs
-* Existing Git remote URLs for projects under the group will no longer work, but
-Git responses will show an error with the new remote URL
-* The original namespace can be claimed again by any group or user, which will
-destroy web redirects and Git remote warnings
+redirect to the new URLs.
+* Existing Git remote URLs for projects under the group will redirect to the new remote URL, and they
+will show a warning with the new remote URL.
+* The redirect to the new URL is permanent, that implies the original namespace
+can't be claimed again by any group or user.
* If you are vacating the path so it can be claimed by another group or user,
you may need to rename the group name as well since both names and paths must be
unique
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 04e615330ce..dae4cbe170b 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -45,11 +45,10 @@ Alternatively, you can follow [this detailed procedure from the GitLab Team Hand
Changing your username can have unintended side effects.
* Existing web URLs for the user and anything under it (i.e. projects) will
-redirect to the new URLs
-* Existing Git remote URLs for projects under the user will no longer work, but
-Git responses will show an error with the new remote URL
-* The original namespace can be claimed again by any group or user, which will
-destroy any web redirects and Git remote warnings
+redirect to the new URLs.
+* Existing Git remote URLs for projects under the user will redirect to the new remote URL. Git responses
+will show a warning with the new remote URL.
+* The redirect to the new URL is permanent, that implies the original namespace can't be claimed again by any group or user.
> It is currently not possible to rename a namespace if it contains a
project with container registry tags, because the project cannot be moved.
diff --git a/doc/user/project/merge_requests/img/create_from_email.png b/doc/user/project/merge_requests/img/create_from_email.png
new file mode 100644
index 00000000000..71eb4bf267d
--- /dev/null
+++ b/doc/user/project/merge_requests/img/create_from_email.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index b5c3f74a113..7037d7f5989 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -27,7 +27,7 @@ With GitLab merge requests, you can:
- [Resolve merge conflicts from the UI](#resolve-conflicts)
- Enable [fast-forward merge requests](#fast-forward-merge-requests)
- Enable [semi-linear history merge requests](#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch
-- [Create new merge requests by email](#create_by_email)
+- [Create new merge requests by email](#create-new-merge-requests-by-email)
With **[GitLab Enterprise Edition][ee]**, you can also:
@@ -139,7 +139,12 @@ address. The address can be obtained on the merge requests page by clicking on
a **Email a new merge request to this project** button. The subject will be
used as the source branch name for the new merge request and the target branch
will be the default branch for the project. The message body (if not empty)
-will be used as the merge request description.
+will be used as the merge request description. You need
+["Reply by email"](../../../administration/reply_by_email.md) enabled to use
+this feature. If it's not enabled to your instance, you may ask your GitLab
+administrator to do so.
+
+![Create new merge requests by email](img/create_from_email.png)
## Revert changes
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index daa5463d680..43451844f2d 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -68,7 +68,7 @@ in the pipelines settings page.
Access to pipelines and job details (including output of logs and artifacts)
is checked against your current user access level and the **Public pipelines**
-project setting.
+project setting under your project's **Settings > CI/CD > General pipelines settings**.
If **Public pipelines** is enabled (default):
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index a234a647b77..2b6fde1e2a5 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -50,3 +50,9 @@ Here you can run housekeeping, archive, rename, transfer, or remove a project.
It's possible to mark a project as archived via the Project Settings. An archived project will be hidden by default in the project listings.
An archived project can be fully restored and will therefore retain it's repository and all associated resources whilst in an archived state.
+
+#### Renaming a project
+
+>**Note:** Only Project Owners and Admin users have the permission to rename a project
+
+It's possible to rename a project from "Rename repository" or "Transfer project" sections. When doing so, you will need to update your local repositories to point to the new location, otherwise Git operations will be rejected.
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 7887b886c03..4f36bbd760f 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -110,10 +110,12 @@ module API
end
params do
use :pagination
+ optional :order_by, type: String, values: %w[email name commits], default: nil, desc: 'Return contributors ordered by `name` or `email` or `commits`'
+ optional :sort, type: String, values: %w[asc desc], default: nil, desc: 'Sort by asc (ascending) or desc (descending)'
end
get ':id/repository/contributors' do
begin
- contributors = ::Kaminari.paginate_array(user_project.repository.contributors)
+ contributors = ::Kaminari.paginate_array(user_project.repository.contributors(order_by: params[:order_by], sort: params[:sort]))
present paginate(contributors), with: Entities::Contributor
rescue
not_found!
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 0d394a7b441..5e0afc6a7e4 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -14,10 +14,15 @@ module API
success Entities::Tag
end
params do
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return tags sorted in updated by `asc` or `desc` order.'
+ optional :order_by, type: String, values: %w[name updated], default: 'updated',
+ desc: 'Return tags ordered by `name` or `updated` fields.'
use :pagination
end
get ':id/repository/tags' do
- tags = ::Kaminari.paginate_array(user_project.repository.tags.sort_by(&:name).reverse)
+ tags = ::Kaminari.paginate_array(::TagsFinder.new(user_project.repository, sort: "#{params[:order_by]}_#{params[:sort]}").execute)
+
present paginate(tags), with: Entities::Tag, project: user_project
end
diff --git a/lib/feature.rb b/lib/feature.rb
index ac3bc65c0d5..8e9ba5c530a 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -1,5 +1,3 @@
-require 'flipper/adapters/active_record'
-
class Feature
# Classes to override flipper table names
class FlipperFeature < Flipper::Adapters::ActiveRecord::Feature
@@ -62,12 +60,7 @@ class Feature
end
def flipper
- @flipper ||= begin
- adapter = Flipper::Adapters::ActiveRecord.new(
- feature_class: FlipperFeature, gate_class: FlipperGate)
-
- Flipper.new(adapter)
- end
+ @flipper ||= Flipper.instance
end
# This method is called from config/initializers/flipper.rb and can be used
diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb
index fb28e80ff73..b9099ce256a 100644
--- a/lib/gitlab/conflict/file_collection.rb
+++ b/lib/gitlab/conflict/file_collection.rb
@@ -19,6 +19,8 @@ module Gitlab
commit_message: commit_message || default_commit_message
}
resolver.resolve_conflicts(user, files, args)
+ ensure
+ @merge_request.clear_memoized_shas
end
def files
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index fbdd646ea13..0e0a1987c7d 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1073,12 +1073,17 @@ module Gitlab
end
end
- def write_ref(ref_path, ref)
+ def write_ref(ref_path, ref, force: false)
raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
- input = "update #{ref_path}\x00#{ref}\x00\x00"
- run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) }
+ ref = "refs/heads/#{ref}" unless ref.start_with?("refs") || ref =~ /\A[a-f0-9]+\z/i
+
+ rugged.references.create(ref_path, ref, force: force)
+ rescue Rugged::ReferenceError => ex
+ raise GitError, "could not create ref #{ref_path}: #{ex}"
+ rescue Rugged::OSError => ex
+ raise GitError, "could not create ref #{ref_path}: #{ex}"
end
def fetch_ref(source_repository, source_ref:, target_ref:)
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index 65d55576ac2..9112164f22e 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -1,7 +1,11 @@
+# rubocop:disable Style/ClassVars
+
module Gitlab
module Metrics
# Class for tracking timing information about method calls
class MethodCall
+ @@measurement_enabled_cache = Concurrent::AtomicBoolean.new(false)
+ @@measurement_enabled_cache_expires_at = Concurrent::AtomicFixnum.new(Time.now.to_i)
MUTEX = Mutex.new
BASE_LABELS = { module: nil, method: nil }.freeze
attr_reader :real_time, :cpu_time, :call_count, :labels
@@ -18,6 +22,10 @@ module Gitlab
end
end
+ def self.measurement_enabled_cache_expires_at
+ @@measurement_enabled_cache_expires_at
+ end
+
# name - The full name of the method (including namespace) such as
# `User#sign_in`.
#
@@ -72,7 +80,14 @@ module Gitlab
end
def call_measurement_enabled?
- Feature.get(:prometheus_metrics_method_instrumentation).enabled?
+ expires_at = @@measurement_enabled_cache_expires_at.value
+ if expires_at < Time.now.to_i
+ if @@measurement_enabled_cache_expires_at.compare_and_set(expires_at, 1.minute.from_now.to_i)
+ @@measurement_enabled_cache.value = Feature.get(:prometheus_metrics_method_instrumentation).enabled?
+ end
+ end
+
+ @@measurement_enabled_cache.value
end
end
end
diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb
index dc9886732b5..c3d7814551c 100644
--- a/lib/gitlab/sidekiq_config.rb
+++ b/lib/gitlab/sidekiq_config.rb
@@ -1,16 +1,35 @@
require 'yaml'
+require 'set'
module Gitlab
module SidekiqConfig
- def self.redis_queues
- @redis_queues ||= Sidekiq::Queue.all.map(&:name)
+ # This method is called by `bin/sidekiq-cluster` in EE, which runs outside
+ # of bundler/Rails context, so we cannot use any gem or Rails methods.
+ def self.worker_queues(rails_path = Rails.root.to_s)
+ @worker_queues ||= {}
+ @worker_queues[rails_path] ||= YAML.load_file(File.join(rails_path, 'app/workers/all_queues.yml'))
end
# This method is called by `bin/sidekiq-cluster` in EE, which runs outside
# of bundler/Rails context, so we cannot use any gem or Rails methods.
- def self.config_queues(rails_path = Rails.root.to_s)
+ def self.expand_queues(queues, all_queues = self.worker_queues)
+ return [] if queues.empty?
+
+ queues_set = all_queues.to_set
+
+ queues.flat_map do |queue|
+ [queue, *queues_set.grep(/\A#{queue}:/)]
+ end
+ end
+
+ def self.redis_queues
+ # Not memoized, because this can change during the life of the application
+ Sidekiq::Queue.all.map(&:name)
+ end
+
+ def self.config_queues
@config_queues ||= begin
- config = YAML.load_file(File.join(rails_path, 'config', 'sidekiq_queues.yml'))
+ config = YAML.load_file(Rails.root.join('config/sidekiq_queues.yml'))
config[:queues].map(&:first)
end
end
@@ -23,14 +42,6 @@ module Gitlab
@workers ||= find_workers(Rails.root.join('app', 'workers'))
end
- def self.default_queues
- [ActionMailer::DeliveryJob.queue_name, 'default']
- end
-
- def self.worker_queues
- @worker_queues ||= (workers.map(&:queue) + default_queues).uniq
- end
-
def self.find_workers(root)
concerns = root.join('concerns').to_s
@@ -43,7 +54,7 @@ module Gitlab
ns.camelize.constantize
end
- # Skip concerns
+ # Skip things that aren't workers
workers.select { |w| w < Sidekiq::Worker }
end
end
diff --git a/lib/gitlab/sidekiq_versioning.rb b/lib/gitlab/sidekiq_versioning.rb
new file mode 100644
index 00000000000..9683214ec18
--- /dev/null
+++ b/lib/gitlab/sidekiq_versioning.rb
@@ -0,0 +1,25 @@
+module Gitlab
+ module SidekiqVersioning
+ def self.install!
+ Sidekiq::Manager.prepend SidekiqVersioning::Manager
+
+ # The Sidekiq client API always adds the queue to the Sidekiq queue
+ # list, but mail_room and gitlab-shell do not. This is only necessary
+ # for monitoring.
+ begin
+ queues = SidekiqConfig.worker_queues
+
+ if queues.any?
+ Sidekiq.redis do |conn|
+ conn.pipelined do
+ queues.each do |queue|
+ conn.sadd('queues', queue)
+ end
+ end
+ end
+ end
+ rescue ::Redis::BaseError, SocketError, Errno::ENOENT, Errno::EADDRNOTAVAIL, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_versioning/manager.rb b/lib/gitlab/sidekiq_versioning/manager.rb
new file mode 100644
index 00000000000..308be0fdf76
--- /dev/null
+++ b/lib/gitlab/sidekiq_versioning/manager.rb
@@ -0,0 +1,12 @@
+module Gitlab
+ module SidekiqVersioning
+ module Manager
+ def initialize(options = {})
+ options[:strict] = false
+ options[:queues] = SidekiqConfig.expand_queues(options[:queues])
+ Sidekiq.logger.info "Listening on queues #{options[:queues].uniq.sort}"
+ super
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tcp_checker.rb b/lib/gitlab/tcp_checker.rb
new file mode 100644
index 00000000000..6e24e46d0ea
--- /dev/null
+++ b/lib/gitlab/tcp_checker.rb
@@ -0,0 +1,45 @@
+module Gitlab
+ class TcpChecker
+ attr_reader :remote_host, :remote_port, :local_host, :local_port, :error
+
+ def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
+ @remote_host = remote_host
+ @remote_port = remote_port
+ @local_host = local_host
+ @local_port = local_port
+ end
+
+ def local
+ join_host_port(local_host, local_port)
+ end
+
+ def remote
+ join_host_port(remote_host, remote_port)
+ end
+
+ def check(timeout: 10)
+ Socket.tcp(
+ remote_host, remote_port,
+ local_host, local_port,
+ connect_timeout: timeout
+ ) do |sock|
+ @local_port, @local_host = Socket.unpack_sockaddr_in(sock.local_address)
+ @remote_port, @remote_host = Socket.unpack_sockaddr_in(sock.remote_address)
+ end
+
+ true
+ rescue => err
+ @error = err
+
+ false
+ end
+
+ private
+
+ def join_host_port(host, port)
+ host = "[#{host}]" if host.include?(':')
+
+ "#{host}:#{port}"
+ end
+ end
+end
diff --git a/lib/gitlab/utils/strong_memoize.rb b/lib/gitlab/utils/strong_memoize.rb
index a2ac9285b56..fe091f4611b 100644
--- a/lib/gitlab/utils/strong_memoize.rb
+++ b/lib/gitlab/utils/strong_memoize.rb
@@ -11,6 +11,8 @@ module Gitlab
#
# We could write it like:
#
+ # include Gitlab::Utils::StrongMemoize
+ #
# def trigger_from_token
# strong_memoize(:trigger) do
# Ci::Trigger.find_by_token(params[:token].to_s)
@@ -18,14 +20,22 @@ module Gitlab
# end
#
def strong_memoize(name)
- ivar_name = "@#{name}"
-
- if instance_variable_defined?(ivar_name)
- instance_variable_get(ivar_name)
+ if instance_variable_defined?(ivar(name))
+ instance_variable_get(ivar(name))
else
- instance_variable_set(ivar_name, yield)
+ instance_variable_set(ivar(name), yield)
end
end
+
+ def clear_memoization(name)
+ remove_instance_variable(ivar(name)) if instance_variable_defined?(ivar(name))
+ end
+
+ private
+
+ def ivar(name)
+ "@#{name}"
+ end
end
end
end
diff --git a/lib/gitlab/view/presenter/factory.rb b/lib/gitlab/view/presenter/factory.rb
index d172d61e2c9..570f0723e39 100644
--- a/lib/gitlab/view/presenter/factory.rb
+++ b/lib/gitlab/view/presenter/factory.rb
@@ -16,7 +16,7 @@ module Gitlab
attr_reader :subject, :attributes
def presenter_class
- "#{subject.class.name}Presenter".constantize
+ attributes.delete(:presenter_class) { "#{subject.class.name}Presenter".constantize }
end
end
end
diff --git a/lib/tasks/gitlab/tcp_check.rake b/lib/tasks/gitlab/tcp_check.rake
new file mode 100644
index 00000000000..1400f57d6b9
--- /dev/null
+++ b/lib/tasks/gitlab/tcp_check.rake
@@ -0,0 +1,20 @@
+namespace :gitlab do
+ desc "GitLab | Check TCP connectivity to a specific host and port"
+ task :tcp_check, [:host, :port] => :environment do |_t, args|
+ unless args.host && args.port
+ puts "Please specify a host and port: `rake gitlab:tcp_check[example.com,80]`".color(:red)
+ exit 1
+ end
+
+ checker = Gitlab::TcpChecker.new(args.host, args.port)
+
+ if checker.check
+ puts "TCP connection from #{checker.local} to #{checker.remote} succeeded".color(:green)
+ else
+ puts "TCP connection to #{checker.remote} failed: #{checker.error}".color(:red)
+ puts
+ puts 'Check that host and port are correct, and that the traffic is permitted through any firewalls.'
+ exit 1
+ end
+ end
+end
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 9b6ffff7c4d..ed2ee73bea0 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -1,4 +1,4 @@
-FROM ruby:2.3
+FROM ruby:2.4
LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>"
ENV DEBIAN_FRONTEND noninteractive
diff --git a/qa/Gemfile b/qa/Gemfile
index ff29824529f..4c866a3f893 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -1,8 +1,8 @@
source 'https://rubygems.org'
-gem 'pry-byebug', '~> 3.4.1', platform: :mri
-gem 'capybara', '~> 2.12.1'
-gem 'capybara-screenshot', '~> 1.0.14'
-gem 'rake', '~> 12.0.0'
-gem 'rspec', '~> 3.5'
-gem 'selenium-webdriver', '~> 2.53'
+gem 'pry-byebug', '~> 3.5.1', platform: :mri
+gem 'capybara', '~> 2.16.1'
+gem 'capybara-screenshot', '~> 1.0.18'
+gem 'rake', '~> 12.3.0'
+gem 'rspec', '~> 3.7'
+gem 'selenium-webdriver', '~> 3.8.0'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 22d12b479cb..88d5fe834a0 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -1,78 +1,72 @@
GEM
remote: https://rubygems.org/
specs:
- addressable (2.5.0)
- public_suffix (~> 2.0, >= 2.0.2)
- byebug (9.0.6)
- capybara (2.12.1)
+ addressable (2.5.2)
+ public_suffix (>= 2.0.2, < 4.0)
+ byebug (9.1.0)
+ capybara (2.16.1)
addressable
- mime-types (>= 1.16)
+ mini_mime (>= 0.1.3)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
- capybara-screenshot (1.0.14)
+ capybara-screenshot (1.0.18)
capybara (>= 1.0, < 3)
launchy
- childprocess (0.7.0)
+ childprocess (0.8.0)
ffi (~> 1.0, >= 1.0.11)
- coderay (1.1.1)
+ coderay (1.1.2)
diff-lcs (1.3)
ffi (1.9.18)
launchy (2.4.3)
addressable (~> 2.3)
- method_source (0.8.2)
- mime-types (3.1)
- mime-types-data (~> 3.2015)
- mime-types-data (3.2016.0521)
+ method_source (0.9.0)
+ mini_mime (1.0.0)
mini_portile2 (2.3.0)
nokogiri (1.8.1)
mini_portile2 (~> 2.3.0)
- pry (0.10.4)
+ pry (0.11.3)
coderay (~> 1.1.0)
- method_source (~> 0.8.1)
- slop (~> 3.4)
- pry-byebug (3.4.2)
- byebug (~> 9.0)
+ method_source (~> 0.9.0)
+ pry-byebug (3.5.1)
+ byebug (~> 9.1)
pry (~> 0.10)
- public_suffix (2.0.5)
- rack (2.0.1)
- rack-test (0.6.3)
- rack (>= 1.0)
- rake (12.0.0)
- rspec (3.5.0)
- rspec-core (~> 3.5.0)
- rspec-expectations (~> 3.5.0)
- rspec-mocks (~> 3.5.0)
- rspec-core (3.5.4)
- rspec-support (~> 3.5.0)
- rspec-expectations (3.5.0)
+ public_suffix (3.0.1)
+ rack (2.0.3)
+ rack-test (0.8.2)
+ rack (>= 1.0, < 3)
+ rake (12.3.0)
+ rspec (3.7.0)
+ rspec-core (~> 3.7.0)
+ rspec-expectations (~> 3.7.0)
+ rspec-mocks (~> 3.7.0)
+ rspec-core (3.7.0)
+ rspec-support (~> 3.7.0)
+ rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.5.0)
- rspec-mocks (3.5.0)
+ rspec-support (~> 3.7.0)
+ rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.5.0)
- rspec-support (3.5.0)
+ rspec-support (~> 3.7.0)
+ rspec-support (3.7.0)
rubyzip (1.2.1)
- selenium-webdriver (2.53.4)
+ selenium-webdriver (3.8.0)
childprocess (~> 0.5)
rubyzip (~> 1.0)
- websocket (~> 1.0)
- slop (3.6.0)
- websocket (1.2.4)
- xpath (2.0.0)
+ xpath (2.1.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
- capybara (~> 2.12.1)
- capybara-screenshot (~> 1.0.14)
- pry-byebug (~> 3.4.1)
- rake (~> 12.0.0)
- rspec (~> 3.5)
- selenium-webdriver (~> 2.53)
+ capybara (~> 2.16.1)
+ capybara-screenshot (~> 1.0.18)
+ pry-byebug (~> 3.5.1)
+ rake (~> 12.3.0)
+ rspec (~> 3.7)
+ selenium-webdriver (~> 3.8.0)
BUNDLED WITH
- 1.15.4
+ 1.16.0
diff --git a/qa/qa.rb b/qa/qa.rb
index 4cbcf585030..0294fc28edf 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -9,6 +9,7 @@ module QA
autoload :User, 'qa/runtime/user'
autoload :Namespace, 'qa/runtime/namespace'
autoload :Scenario, 'qa/runtime/scenario'
+ autoload :Browser, 'qa/runtime/browser'
end
##
@@ -69,7 +70,6 @@ module QA
autoload :Base, 'qa/page/base'
module Main
- autoload :Entry, 'qa/page/main/entry'
autoload :Login, 'qa/page/main/login'
autoload :Menu, 'qa/page/main/menu'
autoload :OAuth, 'qa/page/main/oauth'
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index f9a93ef051e..99eba02b6e3 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -10,6 +10,18 @@ module QA
visit current_url
end
+ def wait(css = '.application', time: 60)
+ Time.now.tap do |start|
+ while Time.now - start < time
+ break if page.has_css?(css, wait: 5)
+
+ refresh
+ end
+ end
+
+ yield if block_given?
+ end
+
def scroll_to(selector, text: nil)
page.execute_script <<~JS
var elements = Array.from(document.querySelectorAll('#{selector}'));
@@ -24,6 +36,10 @@ module QA
page.within(selector) { yield } if block_given?
end
+
+ def self.path
+ raise NotImplementedError
+ end
end
end
end
diff --git a/qa/qa/page/main/entry.rb b/qa/qa/page/main/entry.rb
deleted file mode 100644
index ae6484b4bfe..00000000000
--- a/qa/qa/page/main/entry.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module QA
- module Page
- module Main
- class Entry < Page::Base
- def visit_login_page
- visit("#{Runtime::Scenario.gitlab_address}/users/sign_in")
- wait_for_instance_to_be_ready
- end
-
- private
-
- def wait_for_instance_to_be_ready
- # This resolves cold boot / background tasks problems
- #
- start = Time.now
-
- while Time.now - start < 240
- break if page.has_css?('.application', wait: 10)
-
- refresh
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 8b0111a78a2..f88325f408b 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -2,6 +2,10 @@ module QA
module Page
module Main
class Login < Page::Base
+ def initialize
+ wait('.application', time: 500)
+ end
+
def sign_in_using_credentials
if page.has_content?('Change your password')
fill_in :user_password, with: Runtime::User.password
@@ -13,6 +17,10 @@ module QA
fill_in :user_password, with: Runtime::User.password
click_button 'Sign in'
end
+
+ def self.path
+ '/users/sign_in'
+ end
end
end
end
diff --git a/qa/qa/page/mattermost/login.rb b/qa/qa/page/mattermost/login.rb
index 42ab9c6f675..8ffd4fdad13 100644
--- a/qa/qa/page/mattermost/login.rb
+++ b/qa/qa/page/mattermost/login.rb
@@ -2,10 +2,6 @@ module QA
module Page
module Mattermost
class Login < Page::Base
- def initialize
- visit(Runtime::Scenario.mattermost_address + '/login')
- end
-
def sign_in_using_oauth
click_link class: 'btn btn-custom-login gitlab'
@@ -13,6 +9,10 @@ module QA
click_button 'Authorize'
end
end
+
+ def self.path
+ '/login'
+ end
end
end
end
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
new file mode 100644
index 00000000000..6fb37fdfc7f
--- /dev/null
+++ b/qa/qa/runtime/browser.rb
@@ -0,0 +1,109 @@
+require 'rspec/core'
+require 'capybara/rspec'
+require 'capybara-screenshot/rspec'
+require 'selenium-webdriver'
+
+module QA
+ module Runtime
+ class Browser
+ include QA::Scenario::Actable
+
+ def initialize
+ self.class.configure!
+ end
+
+ ##
+ # Visit a page that belongs to a GitLab instance under given address.
+ #
+ # Example:
+ #
+ # visit(:gitlab, Page::Main::Login)
+ # visit('http://gitlab.example/users/sign_in')
+ #
+ # In case of an address that is a symbol we will try to guess address
+ # based on `Runtime::Scenario#something_address`.
+ #
+ def visit(address, page, &block)
+ Browser::Session.new(address, page).tap do |session|
+ session.perform(&block)
+ end
+ end
+
+ def self.visit(address, page, &block)
+ new.visit(address, page, &block)
+ end
+
+ def self.configure!
+ return if Capybara.drivers.include?(:chrome)
+
+ Capybara.register_driver :chrome do |app|
+ capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
+ 'chromeOptions' => {
+ 'args' => %w[headless no-sandbox disable-gpu window-size=1280,1680]
+ }
+ )
+
+ Capybara::Selenium::Driver
+ .new(app, browser: :chrome, desired_capabilities: capabilities)
+ end
+
+ Capybara::Screenshot.register_driver(:chrome) do |driver, path|
+ driver.browser.save_screenshot(path)
+ end
+
+ Capybara.configure do |config|
+ config.default_driver = :chrome
+ config.javascript_driver = :chrome
+ config.default_max_wait_time = 10
+ # https://github.com/mattheworiordan/capybara-screenshot/issues/164
+ config.save_path = 'tmp'
+ end
+ end
+
+ class Session
+ include Capybara::DSL
+
+ def initialize(instance, page = nil)
+ @instance = instance
+ @address = host + page&.path
+ end
+
+ def host
+ if @instance.is_a?(Symbol)
+ Runtime::Scenario.send("#{@instance}_address")
+ else
+ @instance.to_s
+ end
+ end
+
+ def perform(&block)
+ visit(@address)
+
+ yield if block_given?
+ rescue
+ raise if block.nil?
+
+ # RSpec examples will take care of screenshots on their own
+ #
+ unless block.binding.receiver.is_a?(RSpec::Core::ExampleGroup)
+ screenshot_and_save_page
+ end
+
+ raise
+ ensure
+ clear! if block_given?
+ end
+
+ ##
+ # Selenium allows to reset session cookies for current domain only.
+ #
+ # See gitlab-org/gitlab-qa#102
+ #
+ def clear!
+ visit(@address)
+ reset_session!
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/scenario/entrypoint.rb b/qa/qa/scenario/entrypoint.rb
index b9d924651a0..ae099fd911e 100644
--- a/qa/qa/scenario/entrypoint.rb
+++ b/qa/qa/scenario/entrypoint.rb
@@ -8,7 +8,6 @@ module QA
include Bootable
def perform(address, *files)
- Specs::Config.act { configure_capybara! }
Runtime::Scenario.define(:gitlab_address, address)
##
diff --git a/qa/qa/scenario/gitlab/admin/hashed_storage.rb b/qa/qa/scenario/gitlab/admin/hashed_storage.rb
index ac2cd549829..44604c6bc66 100644
--- a/qa/qa/scenario/gitlab/admin/hashed_storage.rb
+++ b/qa/qa/scenario/gitlab/admin/hashed_storage.rb
@@ -6,7 +6,6 @@ module QA
def perform(*traits)
raise ArgumentError unless traits.include?(:enabled)
- Page::Main::Entry.act { visit_login_page }
Page::Main::Login.act { sign_in_using_credentials }
Page::Main::Menu.act { go_to_admin_area }
Page::Admin::Menu.act { go_to_settings }
diff --git a/qa/qa/specs/config.rb b/qa/qa/specs/config.rb
deleted file mode 100644
index bce7923e52d..00000000000
--- a/qa/qa/specs/config.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-require 'rspec/core'
-require 'capybara/rspec'
-require 'capybara-screenshot/rspec'
-require 'selenium-webdriver'
-
-# rubocop:disable Metrics/MethodLength
-# rubocop:disable Metrics/LineLength
-
-module QA
- module Specs
- class Config < Scenario::Template
- include Scenario::Actable
-
- def perform
- configure_rspec!
- configure_capybara!
- end
-
- def configure_rspec!
- RSpec.configure do |config|
- config.expect_with :rspec do |expectations|
- expectations.include_chain_clauses_in_custom_matcher_descriptions = true
- end
-
- config.mock_with :rspec do |mocks|
- mocks.verify_partial_doubles = true
- end
-
- config.order = :random
- Kernel.srand config.seed
- config.formatter = :documentation
- config.color = true
- end
- end
-
- def configure_capybara!
- Capybara.register_driver :chrome do |app|
- capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
- 'chromeOptions' => {
- 'args' => %w[headless no-sandbox disable-gpu window-size=1280,1680]
- }
- )
-
- Capybara::Selenium::Driver
- .new(app, browser: :chrome, desired_capabilities: capabilities)
- end
-
- Capybara::Screenshot.register_driver(:chrome) do |driver, path|
- driver.browser.save_screenshot(path)
- end
-
- Capybara.configure do |config|
- config.default_driver = :chrome
- config.javascript_driver = :chrome
- config.default_max_wait_time = 10
-
- # https://github.com/mattheworiordan/capybara-screenshot/issues/164
- config.save_path = 'tmp'
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/login/standard_spec.rb b/qa/qa/specs/features/login/standard_spec.rb
index b155708c387..9eaa2b772e6 100644
--- a/qa/qa/specs/features/login/standard_spec.rb
+++ b/qa/qa/specs/features/login/standard_spec.rb
@@ -1,7 +1,7 @@
module QA
- feature 'standard root login', :core do
+ feature 'standard user login', :core do
scenario 'user logs in using credentials' do
- Page::Main::Entry.act { visit_login_page }
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
# TODO, since `Signed in successfully` message was removed
diff --git a/qa/qa/specs/features/mattermost/group_create_spec.rb b/qa/qa/specs/features/mattermost/group_create_spec.rb
index 853a9a6a4f4..b3dbe44bf6e 100644
--- a/qa/qa/specs/features/mattermost/group_create_spec.rb
+++ b/qa/qa/specs/features/mattermost/group_create_spec.rb
@@ -1,7 +1,7 @@
module QA
feature 'create a new group', :mattermost do
scenario 'creating a group with a mattermost team' do
- Page::Main::Entry.act { visit_login_page }
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Page::Main::Menu.act { go_to_groups }
diff --git a/qa/qa/specs/features/mattermost/login_spec.rb b/qa/qa/specs/features/mattermost/login_spec.rb
index 1fde3f89a99..637bbdd643a 100644
--- a/qa/qa/specs/features/mattermost/login_spec.rb
+++ b/qa/qa/specs/features/mattermost/login_spec.rb
@@ -1,24 +1,17 @@
module QA
feature 'logging in to Mattermost', :mattermost do
scenario 'can use gitlab oauth' do
- Page::Main::Entry.act { visit_login_page }
- Page::Main::Login.act { sign_in_using_credentials }
- Page::Mattermost::Login.act { sign_in_using_oauth }
+ Runtime::Browser.visit(:gitlab, Page::Main::Login) do
+ Page::Main::Login.act { sign_in_using_credentials }
- Page::Mattermost::Main.perform do |page|
- expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
- end
- end
+ Runtime::Browser.visit(:mattermost, Page::Mattermost::Login) do
+ Page::Mattermost::Login.act { sign_in_using_oauth }
- ##
- # TODO, temporary workaround for gitlab-org/gitlab-qa#102.
- #
- after do
- visit Runtime::Scenario.mattermost_address
- reset_session!
-
- visit Runtime::Scenario.gitlab_address
- reset_session!
+ Page::Mattermost::Main.perform do |page|
+ expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
+ end
+ end
+ end
end
end
end
diff --git a/qa/qa/specs/features/project/create_spec.rb b/qa/qa/specs/features/project/create_spec.rb
index aba0c2b4c14..0b3accb848d 100644
--- a/qa/qa/specs/features/project/create_spec.rb
+++ b/qa/qa/specs/features/project/create_spec.rb
@@ -1,7 +1,7 @@
module QA
feature 'create a new project', :core do
scenario 'user creates a new project' do
- Page::Main::Entry.act { visit_login_page }
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |project|
diff --git a/qa/qa/specs/features/repository/clone_spec.rb b/qa/qa/specs/features/repository/clone_spec.rb
index 5cc3b3b9c1b..c5c24622657 100644
--- a/qa/qa/specs/features/repository/clone_spec.rb
+++ b/qa/qa/specs/features/repository/clone_spec.rb
@@ -9,7 +9,7 @@ module QA
end
before do
- Page::Main::Entry.act { visit_login_page }
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |scenario|
diff --git a/qa/qa/specs/features/repository/push_spec.rb b/qa/qa/specs/features/repository/push_spec.rb
index 5b930b9818a..ef29dfa2d2f 100644
--- a/qa/qa/specs/features/repository/push_spec.rb
+++ b/qa/qa/specs/features/repository/push_spec.rb
@@ -2,7 +2,7 @@ module QA
feature 'push code to repository', :core do
context 'with regular account over http' do
scenario 'user pushes code to the repository' do
- Page::Main::Entry.act { visit_login_page }
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |scenario|
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index f98b8f88e9a..3f7b75df986 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -17,7 +17,7 @@ module QA
tags.to_a.each { |tag| args.push(['-t', tag.to_s]) }
args.push(files)
- Specs::Config.perform
+ Runtime::Browser.configure!
RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
abort if status.nonzero?
diff --git a/rubocop/cop/include_sidekiq_worker.rb b/rubocop/cop/include_sidekiq_worker.rb
new file mode 100644
index 00000000000..4a6332286a2
--- /dev/null
+++ b/rubocop/cop/include_sidekiq_worker.rb
@@ -0,0 +1,29 @@
+require_relative '../spec_helpers'
+
+module RuboCop
+ module Cop
+ # Cop that makes sure workers include `ApplicationWorker`, not `Sidekiq::Worker`.
+ class IncludeSidekiqWorker < RuboCop::Cop::Cop
+ include SpecHelpers
+
+ MSG = 'Include `ApplicationWorker`, not `Sidekiq::Worker`.'.freeze
+
+ def_node_matcher :includes_sidekiq_worker?, <<~PATTERN
+ (send nil :include (const (const nil :Sidekiq) :Worker))
+ PATTERN
+
+ def on_send(node)
+ return if in_spec?(node)
+ return unless includes_sidekiq_worker?(node)
+
+ add_offense(node.arguments.first, :expression)
+ end
+
+ def autocorrect(node)
+ lambda do |corrector|
+ corrector.replace(node.source_range, 'ApplicationWorker')
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/sidekiq_options_queue.rb b/rubocop/cop/sidekiq_options_queue.rb
new file mode 100644
index 00000000000..43b35ba0214
--- /dev/null
+++ b/rubocop/cop/sidekiq_options_queue.rb
@@ -0,0 +1,27 @@
+require_relative '../spec_helpers'
+
+module RuboCop
+ module Cop
+ # Cop that prevents manually setting a queue in Sidekiq workers.
+ class SidekiqOptionsQueue < RuboCop::Cop::Cop
+ include SpecHelpers
+
+ MSG = 'Do not manually set a queue; `ApplicationWorker` sets one automatically.'.freeze
+
+ def_node_matcher :sidekiq_options?, <<~PATTERN
+ (send nil :sidekiq_options $...)
+ PATTERN
+
+ def on_send(node)
+ return if in_spec?(node)
+ return unless sidekiq_options?(node)
+
+ node.arguments.first.each_node(:pair) do |pair|
+ key_name = pair.key.children[0]
+
+ add_offense(pair, :expression) if key_name == :queue
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index eb52be3d731..3e3b4c8349a 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -3,10 +3,12 @@ require_relative 'cop/active_record_serialize'
require_relative 'cop/custom_error_class'
require_relative 'cop/gem_fetcher'
require_relative 'cop/in_batches'
+require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/line_break_after_guard_clauses'
require_relative 'cop/polymorphic_associations'
require_relative 'cop/project_path_helper'
require_relative 'cop/redirect_with_status'
+require_relative 'cop/sidekiq_options_queue'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index f4f12a095fc..4e2d8e8969e 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -2,15 +2,28 @@ require_relative '../support/repo_helpers'
FactoryGirl.define do
factory :commit do
- git_commit RepoHelpers.sample_commit
+ transient do
+ author nil
+ end
+
+ git_commit do
+ commit = RepoHelpers.sample_commit
+
+ if author
+ commit.author_email = author.email
+ commit.author_name = author.name
+ end
+
+ commit
+ end
project
initialize_with do
new(git_commit, project)
end
- after(:build) do |commit|
- allow(commit).to receive(:author).and_return build(:author)
+ after(:build) do |commit, evaluator|
+ allow(commit).to receive(:author).and_return(evaluator.author || build(:author))
end
trait :without_author do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index c870910c8ea..77dcdf89f37 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -197,7 +197,7 @@ describe 'Commits' do
commits = project.repository.commits(branch_name)
commits.each do |commit|
- expect(page).to have_content("committed #{commit.committed_date.strftime("%b %d, %Y")}")
+ expect(page).to have_content("authored #{commit.authored_date.strftime("%b %d, %Y")}")
end
end
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index 6f916078b1a..94133c62b5c 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -13,7 +13,7 @@ feature 'Dashboard Todos' do
end
it 'shows "All done" message' do
- expect(page).to have_content 'Todos let you see what you should do next.'
+ expect(page).to have_content 'Todos let you see what you should do next'
end
end
diff --git a/spec/features/groups/labels/user_sees_links_to_issuables.rb b/spec/features/groups/labels/user_sees_links_to_issuables.rb
new file mode 100644
index 00000000000..5d6290d2109
--- /dev/null
+++ b/spec/features/groups/labels/user_sees_links_to_issuables.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+feature 'Groups > Labels > User sees links to issuables' do
+ set(:group) { create(:group, :public) }
+
+ before do
+ create(:group_label, group: group, title: 'bug')
+ visit group_labels_path(group)
+ end
+
+ scenario 'shows links to MRs and issues' do
+ expect(page).to have_link('view merge requests')
+ expect(page).to have_link('view open issues')
+ end
+end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index c60883911f7..0848857ed1e 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -25,7 +25,7 @@ describe 'Profile account page', :js do
fill_in 'password', with: '12345678'
- page.within '.popup-dialog' do
+ page.within '.modal' do
click_button 'Delete account'
end
@@ -38,7 +38,7 @@ describe 'Profile account page', :js do
fill_in 'password', with: 'testing123'
- page.within '.popup-dialog' do
+ page.within '.modal' do
click_button 'Delete account'
end
diff --git a/spec/features/projects/labels/user_sees_links_to_issuables.rb b/spec/features/projects/labels/user_sees_links_to_issuables.rb
new file mode 100644
index 00000000000..aa56fd7f74e
--- /dev/null
+++ b/spec/features/projects/labels/user_sees_links_to_issuables.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+feature 'Projects > Labels > User sees links to issuables' do
+ set(:user) { create(:user) }
+
+ before do
+ label # creates the label
+ project.add_developer(user)
+ sign_in user
+ visit project_labels_path(project)
+ end
+
+ context 'with a project label' do
+ let(:label) { create(:label, project: project, title: 'bug') }
+
+ context 'when merge requests and issues are enabled for the project' do
+ let(:project) { create(:project, :public) }
+
+ scenario 'shows links to MRs and issues' do
+ expect(page).to have_link('view merge requests')
+ expect(page).to have_link('view open issues')
+ end
+ end
+
+ context 'when issues are disabled for the project' do
+ let(:project) { create(:project, :public, issues_access_level: ProjectFeature::DISABLED) }
+
+ scenario 'shows links to MRs but not to issues' do
+ expect(page).to have_link('view merge requests')
+ expect(page).not_to have_link('view open issues')
+ end
+ end
+
+ context 'when merge requests are disabled for the project' do
+ let(:project) { create(:project, :public, merge_requests_access_level: ProjectFeature::DISABLED) }
+
+ scenario 'shows links to issues but not to MRs' do
+ expect(page).not_to have_link('view merge requests')
+ expect(page).to have_link('view open issues')
+ end
+ end
+ end
+
+ context 'with a group label' do
+ set(:group) { create(:group) }
+ let(:label) { create(:group_label, group: group, title: 'bug') }
+
+ context 'when merge requests and issues are enabled for the project' do
+ let(:project) { create(:project, :public, namespace: group) }
+
+ scenario 'shows links to MRs and issues' do
+ expect(page).to have_link('view merge requests')
+ expect(page).to have_link('view open issues')
+ end
+ end
+
+ context 'when issues are disabled for the project' do
+ let(:project) { create(:project, :public, namespace: group, issues_access_level: ProjectFeature::DISABLED) }
+
+ scenario 'shows links to MRs and issues' do
+ expect(page).to have_link('view merge requests')
+ expect(page).to have_link('view open issues')
+ end
+ end
+
+ context 'when merge requests are disabled for the project' do
+ let(:project) { create(:project, :public, namespace: group, merge_requests_access_level: ProjectFeature::DISABLED) }
+
+ scenario 'shows links to MRs and issues' do
+ expect(page).to have_link('view merge requests')
+ expect(page).to have_link('view open issues')
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 156293289dd..8f06328962e 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -20,7 +20,7 @@ feature 'Multi-file editor new directory', :js do
click_link('New directory')
- page.within('.popup-dialog') do
+ page.within('.modal') do
find('.form-control').set('foldername')
click_button('Create directory')
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index 8fb8476e631..bdebc12ef47 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -20,7 +20,7 @@ feature 'Multi-file editor new file', :js do
click_link('New file')
- page.within('.popup-dialog') do
+ page.within('.modal') do
find('.form-control').set('filename')
click_button('Create file')
diff --git a/spec/fixtures/api/schemas/contributor.json b/spec/fixtures/api/schemas/contributor.json
new file mode 100644
index 00000000000..e88470a2363
--- /dev/null
+++ b/spec/fixtures/api/schemas/contributor.json
@@ -0,0 +1,18 @@
+{
+ "type": "object",
+ "required" : [
+ "name",
+ "email",
+ "commits",
+ "additions",
+ "deletions"
+ ],
+ "properties" : {
+ "name": { "type": "string" },
+ "email": { "type": "string" },
+ "commits": { "type": "integer" },
+ "additions": { "type": "integer" },
+ "deletions": { "type": "integer" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/contributors.json b/spec/fixtures/api/schemas/contributors.json
new file mode 100644
index 00000000000..a9f1d1ea64f
--- /dev/null
+++ b/spec/fixtures/api/schemas/contributors.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "contributor.json" }
+}
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 4ac4302adfd..0286d36952c 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -1,6 +1,69 @@
require 'spec_helper'
describe LabelsHelper do
+ describe '#show_label_issuables_link?' do
+ shared_examples 'a valid response to show_label_issuables_link?' do |issuables_type, when_enabled = true, when_disabled = false|
+ let(:context_project) { project }
+
+ context "when asking for a #{issuables_type} link" do
+ subject { show_label_issuables_link?(label, issuables_type, project: context_project) }
+
+ context "when #{issuables_type} are enabled for the project" do
+ let(:project) { create(:project, "#{issuables_type}_access_level": ProjectFeature::ENABLED) }
+
+ it { is_expected.to be(when_enabled) }
+ end
+
+ context "when #{issuables_type} are disabled for the project" do
+ let(:project) { create(:project, :public, "#{issuables_type}_access_level": ProjectFeature::DISABLED) }
+
+ it { is_expected.to be(when_disabled) }
+ end
+ end
+ end
+
+ context 'with a project label' do
+ let(:label) { create(:label, project: project, title: 'bug') }
+
+ context 'when asking for an issue link' do
+ it_behaves_like 'a valid response to show_label_issuables_link?', :issues
+ end
+
+ context 'when asking for a merge requests link' do
+ it_behaves_like 'a valid response to show_label_issuables_link?', :merge_requests
+ end
+ end
+
+ context 'with a group label' do
+ set(:group) { create(:group) }
+ let(:label) { create(:group_label, group: group, title: 'bug') }
+
+ context 'when asking for an issue link' do
+ context 'in the context of a project' do
+ it_behaves_like 'a valid response to show_label_issuables_link?', :issues, true, true
+ end
+
+ context 'in the context of a group' do
+ let(:context_project) { nil }
+
+ it_behaves_like 'a valid response to show_label_issuables_link?', :issues, true, true
+ end
+ end
+
+ context 'when asking for a merge requests link' do
+ context 'in the context of a project' do
+ it_behaves_like 'a valid response to show_label_issuables_link?', :merge_requests, true, true
+ end
+
+ context 'in the context of a group' do
+ let(:context_project) { nil }
+
+ it_behaves_like 'a valid response to show_label_issuables_link?', :merge_requests, true, true
+ end
+ end
+ end
+ end
+
describe 'link_to_label' do
let(:project) { create(:project) }
let(:label) { create(:label, project: project) }
diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb
index 33186cf50d5..45ffbeb27a4 100644
--- a/spec/helpers/members_helper_spec.rb
+++ b/spec/helpers/members_helper_spec.rb
@@ -1,14 +1,6 @@
require 'spec_helper'
describe MembersHelper do
- describe '#action_member_permission' do
- let(:project_member) { build(:project_member) }
- let(:group_member) { build(:group_member) }
-
- it { expect(action_member_permission(:admin, project_member)).to eq :admin_project_member }
- it { expect(action_member_permission(:admin, group_member)).to eq :admin_group_member }
- end
-
describe '#remove_member_message' do
let(:requester) { create(:user) }
let(:project) { create(:project, :public, :access_requestable) }
diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js
index e8c5f721423..fc9be14df8f 100644
--- a/spec/javascripts/activities_spec.js
+++ b/spec/javascripts/activities_spec.js
@@ -2,7 +2,7 @@
import 'vendor/jquery.endless-scroll';
import '~/pager';
-import '~/activities';
+import Activities from '~/activities';
(() => {
window.gon || (window.gon = {});
@@ -35,7 +35,7 @@ import '~/activities';
describe('Activities', () => {
beforeEach(() => {
loadFixtures(fixtureTemplate);
- new gl.Activities();
+ new Activities();
});
for (let i = 0; i < filters.length; i += 1) {
diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js
index 0f7bf9ec712..2e5b65f5610 100644
--- a/spec/javascripts/datetime_utility_spec.js
+++ b/spec/javascripts/datetime_utility_spec.js
@@ -1,110 +1,108 @@
import * as datetimeUtility from '~/lib/utils/datetime_utility';
-(() => {
- describe('Date time utils', () => {
- describe('timeFor', () => {
- it('returns `past due` when in past', () => {
- const date = new Date();
- date.setFullYear(date.getFullYear() - 1);
-
- expect(
- gl.utils.timeFor(date),
- ).toBe('Past due');
- });
-
- it('returns remaining time when in the future', () => {
- const date = new Date();
- date.setFullYear(date.getFullYear() + 1);
-
- // Add a day to prevent a transient error. If date is even 1 second
- // short of a full year, timeFor will return '11 months remaining'
- date.setDate(date.getDate() + 1);
-
- expect(
- gl.utils.timeFor(date),
- ).toBe('1 year remaining');
- });
+describe('Date time utils', () => {
+ describe('timeFor', () => {
+ it('returns `past due` when in past', () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() - 1);
+
+ expect(
+ datetimeUtility.timeFor(date),
+ ).toBe('Past due');
});
- describe('get day name', () => {
- it('should return Sunday', () => {
- const day = gl.utils.getDayName(new Date('07/17/2016'));
- expect(day).toBe('Sunday');
- });
-
- it('should return Monday', () => {
- const day = gl.utils.getDayName(new Date('07/18/2016'));
- expect(day).toBe('Monday');
- });
-
- it('should return Tuesday', () => {
- const day = gl.utils.getDayName(new Date('07/19/2016'));
- expect(day).toBe('Tuesday');
- });
-
- it('should return Wednesday', () => {
- const day = gl.utils.getDayName(new Date('07/20/2016'));
- expect(day).toBe('Wednesday');
- });
-
- it('should return Thursday', () => {
- const day = gl.utils.getDayName(new Date('07/21/2016'));
- expect(day).toBe('Thursday');
- });
-
- it('should return Friday', () => {
- const day = gl.utils.getDayName(new Date('07/22/2016'));
- expect(day).toBe('Friday');
- });
-
- it('should return Saturday', () => {
- const day = gl.utils.getDayName(new Date('07/23/2016'));
- expect(day).toBe('Saturday');
- });
- });
+ it('returns remaining time when in the future', () => {
+ const date = new Date();
+ date.setFullYear(date.getFullYear() + 1);
+
+ // Add a day to prevent a transient error. If date is even 1 second
+ // short of a full year, timeFor will return '11 months remaining'
+ date.setDate(date.getDate() + 1);
- describe('get day difference', () => {
- it('should return 7', () => {
- const firstDay = new Date('07/01/2016');
- const secondDay = new Date('07/08/2016');
- const difference = gl.utils.getDayDifference(firstDay, secondDay);
- expect(difference).toBe(7);
- });
-
- it('should return 31', () => {
- const firstDay = new Date('07/01/2016');
- const secondDay = new Date('08/01/2016');
- const difference = gl.utils.getDayDifference(firstDay, secondDay);
- expect(difference).toBe(31);
- });
-
- it('should return 365', () => {
- const firstDay = new Date('07/02/2015');
- const secondDay = new Date('07/01/2016');
- const difference = gl.utils.getDayDifference(firstDay, secondDay);
- expect(difference).toBe(365);
- });
+ expect(
+ datetimeUtility.timeFor(date),
+ ).toBe('1 year remaining');
});
});
- describe('timeIntervalInWords', () => {
- it('should return string with number of minutes and seconds', () => {
- expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds');
- expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second');
- expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds');
- expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds');
+ describe('get day name', () => {
+ it('should return Sunday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/17/2016'));
+ expect(day).toBe('Sunday');
+ });
+
+ it('should return Monday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/18/2016'));
+ expect(day).toBe('Monday');
+ });
+
+ it('should return Tuesday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/19/2016'));
+ expect(day).toBe('Tuesday');
+ });
+
+ it('should return Wednesday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/20/2016'));
+ expect(day).toBe('Wednesday');
+ });
+
+ it('should return Thursday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/21/2016'));
+ expect(day).toBe('Thursday');
+ });
+
+ it('should return Friday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/22/2016'));
+ expect(day).toBe('Friday');
+ });
+
+ it('should return Saturday', () => {
+ const day = datetimeUtility.getDayName(new Date('07/23/2016'));
+ expect(day).toBe('Saturday');
});
});
- describe('dateInWords', () => {
- const date = new Date('07/01/2016');
+ describe('get day difference', () => {
+ it('should return 7', () => {
+ const firstDay = new Date('07/01/2016');
+ const secondDay = new Date('07/08/2016');
+ const difference = datetimeUtility.getDayDifference(firstDay, secondDay);
+ expect(difference).toBe(7);
+ });
- it('should return date in words', () => {
- expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016');
+ it('should return 31', () => {
+ const firstDay = new Date('07/01/2016');
+ const secondDay = new Date('08/01/2016');
+ const difference = datetimeUtility.getDayDifference(firstDay, secondDay);
+ expect(difference).toBe(31);
});
- it('should return abbreviated month name', () => {
- expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016');
+ it('should return 365', () => {
+ const firstDay = new Date('07/02/2015');
+ const secondDay = new Date('07/01/2016');
+ const difference = datetimeUtility.getDayDifference(firstDay, secondDay);
+ expect(difference).toBe(365);
});
});
-})();
+});
+
+describe('timeIntervalInWords', () => {
+ it('should return string with number of minutes and seconds', () => {
+ expect(datetimeUtility.timeIntervalInWords(9.54)).toEqual('9 seconds');
+ expect(datetimeUtility.timeIntervalInWords(1)).toEqual('1 second');
+ expect(datetimeUtility.timeIntervalInWords(200)).toEqual('3 minutes 20 seconds');
+ expect(datetimeUtility.timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds');
+ });
+});
+
+describe('dateInWords', () => {
+ const date = new Date('07/01/2016');
+
+ it('should return date in words', () => {
+ expect(datetimeUtility.dateInWords(date)).toEqual('July 1, 2016');
+ });
+
+ it('should return abbreviated month name', () => {
+ expect(datetimeUtility.dateInWords(date, true)).toEqual('Jul 1, 2016');
+ });
+});
diff --git a/spec/javascripts/deploy_keys/components/key_spec.js b/spec/javascripts/deploy_keys/components/key_spec.js
index 5b64cbb2dfc..2f28c5bbf01 100644
--- a/spec/javascripts/deploy_keys/components/key_spec.js
+++ b/spec/javascripts/deploy_keys/components/key_spec.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import DeployKeysStore from '~/deploy_keys/store';
import key from '~/deploy_keys/components/key.vue';
+import { getTimeago } from '~/lib/utils/datetime_utility';
describe('Deploy keys key', () => {
let vm;
@@ -37,7 +38,7 @@ describe('Deploy keys key', () => {
it('renders human friendly formatted created date', () => {
expect(
vm.$el.querySelector('.key-created-at').textContent.trim(),
- ).toBe(`created ${gl.utils.getTimeago().format(deployKey.created_at)}`);
+ ).toBe(`created ${getTimeago().format(deployKey.created_at)}`);
});
it('shows edit button', () => {
diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js
index 2ce1a749a96..7a5c1da4d1d 100644
--- a/spec/javascripts/groups/components/item_actions_spec.js
+++ b/spec/javascripts/groups/components/item_actions_spec.js
@@ -36,27 +36,27 @@ describe('ItemActionsComponent', () => {
describe('methods', () => {
describe('onLeaveGroup', () => {
- it('should change `dialogStatus` prop to `true` which shows confirmation dialog', () => {
- expect(vm.dialogStatus).toBeFalsy();
+ it('should change `modalStatus` prop to `true` which shows confirmation dialog', () => {
+ expect(vm.modalStatus).toBeFalsy();
vm.onLeaveGroup();
- expect(vm.dialogStatus).toBeTruthy();
+ expect(vm.modalStatus).toBeTruthy();
});
});
describe('leaveGroup', () => {
- it('should change `dialogStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => {
+ it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => {
spyOn(eventHub, '$emit');
- vm.dialogStatus = true;
+ vm.modalStatus = true;
vm.leaveGroup(true);
- expect(vm.dialogStatus).toBeFalsy();
+ expect(vm.modalStatus).toBeFalsy();
expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup);
});
- it('should change `dialogStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => {
+ it('should change `modalStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => {
spyOn(eventHub, '$emit');
- vm.dialogStatus = true;
+ vm.modalStatus = true;
vm.leaveGroup(false);
- expect(vm.dialogStatus).toBeFalsy();
+ expect(vm.modalStatus).toBeFalsy();
expect(eventHub.$emit).not.toHaveBeenCalled();
});
});
@@ -99,9 +99,9 @@ describe('ItemActionsComponent', () => {
newVm.$destroy();
});
- it('should show modal dialog when `dialogStatus` is set to `true`', () => {
- vm.dialogStatus = true;
- const modalDialogEl = vm.$el.querySelector('.modal.popup-dialog');
+ it('should show modal dialog when `modalStatus` is set to `true`', () => {
+ vm.modalStatus = true;
+ const modalDialogEl = vm.$el.querySelector('.modal');
expect(modalDialogEl).toBeDefined();
expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 729c3c29f22..7159148f8fa 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -272,10 +272,10 @@ describe('Issuable output', () => {
});
});
- it('opens recaptcha dialog if update rejected as spam', (done) => {
+ it('opens recaptcha modal if update rejected as spam', (done) => {
function mockScriptSrc() {
const recaptchaChild = vm.$children
- .find(child => child.$options._componentTag === 'recaptcha-dialog'); // eslint-disable-line no-underscore-dangle
+ .find(child => child.$options._componentTag === 'recaptcha-modal'); // eslint-disable-line no-underscore-dangle
recaptchaChild.scriptSrc = '//scriptsrc';
}
@@ -302,7 +302,7 @@ describe('Issuable output', () => {
.then(promise)
.then(() => setTimeoutPromise())
.then(() => {
- modal = vm.$el.querySelector('.js-recaptcha-dialog');
+ modal = vm.$el.querySelector('.js-recaptcha-modal');
expect(modal.style.display).not.toEqual('none');
expect(modal.querySelector('.g-recaptcha').textContent).toEqual('recaptcha_html');
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 2e000a1063f..0da25bdca9c 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -54,7 +54,7 @@ describe('Description component', () => {
it('opens recaptcha dialog if update rejected as spam', (done) => {
let modal;
const recaptchaChild = vm.$children
- .find(child => child.$options._componentTag === 'recaptcha-dialog'); // eslint-disable-line no-underscore-dangle
+ .find(child => child.$options._componentTag === 'recaptcha-modal'); // eslint-disable-line no-underscore-dangle
recaptchaChild.scriptSrc = '//scriptsrc';
@@ -64,7 +64,7 @@ describe('Description component', () => {
vm.$nextTick()
.then(() => {
- modal = vm.$el.querySelector('.js-recaptcha-dialog');
+ modal = vm.$el.querySelector('.js-recaptcha-modal');
expect(modal.style.display).not.toEqual('none');
expect(modal.querySelector('.g-recaptcha').textContent).toEqual('recaptcha_html');
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 3636aac79a0..2cd2e63b15d 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -55,7 +55,7 @@ describe('Issue', function() {
}
function findElements(isIssueInitiallyOpen) {
- $boxClosed = $('div.status-box-closed');
+ $boxClosed = $('div.status-box-issue-closed');
expect($boxClosed).toExist();
expect($boxClosed).toHaveText('Closed');
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 2612c5fd7bc..e09b8dc7fc5 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -222,7 +222,6 @@ import '~/notes';
notes.note_ids = [];
notes.updatedNotesTrackingMap = {};
- spyOn(gl.utils, 'localTimeAgo');
spyOn(Notes, 'isNewNote').and.callThrough();
spyOn(Notes, 'isUpdatedNote').and.callThrough();
spyOn(Notes, 'animateAppendNote').and.callThrough();
@@ -349,7 +348,6 @@ import '~/notes';
]);
notes.note_ids = [];
- spyOn(gl.utils, 'localTimeAgo');
spyOn(Notes, 'isNewNote');
spyOn(Notes, 'animateAppendNote');
Notes.isNewNote.and.returnValue(true);
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 5505f983d71..72790eb215a 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -41,7 +41,7 @@ import '~/right_sidebar';
loadFixtures(fixtureName);
this.sidebar = new Sidebar;
$aside = $('.right-sidebar');
- $page = $('.page-with-sidebar');
+ $page = $('.layout-page');
$icon = $aside.find('i');
$toggle = $aside.find('.js-sidebar-toggle');
return $labelsIcon = $aside.find('.sidebar-collapsed-icon');
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 1e4c2c9faad..206f95abc1a 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable space-before-function-paren, max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, comma-dangle, object-shorthand, prefer-template, quotes, new-parens, vars-on-top, new-cap, max-len */
import '~/gl_dropdown';
-import '~/search_autocomplete';
+import SearchAutocomplete from '~/search_autocomplete';
import '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility';
@@ -128,7 +128,7 @@ import * as urlUtils from '~/lib/utils/url_utility';
window.gon.current_user_id = userId;
window.gon.current_username = userName;
- return widget = new gl.SearchAutocomplete;
+ return widget = new SearchAutocomplete();
});
afterEach(function() {
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
index 946f98379ce..763a15e710b 100644
--- a/spec/javascripts/syntax_highlight_spec.js
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -1,44 +1,42 @@
/* eslint-disable space-before-function-paren, no-var, no-return-assign, quotes */
-import '~/syntax_highlight';
+import syntaxHighlight from '~/syntax_highlight';
-(function() {
- describe('Syntax Highlighter', function() {
- var stubUserColorScheme;
- stubUserColorScheme = function(value) {
- if (window.gon == null) {
- window.gon = {};
- }
- return window.gon.user_color_scheme = value;
- };
- describe('on a js-syntax-highlight element', function() {
- beforeEach(function() {
- return setFixtures('<div class="js-syntax-highlight"></div>');
- });
- return it('applies syntax highlighting', function() {
- stubUserColorScheme('monokai');
- $('.js-syntax-highlight').syntaxHighlight();
- return expect($('.js-syntax-highlight')).toHaveClass('monokai');
- });
+describe('Syntax Highlighter', function() {
+ var stubUserColorScheme;
+ stubUserColorScheme = function(value) {
+ if (window.gon == null) {
+ window.gon = {};
+ }
+ return window.gon.user_color_scheme = value;
+ };
+ describe('on a js-syntax-highlight element', function() {
+ beforeEach(function() {
+ return setFixtures('<div class="js-syntax-highlight"></div>');
});
- return describe('on a parent element', function() {
- beforeEach(function() {
- return setFixtures("<div class=\"parent\">\n <div class=\"js-syntax-highlight\"></div>\n <div class=\"foo\"></div>\n <div class=\"js-syntax-highlight\"></div>\n</div>");
- });
- it('applies highlighting to all applicable children', function() {
- stubUserColorScheme('monokai');
- $('.parent').syntaxHighlight();
- expect($('.parent, .foo')).not.toHaveClass('monokai');
- return expect($('.monokai').length).toBe(2);
- });
- return it('prevents an infinite loop when no matches exist', function() {
- var highlight;
- setFixtures('<div></div>');
- highlight = function() {
- return $('div').syntaxHighlight();
- };
- return expect(highlight).not.toThrow();
- });
+ return it('applies syntax highlighting', function() {
+ stubUserColorScheme('monokai');
+ syntaxHighlight($('.js-syntax-highlight'));
+ return expect($('.js-syntax-highlight')).toHaveClass('monokai');
});
});
-}).call(window);
+ return describe('on a parent element', function() {
+ beforeEach(function() {
+ return setFixtures("<div class=\"parent\">\n <div class=\"js-syntax-highlight\"></div>\n <div class=\"foo\"></div>\n <div class=\"js-syntax-highlight\"></div>\n</div>");
+ });
+ it('applies highlighting to all applicable children', function() {
+ stubUserColorScheme('monokai');
+ syntaxHighlight($('.parent'));
+ expect($('.parent, .foo')).not.toHaveClass('monokai');
+ return expect($('.monokai').length).toBe(2);
+ });
+ return it('prevents an infinite loop when no matches exist', function() {
+ var highlight;
+ setFixtures('<div></div>');
+ highlight = function() {
+ return syntaxHighlight($('div'));
+ };
+ return expect(highlight).not.toThrow();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
index d5b8947c86f..db7d083065b 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
+import { getTimeago } from '~/lib/utils/datetime_utility';
const deploymentMockData = [
{
@@ -49,7 +50,7 @@ describe('MRWidgetDeployment', () => {
describe('formatDate', () => {
it('should work', () => {
- const readable = gl.utils.getTimeago().format(deployment.deployed_at);
+ const readable = getTimeago().format(deployment.deployed_at);
expect(vm.formatDate(deployment.deployed_at)).toEqual(readable);
});
});
diff --git a/spec/javascripts/vue_shared/components/popup_dialog_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js
index 5c1d2a196f4..721f4044659 100644
--- a/spec/javascripts/vue_shared/components/popup_dialog_spec.js
+++ b/spec/javascripts/vue_shared/components/modal_spec.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
-import PopupDialog from '~/vue_shared/components/popup_dialog.vue';
+import modal from '~/vue_shared/components/modal.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
-describe('PopupDialog', () => {
+describe('Modal', () => {
it('does not render a primary button if no primaryButtonLabel', () => {
- const popupDialog = Vue.extend(PopupDialog);
- const vm = mountComponent(popupDialog);
+ const modalComponent = Vue.extend(modal);
+ const vm = mountComponent(modalComponent);
expect(vm.$el.querySelector('.js-primary-button')).toBeNull();
});
diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
index b4c1f70ed1e..b4fb568f1d4 100644
--- a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
+++ b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import '~/lib/utils/datetime_utility';
+import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
describe('Time ago with tooltip component', () => {
let TimeagoTooltip;
@@ -24,10 +24,10 @@ describe('Time ago with tooltip component', () => {
expect(vm.$el.tagName).toEqual('TIME');
expect(
vm.$el.getAttribute('data-original-title'),
- ).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z'));
+ ).toEqual(formatDate('2017-05-08T14:57:39.781Z'));
expect(vm.$el.getAttribute('data-placement')).toEqual('top');
- const timeago = gl.utils.getTimeago();
+ const timeago = getTimeago();
expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z'));
});
diff --git a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb
index 79d2c071446..e1c4f9cfea7 100644
--- a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb
+++ b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb
@@ -2,19 +2,20 @@ require 'spec_helper'
describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migration, schema: 20170929131201 do
let(:migration) { described_class.new }
+ let(:projects) { table(:projects) }
- let(:base1) { create(:project) }
- let(:base1_fork1) { create(:project) }
- let(:base1_fork2) { create(:project) }
+ let(:base1) { projects.create }
+ let(:base1_fork1) { projects.create }
+ let(:base1_fork2) { projects.create }
- let(:base2) { create(:project) }
- let(:base2_fork1) { create(:project) }
- let(:base2_fork2) { create(:project) }
+ let(:base2) { projects.create }
+ let(:base2_fork1) { projects.create }
+ let(:base2_fork2) { projects.create }
- let(:fork_of_fork) { create(:project) }
- let(:fork_of_fork2) { create(:project) }
- let(:second_level_fork) { create(:project) }
- let(:third_level_fork) { create(:project) }
+ let(:fork_of_fork) { projects.create }
+ let(:fork_of_fork2) { projects.create }
+ let(:second_level_fork) { projects.create }
+ let(:third_level_fork) { projects.create }
let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) }
let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) }
@@ -97,7 +98,7 @@ describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migrat
end
it 'does not miss members for forks of forks for which the root was deleted' do
- forked_project_links.create(id: 9, forked_from_project_id: base1_fork1.id, forked_to_project_id: create(:project).id)
+ forked_project_links.create(id: 9, forked_from_project_id: base1_fork1.id, forked_to_project_id: projects.create.id)
base1.destroy
expect(migration.missing_members?(7, 10)).to be_falsy
@@ -105,8 +106,8 @@ describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migrat
context 'with more forks' do
before do
- forked_project_links.create(id: 9, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id)
- forked_project_links.create(id: 10, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id)
+ forked_project_links.create(id: 9, forked_from_project_id: fork_of_fork.id, forked_to_project_id: projects.create.id)
+ forked_project_links.create(id: 10, forked_from_project_id: fork_of_fork.id, forked_to_project_id: projects.create.id)
end
it 'only processes a single batch of links at a time' do
diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
index cb52d971047..3998ca940a4 100644
--- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
@@ -225,7 +225,8 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migrati
let(:user_class) { table(:users) }
let(:author) { build(:user).becomes(user_class).tap(&:save!).becomes(User) }
let(:namespace) { create(:namespace, owner: author) }
- let(:project) { create(:project_empty_repo, namespace: namespace, creator: author) }
+ let(:projects) { table(:projects) }
+ let(:project) { projects.create(namespace_id: namespace.id, creator_id: author.id) }
# We can not rely on FactoryGirl as the state of Event may change in ways that
# the background migration does not expect, hence we use the Event class of
diff --git a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb
index e52baf8dde7..8582af96199 100644
--- a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb
@@ -2,10 +2,11 @@ require 'spec_helper'
describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, schema: 20170929131201 do
let(:migration) { described_class.new }
- let(:base1) { create(:project) }
+ let(:projects) { table(:projects) }
+ let(:base1) { projects.create }
- let(:base2) { create(:project) }
- let(:base2_fork1) { create(:project) }
+ let(:base2) { projects.create }
+ let(:base2_fork1) { projects.create }
let!(:forked_project_links) { table(:forked_project_links) }
let!(:fork_networks) { table(:fork_networks) }
@@ -18,10 +19,10 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch
# A normal fork link
forked_project_links.create(id: 1,
forked_from_project_id: base1.id,
- forked_to_project_id: create(:project).id)
+ forked_to_project_id: projects.create.id)
forked_project_links.create(id: 2,
forked_from_project_id: base1.id,
- forked_to_project_id: create(:project).id)
+ forked_to_project_id: projects.create.id)
forked_project_links.create(id: 3,
forked_from_project_id: base2.id,
forked_to_project_id: base2_fork1.id)
@@ -29,10 +30,10 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch
# create a fork of a fork
forked_project_links.create(id: 4,
forked_from_project_id: base2_fork1.id,
- forked_to_project_id: create(:project).id)
+ forked_to_project_id: projects.create.id)
forked_project_links.create(id: 5,
- forked_from_project_id: create(:project).id,
- forked_to_project_id: create(:project).id)
+ forked_from_project_id: projects.create.id,
+ forked_to_project_id: projects.create.id)
# Stub out the calls to the other migrations
allow(BackgroundMigrationWorker).to receive(:perform_in)
@@ -63,7 +64,7 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch
end
it 'creates a fork network for the fork of which the source was deleted' do
- fork = create(:project)
+ fork = projects.create
forked_project_links.create(id: 6, forked_from_project_id: 99999, forked_to_project_id: fork.id)
migration.perform(5, 8)
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
index b80df6956b0..be45c00dfe6 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
@@ -182,13 +182,22 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq do
end
context 'for a pre-Markdown Note attachment file path' do
- class Note < ActiveRecord::Base
- has_many :uploads, as: :model, dependent: :destroy
+ let(:model) { create(:note, :with_attachment) }
+ let!(:expected_upload_attrs) { Upload.where(model_type: 'Note', model_id: model.id).first.attributes.slice('path', 'uploader', 'size', 'checksum') }
+ let!(:untracked_file) { untracked_files_for_uploads.create!(path: expected_upload_attrs['path']) }
+
+ before do
+ Upload.where(model_type: 'Note', model_id: model.id).delete_all
end
- let(:model) { create(:note, :with_attachment) }
+ # Can't use the shared example because Note doesn't have an `uploads` association
+ it 'creates an Upload record' do
+ expect do
+ subject.perform(1, untracked_files_for_uploads.last.id)
+ end.to change { Upload.where(model_type: 'Note', model_id: model.id).count }.from(0).to(1)
- it_behaves_like 'non_markdown_file'
+ expect(Upload.where(model_type: 'Note', model_id: model.id).first.attributes).to include(expected_upload_attrs)
+ end
end
context 'for a user avatar file path' do
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index 5341addf911..78767d06462 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -20,9 +20,39 @@ describe Gitlab::Metrics::MethodCall do
context 'prometheus instrumentation is enabled' do
before do
+ allow(Feature.get(:prometheus_metrics_method_instrumentation)).to receive(:enabled?).and_call_original
+ described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1
Feature.get(:prometheus_metrics_method_instrumentation).enable
end
+ around do |example|
+ Timecop.freeze do
+ example.run
+ end
+ end
+
+ it 'caches subsequent invocations of feature check' do
+ 10.times do
+ method_call.measure { 'foo' }
+ end
+
+ expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).once
+ end
+
+ it 'expires feature check cache after 1 minute' do
+ method_call.measure { 'foo' }
+
+ Timecop.travel(1.minute.from_now) do
+ method_call.measure { 'foo' }
+ end
+
+ Timecop.travel(1.minute.from_now + 1.second) do
+ method_call.measure { 'foo' }
+ end
+
+ expect(Feature.get(:prometheus_metrics_method_instrumentation)).to have_received(:enabled?).twice
+ end
+
it 'observes the performance of the supplied block' do
expect(described_class.call_duration_histogram)
.to receive(:observe)
@@ -34,6 +64,8 @@ describe Gitlab::Metrics::MethodCall do
context 'prometheus instrumentation is disabled' do
before do
+ described_class.measurement_enabled_cache_expires_at.value = Time.now.to_i - 1
+
Feature.get(:prometheus_metrics_method_instrumentation).disable
end
diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb
index 09f95be2213..0c66d764851 100644
--- a/spec/lib/gitlab/sidekiq_config_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config_spec.rb
@@ -16,9 +16,30 @@ describe Gitlab::SidekiqConfig do
expect(queues).to include('post_receive')
expect(queues).to include('merge')
- expect(queues).to include('cronjob')
+ expect(queues).to include('cronjob:stuck_import_jobs')
expect(queues).to include('mailers')
expect(queues).to include('default')
end
end
+
+ describe '.expand_queues' do
+ it 'expands queue namespaces to concrete queue names' do
+ queues = described_class.expand_queues(%w[cronjob])
+
+ expect(queues).to include('cronjob:stuck_import_jobs')
+ expect(queues).to include('cronjob:stuck_merge_jobs')
+ end
+
+ it 'lets concrete queue names pass through' do
+ queues = described_class.expand_queues(%w[post_receive])
+
+ expect(queues).to include('post_receive')
+ end
+
+ it 'lets unknown queues pass through' do
+ queues = described_class.expand_queues(%w[unknown])
+
+ expect(queues).to include('unknown')
+ end
+ end
end
diff --git a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
new file mode 100644
index 00000000000..7debf70a16f
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqVersioning::Manager do
+ before do
+ Sidekiq::Manager.prepend described_class
+ end
+
+ describe '#initialize' do
+ it 'listens on all expanded queues' do
+ manager = Sidekiq::Manager.new(queues: %w[post_receive repository_fork cronjob unknown])
+
+ queues = manager.options[:queues]
+
+ expect(queues).to include('post_receive')
+ expect(queues).to include('repository_fork')
+ expect(queues).to include('cronjob')
+ expect(queues).to include('cronjob:stuck_import_jobs')
+ expect(queues).to include('cronjob:stuck_merge_jobs')
+ expect(queues).to include('unknown')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_versioning_spec.rb b/spec/lib/gitlab/sidekiq_versioning_spec.rb
new file mode 100644
index 00000000000..fa6d42e730d
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_versioning_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqVersioning, :sidekiq, :redis do
+ let(:foo_worker) do
+ Class.new do
+ def self.name
+ 'FooWorker'
+ end
+
+ include ApplicationWorker
+ end
+ end
+
+ let(:bar_worker) do
+ Class.new do
+ def self.name
+ 'BarWorker'
+ end
+
+ include ApplicationWorker
+ end
+ end
+
+ before do
+ allow(Gitlab::SidekiqConfig).to receive(:workers).and_return([foo_worker, bar_worker])
+ allow(Gitlab::SidekiqConfig).to receive(:worker_queues).and_return([foo_worker.queue, bar_worker.queue])
+ end
+
+ describe '.install!' do
+ it 'prepends SidekiqVersioning::Manager into Sidekiq::Manager' do
+ described_class.install!
+
+ expect(Sidekiq::Manager).to include(Gitlab::SidekiqVersioning::Manager)
+ end
+
+ it 'registers all versionless and versioned queues with Redis' do
+ described_class.install!
+
+ queues = Sidekiq::Queue.all.map(&:name)
+ expect(queues).to include('foo')
+ expect(queues).to include('bar')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tcp_checker_spec.rb b/spec/lib/gitlab/tcp_checker_spec.rb
new file mode 100644
index 00000000000..4acf0334496
--- /dev/null
+++ b/spec/lib/gitlab/tcp_checker_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::TcpChecker do
+ before do
+ @server = TCPServer.new('localhost', 0)
+ _, @port, _, @ip = @server.addr
+ end
+
+ after do
+ @server.close
+ end
+
+ subject(:checker) { described_class.new(@ip, @port) }
+
+ describe '#check' do
+ subject { checker.check }
+
+ it 'can connect to an open port' do
+ is_expected.to be_truthy
+
+ expect(checker.error).to be_nil
+ end
+
+ it 'fails to connect to a closed port' do
+ @server.close
+
+ is_expected.to be_falsy
+
+ expect(checker.error).to be_a(Errno::ECONNREFUSED)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils/strong_memoize_spec.rb b/spec/lib/gitlab/utils/strong_memoize_spec.rb
index 4a104ab6d97..473f8100771 100644
--- a/spec/lib/gitlab/utils/strong_memoize_spec.rb
+++ b/spec/lib/gitlab/utils/strong_memoize_spec.rb
@@ -49,4 +49,16 @@ describe Gitlab::Utils::StrongMemoize do
end
end
end
+
+ describe '#clear_memoization' do
+ let(:value) { 'mepmep' }
+
+ it 'removes the instance variable' do
+ object.method_name
+
+ object.clear_memoization(:method_name)
+
+ expect(object.instance_variable_defined?(:@method_name)).to be(false)
+ end
+ end
end
diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb
index 70d2e22b48f..6120bafb2e3 100644
--- a/spec/lib/gitlab/view/presenter/factory_spec.rb
+++ b/spec/lib/gitlab/view/presenter/factory_spec.rb
@@ -27,5 +27,13 @@ describe Gitlab::View::Presenter::Factory do
expect(presenter).to be_a(Ci::BuildPresenter)
end
+
+ it 'uses the presenter_class if given on #initialize' do
+ MyCustomPresenter = Class.new(described_class)
+
+ presenter = described_class.new(build, presenter_class: MyCustomPresenter).fabricate!
+
+ expect(presenter).to be_a(MyCustomPresenter)
+ end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index e1d71a9573b..4d0a3942996 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -342,6 +342,46 @@ describe Notify do
end
end
+ context 'for issue notes' do
+ let(:host) { Gitlab.config.gitlab.host }
+
+ context 'in discussion' do
+ set(:first_note) { create(:discussion_note_on_issue) }
+ set(:second_note) { create(:discussion_note_on_issue, in_reply_to: first_note) }
+ set(:third_note) { create(:discussion_note_on_issue, in_reply_to: second_note) }
+
+ subject { described_class.note_issue_email(recipient.id, third_note.id) }
+
+ it 'has In-Reply-To header pointing to previous note in discussion' do
+ expect(subject.header['In-Reply-To'].message_ids).to eq(["note_#{second_note.id}@#{host}"])
+ end
+
+ it 'has References header including the notes and issue of the discussion' do
+ expect(subject.header['References'].message_ids).to include("issue_#{first_note.noteable.id}@#{host}",
+ "note_#{first_note.id}@#{host}",
+ "note_#{second_note.id}@#{host}")
+ end
+
+ it 'has X-GitLab-Discussion-ID header' do
+ expect(subject.header['X-GitLab-Discussion-ID'].value).to eq(third_note.discussion.id)
+ end
+ end
+
+ context 'individual issue comments' do
+ set(:note) { create(:note_on_issue) }
+
+ subject { described_class.note_issue_email(recipient.id, note.id) }
+
+ it 'has In-Reply-To header pointing to the issue' do
+ expect(subject.header['In-Reply-To'].message_ids).to eq(["issue_#{note.noteable.id}@#{host}"])
+ end
+
+ it 'has References header including the notes and issue of the discussion' do
+ expect(subject.header['References'].message_ids).to include("issue_#{note.noteable.id}@#{host}")
+ end
+ end
+ end
+
context 'for snippet notes' do
let(:project_snippet) { create(:project_snippet, project: project) }
let(:project_snippet_note) { create(:note_on_project_snippet, project: project, noteable: project_snippet) }
diff --git a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb b/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb
index 05f281fffff..57ee2adaaff 100644
--- a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb
+++ b/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb
@@ -2,9 +2,10 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20171013104327_migrate_gcp_clusters_to_new_clusters_architectures.rb')
describe MigrateGcpClustersToNewClustersArchitectures, :migration do
- let(:project) { create(:project) }
+ let(:projects) { table(:projects) }
+ let(:project) { projects.create }
let(:user) { create(:user) }
- let(:service) { create(:kubernetes_service, project: project) }
+ let(:service) { create(:kubernetes_service, project_id: project.id) }
context 'when cluster is being created' do
let(:project_id) { project.id }
@@ -56,8 +57,7 @@ describe MigrateGcpClustersToNewClustersArchitectures, :migration do
expect(cluster.provider_type).to eq('gcp')
expect(cluster.platform_type).to eq('kubernetes')
- expect(cluster.project).to eq(project)
- expect(project.clusters).to include(cluster)
+ expect(cluster.project_ids).to include(project.id)
expect(cluster.provider_gcp.cluster).to eq(cluster)
expect(cluster.provider_gcp.status).to eq(status)
@@ -133,8 +133,7 @@ describe MigrateGcpClustersToNewClustersArchitectures, :migration do
expect(cluster.provider_type).to eq('gcp')
expect(cluster.platform_type).to eq('kubernetes')
- expect(cluster.project).to eq(project)
- expect(project.clusters).to include(cluster)
+ expect(cluster.project_ids).to include(project.id)
expect(cluster.provider_gcp.cluster).to eq(cluster)
expect(cluster.provider_gcp.status).to eq(status)
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index c5e23532aa5..871e8b47650 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1861,9 +1861,9 @@ describe Ci::Build do
describe 'state transition: any => [:running]' do
shared_examples 'validation is active' do
context 'when depended job has not been completed yet' do
- let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
+ let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
- it { expect { job.run! }.to raise_error(Ci::Build::MissingDependenciesError) }
+ it { expect { job.run! }.not_to raise_error(Ci::Build::MissingDependenciesError) }
end
context 'when artifacts of depended job has been expired' do
@@ -1885,11 +1885,10 @@ describe Ci::Build do
shared_examples 'validation is not active' do
context 'when depended job has not been completed yet' do
- let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
+ let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
it { expect { job.run! }.not_to raise_error }
end
-
context 'when artifacts of depended job has been expired' do
let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 129dfa07f15..3c7f578975b 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -102,6 +102,26 @@ describe CacheMarkdownField do
it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_VERSION) }
end
+ context 'when a markdown field is set repeatedly to an empty string' do
+ it do
+ expect(thing).to receive(:refresh_markdown_cache).once
+ thing.foo = ''
+ thing.save
+ thing.foo = ''
+ thing.save
+ end
+ end
+
+ context 'when a markdown field is set repeatedly to a string which renders as empty html' do
+ it do
+ expect(thing).to receive(:refresh_markdown_cache).once
+ thing.foo = '[//]: # (This is also a comment.)'
+ thing.save
+ thing.foo = '[//]: # (This is also a comment.)'
+ thing.save
+ end
+ end
+
context 'a non-markdown field changed' do
before do
thing.bar = 'OK'
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 30a5a3bbff7..bb63abd167b 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -124,6 +124,7 @@ describe MergeRequest do
context 'when the target branch does not exist' do
before do
project.repository.rm_branch(subject.author, subject.target_branch)
+ subject.clear_memoized_shas
end
it 'returns nil' do
@@ -600,30 +601,30 @@ describe MergeRequest do
end
describe '#can_remove_source_branch?' do
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
+ set(:user) { create(:user) }
+ set(:merge_request) { create(:merge_request, :simple) }
- before do
- subject.source_project.team << [user, :master]
+ subject { merge_request }
- subject.source_branch = "feature"
- subject.target_branch = "master"
- subject.save!
+ before do
+ subject.source_project.add_master(user)
end
it "can't be removed when its a protected branch" do
allow(ProtectedBranch).to receive(:protected?).and_return(true)
+
expect(subject.can_remove_source_branch?(user)).to be_falsey
end
it "can't remove a root ref" do
- subject.source_branch = "master"
- subject.target_branch = "feature"
+ subject.update(source_branch: 'master', target_branch: 'feature')
expect(subject.can_remove_source_branch?(user)).to be_falsey
end
it "is unable to remove the source branch for a project the user cannot push to" do
+ user2 = create(:user)
+
expect(subject.can_remove_source_branch?(user2)).to be_falsey
end
@@ -634,6 +635,7 @@ describe MergeRequest do
end
it "cannot be removed if the last commit is not also the head of the source branch" do
+ subject.clear_memoized_shas
subject.source_branch = "lfs"
expect(subject.can_remove_source_branch?(user)).to be_falsey
@@ -733,7 +735,7 @@ describe MergeRequest do
before do
project.repository.raw_repository.delete_branch(subject.target_branch)
- subject.reload
+ subject.clear_memoized_shas
end
it 'does not crash' do
@@ -1404,6 +1406,16 @@ describe MergeRequest do
subject.reload_diff
end
+
+ context 'when using the after_update hook to update' do
+ context 'when the branches are updated' do
+ it 'uses the new heads to generate the diff' do
+ expect { subject.update!(source_branch: subject.target_branch, target_branch: subject.source_branch) }
+ .to change { subject.merge_request_diff.start_commit_sha }
+ .and change { subject.merge_request_diff.head_commit_sha }
+ end
+ end
+ end
end
describe '#update_diff_discussion_positions' do
@@ -1468,6 +1480,7 @@ describe MergeRequest do
context 'when the target branch does not exist' do
before do
subject.project.repository.rm_branch(subject.author, subject.target_branch)
+ subject.clear_memoized_shas
end
it 'returns nil' do
@@ -1855,4 +1868,20 @@ describe MergeRequest do
it_behaves_like 'throttled touch' do
subject { create(:merge_request, updated_at: 1.hour.ago) }
end
+
+ context 'state machine transitions' do
+ describe '#unlock_mr' do
+ subject { create(:merge_request, state: 'locked', merge_jid: 123) }
+
+ it 'updates merge request head pipeline and sets merge_jid to nil' do
+ pipeline = create(:ci_empty_pipeline, project: subject.project, ref: subject.source_branch, sha: subject.source_branch_sha)
+
+ subject.unlock_mr
+
+ subject.reload
+ expect(subject.head_pipeline).to eq(pipeline)
+ expect(subject.merge_jid).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index e1a0c55b6a6..cefbf60b28c 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -756,6 +756,28 @@ describe Note do
end
end
+ describe '#references' do
+ context 'when part of a discussion' do
+ it 'references all earlier notes in the discussion' do
+ first_note = create(:discussion_note_on_issue)
+ second_note = create(:discussion_note_on_issue, in_reply_to: first_note)
+ third_note = create(:discussion_note_on_issue, in_reply_to: second_note)
+ create(:discussion_note_on_issue, in_reply_to: third_note)
+
+ expect(third_note.references).to eq([first_note.noteable, first_note, second_note])
+ end
+ end
+
+ context 'when not part of a discussion' do
+ subject { create(:note) }
+ let(:note) { create(:note, in_reply_to: subject) }
+
+ it 'returns the noteable' do
+ expect(note.references).to eq([note.noteable])
+ end
+ end
+ end
+
describe 'expiring ETag cache' do
let(:note) { build(:note_on_issue) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f4699fd243d..dd9e8498519 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1863,10 +1863,11 @@ describe Project do
project.change_head(project.default_branch)
end
- it 'creates the new reference with rugged' do
- expect(project.repository.rugged.references).to receive(:create).with('HEAD',
+ it 'creates the new reference' do
+ expect(project.repository.raw_repository).to receive(:write_ref).with('HEAD',
"refs/heads/#{project.default_branch}",
force: true)
+
project.change_head(project.default_branch)
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 358bc3dfb94..f0661b0a972 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -59,12 +59,18 @@ describe Repository do
end
describe 'tags_sorted_by' do
- context 'name' do
- subject { repository.tags_sorted_by('name').map(&:name) }
+ context 'name_desc' do
+ subject { repository.tags_sorted_by('name_desc').map(&:name) }
it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
end
+ context 'name_asc' do
+ subject { repository.tags_sorted_by('name_asc').map(&:name) }
+
+ it { is_expected.to eq(['v1.0.0', 'v1.1.0']) }
+ end
+
context 'updated' do
let(:tag_a) { repository.find_tag('v1.0.0') }
let(:tag_b) { repository.find_tag('v1.1.0') }
@@ -1916,6 +1922,23 @@ describe Repository do
File.delete(path)
end
+
+ it "attempting to call keep_around when exists a lock does not fail" do
+ ref = repository.send(:keep_around_ref_name, sample_commit.id)
+ path = File.join(repository.path, ref)
+ lock_path = "#{path}.lock"
+
+ FileUtils.mkdir_p(File.dirname(path))
+ File.open(lock_path, 'w') { |f| f.write('') }
+
+ begin
+ expect { repository.keep_around(sample_commit.id) }.not_to raise_error(Gitlab::Git::Repository::GitError)
+
+ expect(File.exist?(lock_path)).to be_falsey
+ ensure
+ File.delete(path)
+ end
+ end
end
describe '#update_ref' do
@@ -2343,4 +2366,111 @@ describe Repository do
end
end
end
+
+ describe '#contributors' do
+ let(:author_a) { build(:author, email: 'tiagonbotelho@hotmail.com', name: 'tiagonbotelho') }
+ let(:author_b) { build(:author, email: 'gitlab@winniehell.de', name: 'Winnie') }
+ let(:author_c) { build(:author, email: 'douwe@gitlab.com', name: 'Douwe Maan') }
+ let(:stubbed_commits) do
+ [build(:commit, author: author_a),
+ build(:commit, author: author_a),
+ build(:commit, author: author_b),
+ build(:commit, author: author_c),
+ build(:commit, author: author_c),
+ build(:commit, author: author_c)]
+ end
+ let(:order_by) { nil }
+ let(:sort) { nil }
+
+ before do
+ allow(repository).to receive(:commits).with(nil, limit: 2000, offset: 0, skip_merges: true).and_return(stubbed_commits)
+ end
+
+ subject { repository.contributors(order_by: order_by, sort: sort) }
+
+ def expect_contributors(*contributors)
+ expect(subject.map(&:email)).to eq(contributors.map(&:email))
+ end
+
+ it 'returns the array of Gitlab::Contributor for the repository' do
+ expect_contributors(author_a, author_b, author_c)
+ end
+
+ context 'order_by email' do
+ let(:order_by) { 'email' }
+
+ context 'asc' do
+ let(:sort) { 'asc' }
+
+ it 'returns all the contributors ordered by email asc case insensitive' do
+ expect_contributors(author_c, author_b, author_a)
+ end
+ end
+
+ context 'desc' do
+ let(:sort) { 'desc' }
+
+ it 'returns all the contributors ordered by email desc case insensitive' do
+ expect_contributors(author_a, author_b, author_c)
+ end
+ end
+ end
+
+ context 'order_by name' do
+ let(:order_by) { 'name' }
+
+ context 'asc' do
+ let(:sort) { 'asc' }
+
+ it 'returns all the contributors ordered by name asc case insensitive' do
+ expect_contributors(author_c, author_a, author_b)
+ end
+ end
+
+ context 'desc' do
+ let(:sort) { 'desc' }
+
+ it 'returns all the contributors ordered by name desc case insensitive' do
+ expect_contributors(author_b, author_a, author_c)
+ end
+ end
+ end
+
+ context 'order_by commits' do
+ let(:order_by) { 'commits' }
+
+ context 'asc' do
+ let(:sort) { 'asc' }
+
+ it 'returns all the contributors ordered by commits asc' do
+ expect_contributors(author_b, author_a, author_c)
+ end
+ end
+
+ context 'desc' do
+ let(:sort) { 'desc' }
+
+ it 'returns all the contributors ordered by commits desc' do
+ expect_contributors(author_c, author_a, author_b)
+ end
+ end
+ end
+
+ context 'invalid ordering' do
+ let(:order_by) { 'unknown' }
+
+ it 'returns the contributors unsorted' do
+ expect_contributors(author_a, author_b, author_c)
+ end
+ end
+
+ context 'invalid sorting' do
+ let(:order_by) { 'name' }
+ let(:sort) { 'unknown' }
+
+ it 'returns the contributors unsorted' do
+ expect_contributors(author_a, author_b, author_c)
+ end
+ end
+ end
end
diff --git a/spec/presenters/group_member_presenter_spec.rb b/spec/presenters/group_member_presenter_spec.rb
new file mode 100644
index 00000000000..c00e41725d9
--- /dev/null
+++ b/spec/presenters/group_member_presenter_spec.rb
@@ -0,0 +1,138 @@
+require 'spec_helper'
+
+describe GroupMemberPresenter do
+ let(:user) { double(:user) }
+ let(:group) { double(:group) }
+ let(:group_member) { double(:group_member, source: group) }
+ let(:presenter) { described_class.new(group_member, current_user: user) }
+
+ describe '#can_resend_invite?' do
+ context 'when group_member is invited' do
+ before do
+ expect(group_member).to receive(:invite?).and_return(true)
+ end
+
+ context 'and user can admin_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :admin_group_member, group).and_return(true)
+ end
+
+ it { expect(presenter.can_resend_invite?).to eq(true) }
+ end
+
+ context 'and user cannot admin_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :admin_group_member, group).and_return(false)
+ end
+
+ it { expect(presenter.can_resend_invite?).to eq(false) }
+ end
+ end
+
+ context 'when group_member is not invited' do
+ before do
+ expect(group_member).to receive(:invite?).and_return(false)
+ end
+
+ context 'and user can admin_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :admin_group_member, group).and_return(true)
+ end
+
+ it { expect(presenter.can_resend_invite?).to eq(false) }
+ end
+
+ context 'and user cannot admin_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :admin_group_member, group).and_return(false)
+ end
+
+ it { expect(presenter.can_resend_invite?).to eq(false) }
+ end
+ end
+ end
+
+ describe '#can_update?' do
+ context 'when user can update_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_group_member, presenter).and_return(true)
+ end
+
+ it { expect(presenter.can_update?).to eq(true) }
+ end
+
+ context 'when user cannot update_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_group_member, presenter).and_return(false)
+ allow(presenter).to receive(:can?).with(user, :override_group_member, presenter).and_return(false)
+ end
+
+ it { expect(presenter.can_update?).to eq(false) }
+ end
+ end
+
+ describe '#can_remove?' do
+ context 'when user can destroy_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :destroy_group_member, presenter).and_return(true)
+ end
+
+ it { expect(presenter.can_remove?).to eq(true) }
+ end
+
+ context 'when user cannot destroy_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :destroy_group_member, presenter).and_return(false)
+ end
+
+ it { expect(presenter.can_remove?).to eq(false) }
+ end
+ end
+
+ describe '#can_approve?' do
+ context 'when group_member has request an invite' do
+ before do
+ expect(group_member).to receive(:request?).and_return(true)
+ end
+
+ context 'when user can update_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_group_member, presenter).and_return(true)
+ end
+
+ it { expect(presenter.can_approve?).to eq(true) }
+ end
+
+ context 'when user cannot update_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_group_member, presenter).and_return(false)
+ allow(presenter).to receive(:can?).with(user, :override_group_member, presenter).and_return(false)
+ end
+
+ it { expect(presenter.can_approve?).to eq(false) }
+ end
+ end
+
+ context 'when group_member did not request an invite' do
+ before do
+ expect(group_member).to receive(:request?).and_return(false)
+ end
+
+ context 'when user can update_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_group_member, presenter).and_return(true)
+ end
+
+ it { expect(presenter.can_approve?).to eq(false) }
+ end
+
+ context 'when user cannot update_group_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_group_member, presenter).and_return(false)
+ end
+
+ it { expect(presenter.can_approve?).to eq(false) }
+ end
+ end
+ end
+end
diff --git a/spec/presenters/project_member_presenter_spec.rb b/spec/presenters/project_member_presenter_spec.rb
new file mode 100644
index 00000000000..83db5c56cdf
--- /dev/null
+++ b/spec/presenters/project_member_presenter_spec.rb
@@ -0,0 +1,138 @@
+require 'spec_helper'
+
+describe ProjectMemberPresenter do
+ let(:user) { double(:user) }
+ let(:project) { double(:project) }
+ let(:project_member) { double(:project_member, source: project) }
+ let(:presenter) { described_class.new(project_member, current_user: user) }
+
+ describe '#can_resend_invite?' do
+ context 'when project_member is invited' do
+ before do
+ expect(project_member).to receive(:invite?).and_return(true)
+ end
+
+ context 'and user can admin_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :admin_project_member, project).and_return(true)
+ end
+
+ it { expect(presenter.can_resend_invite?).to eq(true) }
+ end
+
+ context 'and user cannot admin_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :admin_project_member, project).and_return(false)
+ end
+
+ it { expect(presenter.can_resend_invite?).to eq(false) }
+ end
+ end
+
+ context 'when project_member is not invited' do
+ before do
+ expect(project_member).to receive(:invite?).and_return(false)
+ end
+
+ context 'and user can admin_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :admin_project_member, project).and_return(true)
+ end
+
+ it { expect(presenter.can_resend_invite?).to eq(false) }
+ end
+
+ context 'and user cannot admin_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :admin_project_member, project).and_return(false)
+ end
+
+ it { expect(presenter.can_resend_invite?).to eq(false) }
+ end
+ end
+ end
+
+ describe '#can_update?' do
+ context 'when user can update_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_project_member, presenter).and_return(true)
+ end
+
+ it { expect(presenter.can_update?).to eq(true) }
+ end
+
+ context 'when user cannot update_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_project_member, presenter).and_return(false)
+ allow(presenter).to receive(:can?).with(user, :override_project_member, presenter).and_return(false)
+ end
+
+ it { expect(presenter.can_update?).to eq(false) }
+ end
+ end
+
+ describe '#can_remove?' do
+ context 'when user can destroy_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :destroy_project_member, presenter).and_return(true)
+ end
+
+ it { expect(presenter.can_remove?).to eq(true) }
+ end
+
+ context 'when user cannot destroy_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :destroy_project_member, presenter).and_return(false)
+ end
+
+ it { expect(presenter.can_remove?).to eq(false) }
+ end
+ end
+
+ describe '#can_approve?' do
+ context 'when project_member has request an invite' do
+ before do
+ expect(project_member).to receive(:request?).and_return(true)
+ end
+
+ context 'and user can update_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_project_member, presenter).and_return(true)
+ end
+
+ it { expect(presenter.can_approve?).to eq(true) }
+ end
+
+ context 'and user cannot update_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_project_member, presenter).and_return(false)
+ allow(presenter).to receive(:can?).with(user, :override_project_member, presenter).and_return(false)
+ end
+
+ it { expect(presenter.can_approve?).to eq(false) }
+ end
+ end
+
+ context 'when project_member did not request an invite' do
+ before do
+ expect(project_member).to receive(:request?).and_return(false)
+ end
+
+ context 'and user can update_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_project_member, presenter).and_return(true)
+ end
+
+ it { expect(presenter.can_approve?).to eq(false) }
+ end
+
+ context 'and user cannot update_project_member' do
+ before do
+ allow(presenter).to receive(:can?).with(user, :update_project_member, presenter).and_return(false)
+ end
+
+ it { expect(presenter.can_approve?).to eq(false) }
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 9f2ff3b5af6..741800ff61d 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -378,6 +378,28 @@ describe API::Repositories do
expect(first_contributor['additions']).to eq(0)
expect(first_contributor['deletions']).to eq(0)
end
+
+ context 'using sorting' do
+ context 'by commits desc' do
+ it 'returns the repository contribuors sorted by commits desc' do
+ get api(route, current_user), { order_by: 'commits', sort: 'desc' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('contributors')
+ expect(json_response.first['commits']).to be > json_response.last['commits']
+ end
+ end
+
+ context 'by name desc' do
+ it 'returns the repository contribuors sorted by name asc case insensitive' do
+ get api(route, current_user), { order_by: 'name', sort: 'asc' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('contributors')
+ expect(json_response.first['name'].downcase).to be < json_response.last['name'].downcase
+ end
+ end
+ end
end
context 'when unauthenticated', 'and project is public' do
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 0bf7863bdc8..e2b19ad59f9 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -16,6 +16,44 @@ describe API::Tags do
describe 'GET /projects/:id/repository/tags' do
let(:route) { "/projects/#{project_id}/repository/tags" }
+ context 'sorting' do
+ let(:current_user) { user }
+
+ it 'sorts by descending order by default' do
+ get api(route, current_user)
+
+ desc_order_tags = project.repository.tags.sort_by { |tag| tag.dereferenced_target.committed_date }
+ desc_order_tags.reverse!.map! { |tag| tag.dereferenced_target.id }
+
+ expect(json_response.map { |tag| tag['commit']['id'] }).to eq(desc_order_tags)
+ end
+
+ it 'sorts by ascending order if specified' do
+ get api("#{route}?sort=asc", current_user)
+
+ asc_order_tags = project.repository.tags.sort_by { |tag| tag.dereferenced_target.committed_date }
+ asc_order_tags.map! { |tag| tag.dereferenced_target.id }
+
+ expect(json_response.map { |tag| tag['commit']['id'] }).to eq(asc_order_tags)
+ end
+
+ it 'sorts by name in descending order when requested' do
+ get api("#{route}?order_by=name", current_user)
+
+ ordered_by_name = project.repository.tags.map { |tag| tag.name }.sort.reverse
+
+ expect(json_response.map { |tag| tag['name'] }).to eq(ordered_by_name)
+ end
+
+ it 'sorts by name in ascending order when requested' do
+ get api("#{route}?order_by=name&sort=asc", current_user)
+
+ ordered_by_name = project.repository.tags.map { |tag| tag.name }.sort
+
+ expect(json_response.map { |tag| tag['name'] }).to eq(ordered_by_name)
+ end
+ end
+
shared_examples_for 'repository tags' do
it 'returns the repository tags' do
get api(route, current_user)
diff --git a/spec/rubocop/cop/include_sidekiq_worker_spec.rb b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
new file mode 100644
index 00000000000..7f406535dda
--- /dev/null
+++ b/spec/rubocop/cop/include_sidekiq_worker_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/include_sidekiq_worker'
+
+describe RuboCop::Cop::IncludeSidekiqWorker do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when `Sidekiq::Worker` is included' do
+ let(:source) { 'include Sidekiq::Worker' }
+ let(:correct_source) { 'include ApplicationWorker' }
+
+ it 'registers an offense ' do
+ inspect_source(cop, source)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['Sidekiq::Worker'])
+ end
+ end
+
+ it 'autocorrects to the right version' do
+ autocorrected = autocorrect_source(cop, source)
+
+ expect(autocorrected).to eq(correct_source)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/sidekiq_options_queue_spec.rb b/spec/rubocop/cop/sidekiq_options_queue_spec.rb
new file mode 100644
index 00000000000..a31de381631
--- /dev/null
+++ b/spec/rubocop/cop/sidekiq_options_queue_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/sidekiq_options_queue'
+
+describe RuboCop::Cop::SidekiqOptionsQueue do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense when `sidekiq_options` is used with the `queue` option' do
+ inspect_source(cop, 'sidekiq_options queue: "some_queue"')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['queue: "some_queue"'])
+ end
+ end
+
+ it 'does not register an offense when `sidekiq_options` is used with another option' do
+ inspect_source(cop, 'sidekiq_options retry: false')
+
+ expect(cop.offenses).to be_empty
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 41ce81e0651..267258b33a8 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -64,6 +64,18 @@ describe Ci::CreatePipelineService do
create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
end
+ context 'when related merge request is already merged' do
+ let!(:merged_merge_request) do
+ create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project, state: 'merged')
+ end
+
+ it 'does not schedule update head pipeline job' do
+ expect(UpdateHeadPipelineForMergeRequestWorker).not_to receive(:perform_async).with(merged_merge_request.id)
+
+ execute_service
+ end
+ end
+
context 'when the head pipeline sha equals merge request sha' do
it 'updates head pipeline of each merge request' do
merge_request_1
@@ -77,13 +89,13 @@ describe Ci::CreatePipelineService do
end
context 'when the head pipeline sha does not equal merge request sha' do
- it 'raises the ArgumentError error from worker and does not update the head piepeline of MRs' do
+ it 'does not update the head piepeline of MRs' do
merge_request_1
merge_request_2
allow_any_instance_of(Ci::Pipeline).to receive(:latest?).and_return(true)
- expect { execute_service(after: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }.to raise_error(ArgumentError)
+ expect { execute_service(after: 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f') }.not_to raise_error
last_pipeline = Ci::Pipeline.last
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 22fb7ed7215..de8a9ce12ff 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -287,9 +287,9 @@ module Ci
shared_examples 'validation is active' do
context 'when depended job has not been completed yet' do
- let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
+ let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
- it_behaves_like 'not pick'
+ it { expect(subject).to eq(pending_job) }
end
context 'when artifacts of depended job has been expired' do
@@ -309,7 +309,7 @@ module Ci
end
context 'when job object is staled' do
- let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
+ let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
before do
allow_any_instance_of(Ci::Build).to receive(:drop!)
@@ -324,11 +324,10 @@ module Ci
shared_examples 'validation is not active' do
context 'when depended job has not been completed yet' do
- let!(:pre_stage_job) { create(:ci_build, :running, pipeline: pipeline, name: 'test', stage_idx: 0) }
+ let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
it { expect(subject).to eq(pending_job) }
end
-
context 'when artifacts of depended job has been expired' do
let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) }
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index a7ab389b357..623b182b205 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -100,5 +100,17 @@ describe MergeRequests::CreateFromIssueService do
expect(result[:merge_request].target_branch).to eq(project.default_branch)
end
+
+ it 'executes quick actions if the build service sets them in the description' do
+ allow(service).to receive(:merge_request).and_wrap_original do |m, *args|
+ m.call(*args).tap do |merge_request|
+ merge_request.description = "/assign #{user.to_reference}"
+ end
+ end
+
+ result = service.execute
+
+ expect(result[:merge_request].assignee).to eq(user)
+ end
end
end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index f86f1ac2443..c38ddf4612b 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
describe MergeRequests::MergeService do
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
+ set(:user) { create(:user) }
+ set(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) }
let(:project) { merge_request.project }
before do
- project.team << [user, :master]
- project.team << [user2, :developer]
+ project.add_master(user)
+ project.add_developer(user2)
end
describe '#execute' do
diff --git a/spec/support/batch_loader.rb b/spec/support/batch_loader.rb
new file mode 100644
index 00000000000..bb790e660a6
--- /dev/null
+++ b/spec/support/batch_loader.rb
@@ -0,0 +1,5 @@
+RSpec.configure do |config|
+ config.after do
+ BatchLoader::Executor.clear_current
+ end
+end
diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb
index 0145563e0ed..901d77178bc 100644
--- a/spec/workers/concerns/application_worker_spec.rb
+++ b/spec/workers/concerns/application_worker_spec.rb
@@ -17,6 +17,14 @@ describe ApplicationWorker do
end
end
+ describe '.queue_namespace' do
+ it 'sets the queue name based on the class name' do
+ worker.queue_namespace :some_namespace
+
+ expect(worker.queue).to eq('some_namespace:foo_bar_dummy')
+ end
+ end
+
describe '.queue' do
it 'returns the queue name' do
worker.sidekiq_options queue: :some_queue
diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb
index 5049886b55c..4118b9aa194 100644
--- a/spec/workers/concerns/cluster_queue_spec.rb
+++ b/spec/workers/concerns/cluster_queue_spec.rb
@@ -14,6 +14,6 @@ describe ClusterQueue do
it 'sets a default pipelines queue automatically' do
expect(worker.sidekiq_options['queue'])
- .to eq :gcp_cluster
+ .to eq 'gcp_cluster:dummy'
end
end
diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb
index 3ae1c5f54d8..c042a52f41f 100644
--- a/spec/workers/concerns/cronjob_queue_spec.rb
+++ b/spec/workers/concerns/cronjob_queue_spec.rb
@@ -13,7 +13,7 @@ describe CronjobQueue do
end
it 'sets the queue name of a worker' do
- expect(worker.sidekiq_options['queue'].to_s).to eq('cronjob')
+ expect(worker.sidekiq_options['queue'].to_s).to eq('cronjob:dummy')
end
it 'disables retrying of failed jobs' do
diff --git a/spec/workers/concerns/gitlab/github_import/queue_spec.rb b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
index 9c69ee32da1..a96f583aff7 100644
--- a/spec/workers/concerns/gitlab/github_import/queue_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
@@ -11,6 +11,6 @@ describe Gitlab::GithubImport::Queue do
include Gitlab::GithubImport::Queue
end
- expect(worker.sidekiq_options['queue']).to eq('github_importer')
+ expect(worker.sidekiq_options['queue']).to eq('github_importer:dummy')
end
end
diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb
index dd911760948..a312b307fce 100644
--- a/spec/workers/concerns/pipeline_queue_spec.rb
+++ b/spec/workers/concerns/pipeline_queue_spec.rb
@@ -14,15 +14,6 @@ describe PipelineQueue do
it 'sets a default pipelines queue automatically' do
expect(worker.sidekiq_options['queue'])
- .to eq 'pipeline_default'
- end
-
- describe '.enqueue_in' do
- it 'sets a custom sidekiq queue with prefix and group' do
- worker.enqueue_in(group: :processing)
-
- expect(worker.sidekiq_options['queue'])
- .to eq 'pipeline_processing'
- end
+ .to eq 'pipeline_default:dummy'
end
end
diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb
index fdbbfcc90a5..d2eeecfc9a8 100644
--- a/spec/workers/concerns/repository_check_queue_spec.rb
+++ b/spec/workers/concerns/repository_check_queue_spec.rb
@@ -13,7 +13,7 @@ describe RepositoryCheckQueue do
end
it 'sets the queue name of a worker' do
- expect(worker.sidekiq_options['queue'].to_s).to eq('repository_check')
+ expect(worker.sidekiq_options['queue'].to_s).to eq('repository_check:dummy')
end
it 'disables retrying of failed jobs' do
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 7ee0a51a263..9e3b99b3502 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -1,21 +1,36 @@
require 'spec_helper'
describe 'Every Sidekiq worker' do
- it 'includes ApplicationWorker' do
- expect(Gitlab::SidekiqConfig.workers).to all(include(ApplicationWorker))
- end
-
it 'does not use the default queue' do
expect(Gitlab::SidekiqConfig.workers.map(&:queue)).not_to include('default')
end
it 'uses the cronjob queue when the worker runs as a cronjob' do
- expect(Gitlab::SidekiqConfig.cron_workers.map(&:queue)).to all(eq('cronjob'))
+ expect(Gitlab::SidekiqConfig.cron_workers.map(&:queue)).to all(start_with('cronjob:'))
+ end
+
+ it 'has its queue in app/workers/all_queues.yml', :aggregate_failures do
+ file_worker_queues = Gitlab::SidekiqConfig.worker_queues.to_set
+
+ worker_queues = Gitlab::SidekiqConfig.workers.map(&:queue).to_set
+ worker_queues << ActionMailer::DeliveryJob.queue_name
+ worker_queues << 'default'
+
+ missing_from_file = worker_queues - file_worker_queues
+ expect(missing_from_file).to be_empty, "expected #{missing_from_file.to_a.inspect} to be in app/workers/all_queues.yml"
+
+ unncessarily_in_file = file_worker_queues - worker_queues
+ expect(unncessarily_in_file).to be_empty, "expected #{unncessarily_in_file.to_a.inspect} not to be in app/workers/all_queues.yml"
end
- it 'defines the queue in the Sidekiq configuration file' do
- config_queue_names = Gitlab::SidekiqConfig.config_queues.to_set
+ it 'has its queue or namespace in config/sidekiq_queues.yml', :aggregate_failures do
+ config_queues = Gitlab::SidekiqConfig.config_queues.to_set
+
+ Gitlab::SidekiqConfig.workers.each do |worker|
+ queue = worker.queue
+ queue_namespace = queue.split(':').first
- expect(Gitlab::SidekiqConfig.worker_queues).to all(be_in(config_queue_names))
+ expect(config_queues).to include(queue).or(include(queue_namespace))
+ end
end
end
diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb
index f8b55e873df..c2c2a5f9121 100644
--- a/spec/workers/stuck_merge_jobs_worker_spec.rb
+++ b/spec/workers/stuck_merge_jobs_worker_spec.rb
@@ -14,7 +14,6 @@ describe StuckMergeJobsWorker do
mr_with_sha.reload
mr_without_sha.reload
-
expect(mr_with_sha).to be_merged
expect(mr_without_sha).to be_opened
expect(mr_with_sha.merge_jid).to be_present
@@ -24,10 +23,13 @@ describe StuckMergeJobsWorker do
it 'updates merge request to opened when locked but has not been merged' do
allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(%w(123))
merge_request = create(:merge_request, :locked, merge_jid: '123', state: :locked)
+ pipeline = create(:ci_empty_pipeline, project: merge_request.project, ref: merge_request.source_branch, sha: merge_request.source_branch_sha)
worker.perform
- expect(merge_request.reload).to be_opened
+ merge_request.reload
+ expect(merge_request).to be_opened
+ expect(merge_request.head_pipeline).to eq(pipeline)
end
it 'logs updated stuck merge job ids' do
diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
index 522e1566271..9adde5fc21a 100644
--- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
+++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
@@ -22,7 +22,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do
end
it 'does not update head_pipeline_id' do
- expect { subject.perform(merge_request.id) }.to raise_error(ArgumentError)
+ expect { subject.perform(merge_request.id) }.not_to raise_error
expect(merge_request.reload.head_pipeline_id).to eq(nil)
end
diff --git a/vendor/Dockerfile/CONTRIBUTING.md b/vendor/Dockerfile/CONTRIBUTING.md
index 0878db6dd9e..3e98f2e7b5b 100644
--- a/vendor/Dockerfile/CONTRIBUTING.md
+++ b/vendor/Dockerfile/CONTRIBUTING.md
@@ -1,22 +1,15 @@
-The canonical repository for `Dockerfile` templates is
-https://gitlab.com/gitlab-org/Dockerfile.
+## Developer Certificate of Origin + License
-GitLab only mirrors the templates. Please submit your merge requests to
-https://gitlab.com/gitlab-org/Dockerfile.
+By contributing to GitLab B.V., You accept and agree to the following terms and
+conditions for Your present and future Contributions submitted to GitLab B.V.
+Except for the license granted herein to GitLab B.V. and recipients of software
+distributed by GitLab B.V., You reserve all right, title, and interest in and to
+Your Contributions. All Contributions are subject to the following DCO + License
+terms.
-## Contributing
+[DCO + License](https://gitlab.com/gitlab-org/dco/blob/master/README.md)
-Thank you for your interest in contributing to this GitLab project! We welcome
-all contributions. By participating in this project, you agree to abide by the
-[code of conduct](#code-of-conduct).
-
-## Contributor license agreement
-
-By submitting code as an individual you agree to the [individual contributor
-license agreement][individual-agreement].
-
-By submitting code as an entity you agree to the [corporate contributor license
-agreement][corporate-agreement].
+_This notice should stay as the first item in the CONTRIBUTING.md file._
## Code of conduct
diff --git a/vendor/Dockerfile/LICENSE b/vendor/Dockerfile/LICENSE
index d6c93c6fcf7..27a215686e7 100644
--- a/vendor/Dockerfile/LICENSE
+++ b/vendor/Dockerfile/LICENSE
@@ -1,6 +1,6 @@
-The MIT License (MIT)
+Copyright (c) 2011-2017 GitLab B.V.
-Copyright (c) 2016-2017 GitLab.org
+With regard to the GitLab Software:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,17 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+For all third party components incorporated into the GitLab Software, those
+components are licensed under the original license provided by the owner of the
+applicable component.
diff --git a/vendor/gitignore/Global/Matlab.gitignore b/vendor/gitignore/Global/Matlab.gitignore
index cca150a88dd..7996ad5058e 100644
--- a/vendor/gitignore/Global/Matlab.gitignore
+++ b/vendor/gitignore/Global/Matlab.gitignore
@@ -1,5 +1,5 @@
##---------------------------------------------------
-## Remove autosaves generated by the Matlab editor
+## Remove autosaves generated by the MATLAB editor
## We have git for backups!
##---------------------------------------------------
@@ -14,6 +14,7 @@
# Simulink Code Generation
slprj/
+sccprj/
# Session info
octave-workspace
diff --git a/vendor/gitignore/Go.gitignore b/vendor/gitignore/Go.gitignore
index a1338d68517..ea58090bd21 100644
--- a/vendor/gitignore/Go.gitignore
+++ b/vendor/gitignore/Go.gitignore
@@ -9,6 +9,3 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
-
-# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
-.glide/
diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore
index eee88b2f0f7..82f3a88e17b 100644
--- a/vendor/gitignore/Haskell.gitignore
+++ b/vendor/gitignore/Haskell.gitignore
@@ -17,5 +17,6 @@ cabal.sandbox.config
*.eventlog
.stack-work/
cabal.project.local
+cabal.project.local~
.HTF/
.ghc.environment.*
diff --git a/vendor/gitignore/Jekyll.gitignore b/vendor/gitignore/Jekyll.gitignore
index 5c91b60c063..2ca868298ce 100644
--- a/vendor/gitignore/Jekyll.gitignore
+++ b/vendor/gitignore/Jekyll.gitignore
@@ -1,3 +1,4 @@
_site/
.sass-cache/
+.jekyll-cache/
.jekyll-metadata
diff --git a/vendor/gitignore/ROS.gitignore b/vendor/gitignore/ROS.gitignore
index f8bcd117371..425641f2c3a 100644
--- a/vendor/gitignore/ROS.gitignore
+++ b/vendor/gitignore/ROS.gitignore
@@ -1,3 +1,5 @@
+devel/
+logs/
build/
bin/
lib/
diff --git a/vendor/gitignore/Symfony.gitignore b/vendor/gitignore/Symfony.gitignore
index 85fd714a965..d098259ffb0 100644
--- a/vendor/gitignore/Symfony.gitignore
+++ b/vendor/gitignore/Symfony.gitignore
@@ -25,6 +25,7 @@
/bin/*
!bin/console
!bin/symfony_requirements
+/vendor/
# Assets and user uploads
/web/bundles/
@@ -37,6 +38,9 @@
# Build data
/build/
+# Composer PHAR
+/composer.phar
+
# Backup entities generated with doctrine:generate:entities command
**/Entity/*~
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index b6418e51766..9bb63365618 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -215,7 +215,11 @@ TSWLatexianTemp*
*~[0-9]*
# auto folder when using emacs and auctex
-/auto/*
+./auto/*
+*.el
# expex forward references with \gathertags
*-tags.tex
+
+# standalone packages
+*.sta
diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore
index eb83a8f122d..75e5b1405da 100644
--- a/vendor/gitignore/Unity.gitignore
+++ b/vendor/gitignore/Unity.gitignore
@@ -1,9 +1,9 @@
-/[Ll]ibrary/
-/[Tt]emp/
-/[Oo]bj/
-/[Bb]uild/
-/[Bb]uilds/
-/Assets/AssetStoreTools*
+[Ll]ibrary/
+[Tt]emp/
+[Oo]bj/
+[Bb]uild/
+[Bb]uilds/
+Assets/AssetStoreTools*
# Visual Studio 2015 cache directory
/.vs/
@@ -25,6 +25,7 @@ ExportedObj/
# Unity3D generated meta files
*.pidb.meta
+*.pdb.meta
# Unity3D Generated File On Crash Reports
sysinfo.txt
diff --git a/vendor/gitignore/UnrealEngine.gitignore b/vendor/gitignore/UnrealEngine.gitignore
index 6c6e1c327fd..1daca8b50d9 100644
--- a/vendor/gitignore/UnrealEngine.gitignore
+++ b/vendor/gitignore/UnrealEngine.gitignore
@@ -50,6 +50,7 @@ SourceArt/**/*.tga
# Binary Files
Binaries/*
+Plugins/*/Binaries/*
# Builds
Build/*
@@ -70,6 +71,7 @@ Saved/*
# Compiled source files for the engine to use
Intermediate/*
+Plugins/*/Intermediate/*
# Cache files for the editor to use
DerivedDataCache/*
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index 509668db67a..6217e6c48e9 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -24,11 +24,14 @@ bld/
[Oo]bj/
[Ll]og/
-# Visual Studio 2015 cache/options directory
+# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
@@ -51,6 +54,10 @@ project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
@@ -247,7 +254,7 @@ FakesAssemblies/
.ntvs_analysis.dat
node_modules/
-# Typescript v1 declaration files
+# TypeScript v1 declaration files
typings/
# Visual Studio 6 build log
@@ -303,3 +310,6 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 88261502d7f..da4d86b9a04 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -83,6 +83,16 @@ codequality:
artifacts:
paths: [codeclimate.json]
+sast:
+ image: registry.gitlab.com/gitlab-org/gl-sast:latest
+ variables:
+ POSTGRES_DB: "false"
+ allow_failure: true
+ script:
+ - /app/bin/run .
+ artifacts:
+ paths: [gl-sast-report.json]
+
review:
stage: review
script:
@@ -218,8 +228,8 @@ production:
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume /tmp/cc:/tmp/cc"
- docker run ${cc_opts} codeclimate/codeclimate init
- docker run ${cc_opts} codeclimate/codeclimate analyze -f json > codeclimate.json
+ docker run ${cc_opts} codeclimate/codeclimate:0.69.0 init
+ docker run ${cc_opts} codeclimate/codeclimate:0.69.0 analyze -f json > codeclimate.json
}
function deploy() {
@@ -345,6 +355,13 @@ production:
}
function build() {
+
+ if [[ -n "$CI_REGISTRY_USER" ]]; then
+ echo "Logging to GitLab Container Registry with CI credentials..."
+ docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
+ echo ""
+ fi
+
if [[ -f Dockerfile ]]; then
echo "Building Dockerfile-based application..."
docker build -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
@@ -362,12 +379,6 @@ production:
echo ""
fi
- if [[ -n "$CI_REGISTRY_USER" ]]; then
- echo "Logging to GitLab Container Registry with CI credentials..."
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- echo ""
- fi
-
echo "Pushing to GitLab Container Registry..."
docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
echo ""
@@ -402,7 +413,9 @@ production:
name="$name-$track"
fi
- helm delete "$name" || true
+ if [[ -n "$(helm ls -q "^$name$")" ]]; then
+ helm delete "$name"
+ fi
}
before_script:
diff --git a/vendor/gitlab-ci-yml/CONTRIBUTING.md b/vendor/gitlab-ci-yml/CONTRIBUTING.md
index d4c057bf9dc..d33a1f06f26 100644
--- a/vendor/gitlab-ci-yml/CONTRIBUTING.md
+++ b/vendor/gitlab-ci-yml/CONTRIBUTING.md
@@ -1,16 +1,15 @@
-## Contributing
+## Developer Certificate of Origin + License
-Thank you for your interest in contributing to this GitLab project! We welcome
-all contributions. By participating in this project, you agree to abide by the
-[code of conduct](#code-of-conduct).
+By contributing to GitLab B.V., You accept and agree to the following terms and
+conditions for Your present and future Contributions submitted to GitLab B.V.
+Except for the license granted herein to GitLab B.V. and recipients of software
+distributed by GitLab B.V., You reserve all right, title, and interest in and to
+Your Contributions. All Contributions are subject to the following DCO + License
+terms.
-## Contributor license agreement
+[DCO + License](https://gitlab.com/gitlab-org/dco/blob/master/README.md)
-By submitting code as an individual you agree to the [individual contributor
-license agreement][individual-agreement].
-
-By submitting code as an entity you agree to the [corporate contributor license
-agreement][corporate-agreement].
+_This notice should stay as the first item in the CONTRIBUTING.md file._
## Code of conduct
diff --git a/vendor/gitlab-ci-yml/Chef.gitlab-ci.yml b/vendor/gitlab-ci-yml/Chef.gitlab-ci.yml
new file mode 100644
index 00000000000..4d5b6484d6e
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Chef.gitlab-ci.yml
@@ -0,0 +1,51 @@
+# This file uses Test Kitchen with the kitchen-dokken driver to
+# perform functional testing. Doing so requires that your runner be a
+# Docker runner configured for privileged mode. Please see
+# https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode
+# for help configuring your runner properly, or, if you want to switch
+# to a different driver, see http://kitchen.ci/docs/drivers
+
+image: "chef/chefdk"
+services:
+ - docker:dind
+
+variables:
+ DOCKER_HOST: "tcp://docker:2375"
+ KITCHEN_LOCAL_YAML: ".kitchen.dokken.yml"
+
+stages:
+ - lint
+ - unit
+ - functional
+
+foodcritic:
+ stage: lint
+ script:
+ - chef exec foodcritic .
+
+cookstyle:
+ stage: lint
+ script:
+ - chef exec cookstyle .
+
+chefspec:
+ stage: unit
+ script:
+ - chef exec rspec spec
+
+# Set up your test matrix here. Example:
+#verify-centos-6:
+# stage: functional
+# before_script:
+# - apt-get update
+# - apt-get -y install rsync
+# script:
+# - kitchen verify default-centos-6 --destroy=always
+#
+#verify-centos-7:
+# stage: functional
+# before_script:
+# - apt-get update
+# - apt-get -y install rsync
+# script:
+# - kitchen verify default-centos-7 --destroy=always
diff --git a/vendor/gitlab-ci-yml/Go.gitlab-ci.yml b/vendor/gitlab-ci-yml/Go.gitlab-ci.yml
index 86e4985d8d2..d572d7a1edc 100644
--- a/vendor/gitlab-ci-yml/Go.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Go.gitlab-ci.yml
@@ -11,8 +11,8 @@ variables:
# repository in /go/src/gitlab.com/namespace/project
# Thus, making a symbolic link corrects this.
before_script:
- - mkdir -p $GOPATH/src/$REPO_NAME
- - ln -svf $CI_PROJECT_DIR/* $GOPATH/src/$REPO_NAME
+ - mkdir -p $GOPATH/src/$(dirname $REPO_NAME)
+ - ln -svf $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME
- cd $GOPATH/src/$REPO_NAME
stages:
diff --git a/vendor/gitlab-ci-yml/LICENSE b/vendor/gitlab-ci-yml/LICENSE
index d6c93c6fcf7..27a215686e7 100644
--- a/vendor/gitlab-ci-yml/LICENSE
+++ b/vendor/gitlab-ci-yml/LICENSE
@@ -1,6 +1,6 @@
-The MIT License (MIT)
+Copyright (c) 2011-2017 GitLab B.V.
-Copyright (c) 2016-2017 GitLab.org
+With regard to the GitLab Software:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,17 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+For all third party components incorporated into the GitLab Software, those
+components are licensed under the original license provided by the owner of the
+applicable component.
diff --git a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml
index 6573eceaa59..1463161a04b 100644
--- a/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Rust.gitlab-ci.yml
@@ -20,4 +20,4 @@ image: "rust:latest"
test:cargo:
script:
- rustc --version && cargo --version # Print version info for debugging
- - cargo test --verbose --jobs 1 --release # Don't paralize to make errors more readable
+ - cargo test --verbose --jobs 1 --release # Don't parallelise to make errors more readable
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 6f6ca5f8b32..b6a5c2f81a0 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -1,460 +1,78 @@
-"","","MIT,ISC,Apache 2.0,New BSD,Simplified BSD"
RedCloth,4.3.2,MIT
-abbrev,1.0.9,ISC
-abbrev,1.1.0,ISC
-accepts,1.3.3,MIT
ace-rails-ap,4.1.2,MIT
-acorn,3.3.0,MIT
-acorn,4.0.13,MIT
-acorn,5.1.1,MIT
-acorn-dynamic-import,2.0.2,MIT
-acorn-jsx,3.0.1,MIT
-actionmailer,4.2.8,MIT
-actionpack,4.2.8,MIT
-actionview,4.2.8,MIT
-activejob,4.2.8,MIT
-activemodel,4.2.8,MIT
-activerecord,4.2.8,MIT
-activesupport,4.2.8,MIT
+actionmailer,4.2.10,MIT
+actionpack,4.2.10,MIT
+actionview,4.2.10,MIT
+activejob,4.2.10,MIT
+activemodel,4.2.10,MIT
+activerecord,4.2.10,MIT
+activesupport,4.2.10,MIT
acts-as-taggable-on,4.0.0,MIT
addressable,2.5.2,Apache 2.0
-after,0.8.2,MIT
-ajv,4.11.8,MIT
-ajv,5.2.2,MIT
-ajv-keywords,1.5.1,MIT
-ajv-keywords,2.1.0,MIT
akismet,2.0.0,MIT
-align-text,0.1.4,MIT
allocations,1.0.5,MIT
-alphanum-sort,1.0.2,MIT
-amdefine,1.0.1,BSD-3-Clause OR MIT
-ansi-escapes,1.4.0,MIT
-ansi-html,0.0.5,"Apache, Version 2.0"
-ansi-html,0.0.7,Apache 2.0
-ansi-regex,2.1.1,MIT
-ansi-styles,2.2.1,MIT
-ansi-styles,3.2.0,MIT
-anymatch,1.3.2,ISC
-append-transform,0.4.0,MIT
-aproba,1.1.1,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-flatten,1.0.1,MIT
-array-find,1.0.0,MIT
-array-find-index,1.0.2,MIT
-array-flatten,1.1.1,MIT
-array-flatten,2.1.1,MIT
-array-slice,0.2.3,MIT
-array-union,1.0.2,MIT
-array-uniq,1.0.3,MIT
-array-unique,0.2.1,MIT
-arraybuffer.slice,0.0.6,MIT
-arrify,1.0.1,MIT
asana,0.6.0,MIT
asciidoctor,1.5.3,MIT
asciidoctor-plantuml,0.0.7,MIT
-asn1,0.2.3,MIT
-asn1.js,4.9.1,MIT
-assert,1.4.1,MIT
-assert-plus,0.2.0,MIT
-assert-plus,1.0.0,MIT
-async,0.9.2,MIT
-async,1.5.2,MIT
-async,2.4.1,MIT
-async-each,1.0.1,MIT
-asynckit,0.4.0,MIT
+asset_sync,2.2.0,MIT
atomic,1.1.99,Apache 2.0
attr_encrypted,3.0.3,MIT
attr_required,1.0.0,MIT
-autoprefixer,6.7.7,MIT
autoprefixer-rails,6.2.3,MIT
-autosize,4.0.0,MIT
-aws-sign2,0.6.0,Apache 2.0
-aws4,1.6.0,MIT
axiom-types,0.1.1,MIT
-axios,0.16.2,MIT
-babel-code-frame,6.22.0,MIT
-babel-core,6.23.1,MIT
-babel-eslint,7.2.1,MIT
-babel-generator,6.23.0,MIT
-babel-helper-bindify-decorators,6.22.0,MIT
-babel-helper-builder-binary-assignment-operator-visitor,6.22.0,MIT
-babel-helper-call-delegate,6.22.0,MIT
-babel-helper-define-map,6.23.0,MIT
-babel-helper-explode-assignable-expression,6.22.0,MIT
-babel-helper-explode-class,6.22.0,MIT
-babel-helper-function-name,6.23.0,MIT
-babel-helper-get-function-arity,6.22.0,MIT
-babel-helper-hoist-variables,6.22.0,MIT
-babel-helper-optimise-call-expression,6.23.0,MIT
-babel-helper-regex,6.22.0,MIT
-babel-helper-remap-async-to-generator,6.22.0,MIT
-babel-helper-replace-supers,6.23.0,MIT
-babel-helpers,6.23.0,MIT
-babel-loader,7.1.1,MIT
-babel-messages,6.23.0,MIT
-babel-plugin-check-es2015-constants,6.22.0,MIT
-babel-plugin-istanbul,4.0.0,New BSD
-babel-plugin-syntax-async-functions,6.13.0,MIT
-babel-plugin-syntax-async-generators,6.13.0,MIT
-babel-plugin-syntax-class-properties,6.13.0,MIT
-babel-plugin-syntax-decorators,6.13.0,MIT
-babel-plugin-syntax-dynamic-import,6.18.0,MIT
-babel-plugin-syntax-exponentiation-operator,6.13.0,MIT
-babel-plugin-syntax-object-rest-spread,6.13.0,MIT
-babel-plugin-syntax-trailing-function-commas,6.22.0,MIT
-babel-plugin-transform-async-generator-functions,6.22.0,MIT
-babel-plugin-transform-async-to-generator,6.22.0,MIT
-babel-plugin-transform-class-properties,6.23.0,MIT
-babel-plugin-transform-decorators,6.22.0,MIT
-babel-plugin-transform-define,1.2.0,MIT
-babel-plugin-transform-es2015-arrow-functions,6.22.0,MIT
-babel-plugin-transform-es2015-block-scoped-functions,6.22.0,MIT
-babel-plugin-transform-es2015-block-scoping,6.23.0,MIT
-babel-plugin-transform-es2015-classes,6.23.0,MIT
-babel-plugin-transform-es2015-computed-properties,6.22.0,MIT
-babel-plugin-transform-es2015-destructuring,6.23.0,MIT
-babel-plugin-transform-es2015-duplicate-keys,6.22.0,MIT
-babel-plugin-transform-es2015-for-of,6.23.0,MIT
-babel-plugin-transform-es2015-function-name,6.22.0,MIT
-babel-plugin-transform-es2015-literals,6.22.0,MIT
-babel-plugin-transform-es2015-modules-amd,6.24.0,MIT
-babel-plugin-transform-es2015-modules-commonjs,6.24.0,MIT
-babel-plugin-transform-es2015-modules-systemjs,6.23.0,MIT
-babel-plugin-transform-es2015-modules-umd,6.24.0,MIT
-babel-plugin-transform-es2015-object-super,6.22.0,MIT
-babel-plugin-transform-es2015-parameters,6.23.0,MIT
-babel-plugin-transform-es2015-shorthand-properties,6.22.0,MIT
-babel-plugin-transform-es2015-spread,6.22.0,MIT
-babel-plugin-transform-es2015-sticky-regex,6.22.0,MIT
-babel-plugin-transform-es2015-template-literals,6.22.0,MIT
-babel-plugin-transform-es2015-typeof-symbol,6.23.0,MIT
-babel-plugin-transform-es2015-unicode-regex,6.22.0,MIT
-babel-plugin-transform-exponentiation-operator,6.22.0,MIT
-babel-plugin-transform-object-rest-spread,6.23.0,MIT
-babel-plugin-transform-regenerator,6.22.0,MIT
-babel-plugin-transform-strict-mode,6.22.0,MIT
-babel-preset-es2015,6.24.0,MIT
-babel-preset-es2016,6.22.0,MIT
-babel-preset-es2017,6.22.0,MIT
-babel-preset-latest,6.24.0,MIT
-babel-preset-stage-2,6.22.0,MIT
-babel-preset-stage-3,6.22.0,MIT
-babel-register,6.23.0,MIT
-babel-runtime,6.22.0,MIT
-babel-template,6.23.0,MIT
-babel-traverse,6.23.1,MIT
-babel-types,6.23.0,MIT
babosa,1.0.2,MIT
-babylon,6.16.1,MIT
-backo2,1.0.2,MIT
-balanced-match,0.4.2,MIT
-balanced-match,1.0.0,MIT
base32,0.3.2,MIT
-base64-arraybuffer,0.1.5,MIT
-base64-js,1.2.0,MIT
-base64id,1.0.0,MIT
-batch,0.6.1,MIT
+batch-loader,1.1.1,MIT
bcrypt,3.1.11,MIT
-bcrypt-pbkdf,1.0.1,New BSD
bcrypt_pbkdf,1.0.0,MIT
-better-assert,1.0.2,MIT
-big.js,3.1.3,MIT
-binary-extensions,1.10.0,MIT
bindata,2.4.1,ruby
-blob,0.0.4,unknown
-block-stream,0.0.9,ISC
-bluebird,2.11.0,MIT
-bluebird,3.5.0,MIT
-bn.js,4.11.6,MIT
-body-parser,1.17.2,MIT
-bonjour,3.5.0,MIT
-boom,2.10.1,New BSD
bootstrap-sass,3.3.6,MIT
bootstrap_form,2.7.0,MIT
-brace-expansion,1.1.7,MIT
-brace-expansion,1.1.8,MIT
-braces,0.1.5,MIT
-braces,1.8.5,MIT
-brorand,1.0.7,MIT
browser,2.2.0,MIT
-browserify-aes,1.0.6,MIT
-browserify-cipher,1.0.0,MIT
-browserify-des,1.0.0,MIT
-browserify-rsa,4.0.1,MIT
-browserify-sign,4.0.0,ISC
-browserify-zlib,0.1.4,MIT
-browserslist,1.7.7,MIT
-buffer,4.9.1,MIT
-buffer-indexof,1.1.0,MIT
-buffer-shims,1.0.0,MIT
-buffer-xor,1.0.3,MIT
builder,3.2.3,MIT
-builtin-modules,1.1.1,MIT
-builtin-status-codes,3.0.0,MIT
-bytes,2.4.0,MIT
-bytes,2.5.0,MIT
-caller-path,0.1.0,MIT
-callsite,1.0.0,unknown
-callsites,0.2.0,MIT
-camelcase,1.2.1,MIT
-camelcase,2.1.1,MIT
-camelcase,3.0.0,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
carrierwave,1.2.1,MIT
-caseless,0.12.0,Apache 2.0
cause,0.1,MIT
-center-align,0.1.3,MIT
-chalk,1.1.3,MIT
-chalk,2.3.0,MIT
charlock_holmes,0.7.5,MIT
-chokidar,1.7.0,MIT
chronic,0.10.2,MIT
chronic_duration,0.10.6,MIT
chunky_png,1.3.5,MIT
-cipher-base,1.0.3,MIT
-circular-json,0.3.3,MIT
citrus,3.0.2,MIT
-clap,1.1.3,MIT
-cli-cursor,1.0.2,MIT
-cli-width,2.1.0,ISC
-clipboard,1.6.1,MIT
-cliui,2.1.0,ISC
-cliui,3.2.0,ISC
-clone,1.0.2,MIT
-co,4.6.0,MIT
-coa,1.0.1,MIT
-code-point-at,1.1.0,MIT
coercible,1.0.0,MIT
-color,0.11.4,MIT
-color-convert,1.9.0,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.5,MIT
-commander,2.9.0,MIT
-commondir,1.0.1,MIT
-component-bind,1.0.0,unknown
-component-emitter,1.1.2,unknown
-component-emitter,1.2.1,MIT
-component-inherit,0.0.3,unknown
-compressible,2.0.11,MIT
-compression,1.7.0,MIT
-compression-webpack-plugin,1.0.0,MIT
-concat-map,0.0.1,MIT
-concat-stream,1.6.0,MIT
concurrent-ruby-ext,1.0.5,MIT
-config-chain,1.1.11,MIT
-configstore,1.4.0,Simplified BSD
-connect,3.6.3,MIT
-connect-history-api-fallback,1.3.0,MIT
connection_pool,2.2.1,MIT
-console-browserify,1.1.0,MIT
-console-control-strings,1.1.0,ISC
-consolidate,0.14.5,MIT
-constants-browserify,1.0.0,MIT
-contains-path,0.1.0,MIT
-content-disposition,0.5.2,MIT
-content-type,1.0.2,MIT
-convert-source-map,1.3.0,MIT
-cookie,0.3.1,MIT
-cookie-signature,1.0.6,MIT
-copy-webpack-plugin,4.0.1,MIT
-core-js,2.3.0,MIT
-core-js,2.4.1,MIT
-core-util-is,1.0.2,MIT
-cosmiconfig,2.1.1,MIT
crack,0.4.3,MIT
-create-ecdh,4.0.0,MIT
-create-hash,1.1.2,MIT
-create-hmac,1.1.4,MIT
creole,0.5.0,ruby
-cropper,2.3.0,MIT
-cross-spawn,5.1.0,MIT
-cryptiles,2.0.5,New BSD
-crypto-browserify,3.11.0,MIT
-css-color-names,0.0.4,MIT
-css-loader,0.28.0,MIT
-css-selector-tokenizer,0.6.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
-d,0.1.1,MIT
-d,1.0.0,MIT
-d3,3.5.11,New BSD
d3_rails,3.5.11,MIT
-dashdash,1.14.1,MIT
-date-now,0.1.4,MIT
-de-indent,1.0.2,MIT
-debug,2.2.0,MIT
-debug,2.3.3,MIT
-debug,2.6.7,MIT
-debug,2.6.8,MIT
debugger-ruby_core_source,1.3.8,MIT
-decamelize,1.2.0,MIT
deckar01-task_list,2.0.0,MIT
declarative,0.0.10,MIT
declarative-option,0.1.0,MIT
-decompress-response,3.3.0,MIT
-deep-equal,1.0.1,MIT
-deep-extend,0.4.2,MIT
-deep-is,0.1.3,MIT
-default-require-extensions,1.0.0,MIT
default_value_for,3.0.2,MIT
-defined,1.0.0,MIT
-del,2.2.2,MIT
-del,3.0.0,MIT
-delayed-stream,1.0.0,MIT
-delegate,3.1.2,MIT
-delegates,1.0.0,MIT
-depd,1.1.0,MIT
-depd,1.1.1,MIT
-des.js,1.0.0,MIT
descendants_tracker,0.0.4,MIT
-destroy,1.0.4,MIT
-detect-indent,4.0.0,MIT
-detect-node,2.0.3,ISC
devise,4.2.0,MIT
devise-two-factor,3.0.0,MIT
-di,0.0.1,MIT
diff-lcs,1.3,"MIT,Artistic-2.0,GPL-2.0+"
-diffie-hellman,5.0.2,MIT
diffy,3.1.0,MIT
-dns-equal,1.0.0,MIT
-dns-packet,1.2.2,MIT
-dns-txt,2.0.2,MIT
-doctrine,1.5.0,BSD
-doctrine,2.0.0,Apache 2.0
-document-register-element,1.3.0,MIT
-dom-serialize,2.2.1,MIT
-dom-serializer,0.1.0,MIT
-domain-browser,1.1.7,MIT
domain_name,0.5.20161021,"Simplified BSD,New BSD,Mozilla Public License 2.0"
-domelementtype,1.1.3,unknown
-domelementtype,1.3.0,unknown
-domhandler,2.3.0,unknown
-domutils,1.5.1,unknown
doorkeeper,4.2.6,MIT
doorkeeper-openid_connect,1.2.0,MIT
-dropzone,4.2.0,MIT
dropzonejs-rails,0.7.2,MIT
-duplexer,0.1.1,MIT
-duplexer3,0.1.4,New BSD
-duplexify,3.5.1,MIT
-ecc-jsbn,0.1.1,MIT
-editorconfig,0.13.2,MIT
-ee-first,1.1.1,MIT
-ejs,2.5.6,Apache 2.0
-electron-to-chromium,1.3.3,ISC
-elliptic,6.3.3,MIT
email_reply_trimmer,0.1.6,MIT
-emoji-unicode-version,0.2.1,MIT
-emojis-list,2.1.0,MIT
-encodeurl,1.0.1,MIT
encryptor,3.0.0,MIT
-end-of-stream,1.4.0,MIT
-engine.io,1.8.3,MIT
-engine.io-client,1.8.3,MIT
-engine.io-parser,1.3.2,MIT
-enhanced-resolve,0.9.1,MIT
-enhanced-resolve,3.4.1,MIT
-ent,2.2.0,MIT
-entities,1.1.1,BSD-like
equalizer,0.0.11,MIT
-errno,0.1.4,MIT
-error-ex,1.3.0,MIT
erubis,2.7.0,MIT
-es5-ext,0.10.24,MIT
-es6-iterator,2.0.1,MIT
-es6-map,0.1.5,MIT
-es6-promise,3.0.2,MIT
-es6-set,0.1.5,MIT
-es6-symbol,3.1.1,MIT
-es6-weak-map,2.0.1,MIT
-escape-html,1.0.3,MIT
-escape-string-regexp,1.0.5,MIT
escape_utils,1.1.1,MIT
-escodegen,1.8.1,Simplified BSD
-escope,3.6.0,Simplified BSD
-eslint,3.19.0,MIT
-eslint-config-airbnb-base,10.0.1,MIT
-eslint-import-resolver-node,0.2.3,MIT
-eslint-import-resolver-webpack,0.8.3,MIT
-eslint-module-utils,2.0.0,MIT
-eslint-plugin-filenames,1.1.0,MIT
-eslint-plugin-html,2.0.1,ISC
-eslint-plugin-import,2.2.0,MIT
-eslint-plugin-jasmine,2.2.0,MIT
-eslint-plugin-promise,3.5.0,ISC
-espree,3.5.0,Simplified BSD
-esprima,2.7.3,Simplified BSD
-esprima,4.0.0,Simplified BSD
-esquery,1.0.0,BSD
-esrecurse,4.1.0,Simplified BSD
-estraverse,1.9.3,BSD
-estraverse,4.1.1,Simplified BSD
-estraverse,4.2.0,Simplified BSD
-esutils,2.0.2,BSD
et-orbi,1.0.3,MIT
-etag,1.8.0,MIT
-eve-raphael,0.5.0,Apache 2.0
-event-emitter,0.3.5,MIT
-event-stream,3.3.4,MIT
-eventemitter3,1.2.0,MIT
-events,1.1.1,MIT
-eventsource,0.1.6,MIT
-evp_bytestokey,1.0.0,MIT
excon,0.57.1,MIT
-execa,0.7.0,MIT
execjs,2.6.0,MIT
-exit-hook,1.1.1,MIT
-expand-braces,0.1.2,MIT
-expand-brackets,0.1.5,MIT
-expand-range,0.1.1,MIT
-expand-range,1.8.2,MIT
-exports-loader,0.6.4,MIT
-express,4.15.4,MIT
expression_parser,0.9.0,MIT
-extend,3.0.1,MIT
-extglob,0.3.2,MIT
-extsprintf,1.0.2,MIT
faraday,0.12.2,MIT
faraday_middleware,0.11.0.1,MIT
faraday_middleware-multi_json,0.0.6,MIT
-fast-deep-equal,1.0.0,MIT
-fast-levenshtein,2.0.6,MIT
fast_gettext,1.4.0,"MIT,ruby"
-fastparse,1.1.1,MIT
-faye-websocket,0.10.0,MIT
-faye-websocket,0.11.1,MIT
-faye-websocket,0.7.3,MIT
ffi,1.9.18,New BSD
-figures,1.7.0,MIT
-file-entry-cache,2.0.0,MIT
-file-loader,0.11.1,MIT
-filename-regex,2.0.0,MIT
-fileset,2.0.3,MIT
-filesize,3.3.0,New BSD
-filesize,3.5.10,New BSD
-fill-range,2.2.3,MIT
-finalhandler,1.0.4,MIT
-find-cache-dir,1.0.0,MIT
-find-root,0.1.2,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.10.2,MIT
flipper-active_record,0.10.2,MIT
flowdock,0.7.1,MIT
@@ -467,392 +85,93 @@ fog-local,0.3.1,MIT
fog-openstack,0.1.21,MIT
fog-rackspace,0.1.1,MIT
fog-xml,0.1.3,MIT
-follow-redirects,1.2.3,MIT
font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License"
-for-in,0.1.6,MIT
-for-own,0.1.4,MIT
-forever-agent,0.6.1,Apache 2.0
-form-data,2.1.4,MIT
formatador,0.2.5,MIT
-forwarded,0.1.0,MIT
-fresh,0.5.0,MIT
-from,0.1.7,MIT
-fs-access,1.0.1,MIT
-fs-extra,0.26.7,MIT
-fs.realpath,1.0.0,ISC
-fsevents,1.1.2,MIT
-fstream,1.0.11,ISC
-fstream-ignore,1.0.5,ISC
-function-bind,1.1.0,MIT
-fuzzaldrin-plus,0.5.0,MIT
-gauge,2.7.4,ISC
gemnasium-gitlab-service,0.2.6,MIT
gemojione,3.3.0,MIT
-generate-function,2.0.0,MIT
-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_process_mem,0.2.0,MIT
-getpass,0.1.7,MIT
gettext_i18n_rails,1.8.0,MIT
gettext_i18n_rails_js,1.2.0,MIT
-gitaly-proto,0.51.0,MIT
+gitaly-proto,0.59.0,MIT
github-linguist,4.7.6,MIT
github-markup,1.6.1,MIT
gitlab-flowdock-git-hook,1.0.1,MIT
gitlab-grit,2.8.2,MIT
gitlab-markup,1.6.3,MIT
-gitlab-svgs,1.0.4,unknown
gitlab_omniauth-ldap,2.0.4,MIT
-glob,5.0.15,ISC
-glob,6.0.4,ISC
-glob,7.1.1,ISC
-glob,7.1.2,ISC
-glob-base,0.3.0,MIT
-glob-parent,2.0.0,ISC
-globalid,0.3.7,MIT
-globals,9.18.0,MIT
-globby,5.0.0,MIT
-globby,6.1.0,MIT
+globalid,0.4.1,MIT
gollum-grit_adapter,1.0.1,MIT
gollum-lib,4.2.7,MIT
gollum-rugged_adapter,0.4.4,MIT
gon,6.1.0,MIT
-good-listener,1.2.2,MIT
google-api-client,0.13.6,Apache 2.0
google-protobuf,3.4.1.1,New BSD
-googleapis-common-protos-types,1.0.0,Apache 2.0
googleauth,0.5.3,Apache 2.0
-got,3.3.1,MIT
-got,7.1.0,MIT
gpgme,2.0.13,LGPL-2.1+
-graceful-fs,4.1.11,ISC
-graceful-readlink,1.0.1,MIT
grape,1.0.0,MIT
grape-entity,0.6.0,MIT
grape-route-helpers,2.1.0,MIT
grape_logging,1.7.0,MIT
-grpc,1.6.6,Apache 2.0
-gzip-size,3.0.0,MIT
+grpc,1.4.5,New BSD
hamlit,2.6.1,MIT
-handle-thing,1.2.5,MIT
-handlebars,4.0.6,MIT
-har-schema,1.0.5,ISC
-har-validator,4.2.1,ISC
-has,1.0.1,MIT
-has-ansi,2.0.0,MIT
-has-binary,0.1.7,MIT
-has-cors,1.1.0,MIT
-has-flag,1.0.0,MIT
-has-flag,2.0.0,MIT
-has-symbol-support-x,1.3.0,MIT
-has-to-string-tag-x,1.3.0,MIT
-has-unicode,2.0.1,ISC
-hash-sum,1.0.2,MIT
-hash.js,1.0.3,MIT
hashie,3.5.6,MIT
hashie-forbidden_attributes,0.1.1,MIT
-hawk,3.1.3,New BSD
-he,1.1.1,MIT
health_check,2.6.0,MIT
hipchat,1.5.2,MIT
-hoek,2.16.3,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,1.11.0,MIT
html2text,0.2.0,MIT
htmlentities,4.3.4,MIT
-htmlparser2,3.9.2,MIT
http,0.9.8,MIT
http-cookie,1.0.3,MIT
-http-deceiver,1.2.7,MIT
-http-errors,1.6.1,MIT
-http-errors,1.6.2,MIT
http-form_data,1.0.1,MIT
-http-proxy,1.16.2,MIT
-http-proxy-middleware,0.17.4,MIT
-http-signature,1.1.1,MIT
http_parser.rb,0.6.0,MIT
httparty,0.13.7,MIT
httpclient,2.8.2,ruby
-https-browserify,0.0.1,MIT
-i18n,0.8.6,MIT
+i18n,0.9.1,MIT
ice_nine,0.11.2,MIT
-iconv-lite,0.4.15,MIT
-icss-replace-symbols,1.0.2,ISC
-ieee754,1.1.8,New BSD
-ignore,3.3.3,MIT
-ignore-by-default,1.0.1,ISC
-immediate,3.0.6,MIT
-imports-loader,0.7.1,MIT
-imurmurhash,0.1.4,MIT
-indent-string,2.1.0,MIT
-indexes-of,1.0.1,MIT
-indexof,0.0.1,unknown
-infinity-agent,2.0.3,MIT
-inflight,1.0.6,ISC
influxdb,0.2.3,MIT
-inherits,2.0.1,ISC
-inherits,2.0.3,ISC
-ini,1.3.4,ISC
-inquirer,0.12.0,MIT
-internal-ip,1.2.0,MIT
-interpret,1.0.1,MIT
-invariant,2.2.2,New BSD
-invert-kv,1.0.0,MIT
-ip,1.1.5,MIT
-ipaddr.js,1.4.0,MIT
ipaddress,0.8.3,MIT
-is-absolute,0.2.6,MIT
-is-absolute-url,2.1.0,MIT
-is-arrayish,0.2.1,MIT
-is-binary-path,1.0.1,MIT
-is-buffer,1.1.5,MIT
-is-builtin-module,1.0.0,MIT
-is-dotfile,1.0.2,MIT
-is-equal-shallow,0.1.3,MIT
-is-extendable,0.1.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-my-json-valid,2.16.0,MIT
-is-npm,1.0.0,MIT
-is-number,0.1.1,MIT
-is-number,2.1.0,MIT
-is-object,1.0.1,MIT
-is-path-cwd,1.0.0,MIT
-is-path-in-cwd,1.0.0,MIT
-is-path-inside,1.0.0,MIT
-is-plain-obj,1.1.0,MIT
-is-posix-bracket,0.1.1,MIT
-is-primitive,2.0.0,MIT
-is-property,1.0.2,MIT
-is-redirect,1.0.0,MIT
-is-relative,0.2.1,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-typedarray,1.0.0,MIT
-is-unc-path,0.1.2,MIT
-is-utf8,0.2.1,MIT
-is-windows,0.2.0,MIT
-isarray,0.0.1,MIT
-isarray,1.0.0,MIT
-isbinaryfile,3.0.2,MIT
-isexe,1.1.2,ISC
-isobject,2.1.0,MIT
-isstream,0.1.2,MIT
-istanbul,0.4.5,New BSD
-istanbul-api,1.1.1,New BSD
-istanbul-lib-coverage,1.0.1,New BSD
-istanbul-lib-hook,1.0.0,New BSD
-istanbul-lib-instrument,1.4.2,New BSD
-istanbul-lib-report,1.0.0-alpha.3,New BSD
-istanbul-lib-source-maps,1.1.0,New BSD
-istanbul-reports,1.0.1,New BSD
-isurl,1.0.0,MIT
-jasmine-core,2.6.3,MIT
-jasmine-jquery,2.1.1,MIT
-jed,1.1.1,MIT
jira-ruby,1.4.1,MIT
-jodid25519,1.0.2,MIT
-jquery,2.2.1,MIT
jquery-atwho-rails,1.3.2,MIT
-jquery-rails,4.1.1,MIT
-jquery-ujs,1.2.1,MIT
-js-base64,2.1.9,BSD
-js-beautify,1.6.12,MIT
-js-cookie,2.1.3,MIT
-js-tokens,3.0.1,MIT
-js-yaml,3.7.0,MIT
-js-yaml,3.9.1,MIT
-jsbn,0.1.1,MIT
-jsesc,0.5.0,MIT
-jsesc,1.3.0,MIT
+jquery-rails,4.3.1,MIT
json,1.8.6,ruby
json-jwt,1.7.2,MIT
-json-loader,0.5.7,MIT
-json-schema,0.2.3,"AFLv2.1,BSD"
-json-schema-traverse,0.3.1,MIT
-json-stable-stringify,1.0.1,MIT
-json-stringify-safe,5.0.1,ISC
-json3,3.3.2,MIT
-json5,0.5.1,MIT
-jsonfile,2.4.0,MIT
-jsonify,0.0.0,Public Domain
-jsonpointer,4.0.1,MIT
-jsprim,1.4.0,MIT
-jszip,3.1.3,(MIT OR GPL-3.0)
-jszip-utils,0.0.2,MIT or GPLv3
jwt,1.5.6,MIT
kaminari,1.0.1,MIT
kaminari-actionview,1.0.1,MIT
kaminari-activerecord,1.0.1,MIT
kaminari-core,1.0.1,MIT
-karma,1.7.0,MIT
-karma-chrome-launcher,2.1.1,MIT
-karma-coverage-istanbul-reporter,0.2.0,MIT
-karma-jasmine,1.1.0,MIT
-karma-mocha-reporter,2.2.2,MIT
-karma-sourcemap-loader,0.3.7,MIT
-karma-webpack,2.0.4,MIT
kgio,2.10.0,LGPL-2.1+
-kind-of,3.1.0,MIT
-klaw,1.3.1,MIT
kubeclient,2.2.0,MIT
-latest-version,1.0.1,MIT
-lazy-cache,1.0.4,MIT
-lcid,1.0.0,MIT
-levn,0.3.0,MIT
licensee,8.7.0,MIT
-lie,3.1.1,MIT
little-plugger,1.1.4,MIT
-load-json-file,1.1.0,MIT
-load-json-file,2.0.0,MIT
-loader-runner,2.3.0,MIT
-loader-utils,0.2.16,MIT
-loader-utils,1.1.0,MIT
locale,2.1.2,"ruby,LGPLv3+"
-locate-path,2.0.0,MIT
-lodash,3.10.1,MIT
-lodash,4.17.4,MIT
-lodash._baseassign,3.2.0,MIT
-lodash._basecopy,3.0.1,MIT
-lodash._baseget,3.7.2,MIT
-lodash._bindcallback,3.0.1,MIT
-lodash._createassigner,3.1.1,MIT
-lodash._getnative,3.9.1,MIT
-lodash._isiterateecall,3.0.9,MIT
-lodash._topath,3.8.1,MIT
-lodash.assign,3.2.0,MIT
-lodash.camelcase,4.1.1,MIT
-lodash.camelcase,4.3.0,MIT
-lodash.capitalize,4.2.1,MIT
-lodash.cond,4.5.2,MIT
-lodash.deburr,4.1.0,MIT
-lodash.defaults,3.1.2,MIT
-lodash.get,3.7.0,MIT
-lodash.get,4.4.2,MIT
-lodash.isarguments,3.1.0,MIT
-lodash.isarray,3.0.4,MIT
-lodash.kebabcase,4.0.1,MIT
-lodash.keys,3.1.2,MIT
-lodash.memoize,4.1.2,MIT
-lodash.restparam,3.6.1,MIT
-lodash.snakecase,4.0.1,MIT
-lodash.uniq,4.5.0,MIT
-lodash.words,4.2.0,MIT
-log4js,0.6.38,Apache 2.0
logging,2.2.2,MIT
-loglevel,1.4.1,MIT
lograge,0.5.1,MIT
-longest,1.0.1,MIT
loofah,2.0.3,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,3.2.0,ISC
-lru-cache,4.0.2,ISC
-macaddress,0.2.8,MIT
-mail,2.6.6,MIT
+mail,2.7.0,MIT
mail_room,0.9.1,MIT
-make-dir,1.0.0,MIT
-map-obj,1.0.1,MIT
-map-stream,0.1.0,unknown
-marked,0.3.6,MIT
-math-expression-evaluator,1.2.16,MIT
-media-typer,0.3.0,MIT
-mem,1.1.0,MIT
memoist,0.16.0,MIT
-memory-fs,0.2.0,MIT
-memory-fs,0.4.1,MIT
-meow,3.7.0,MIT
-merge-descriptors,1.0.1,MIT
method_source,0.8.2,MIT
-methods,1.1.2,MIT
-micromatch,2.3.11,MIT
-miller-rabin,4.0.0,MIT
-mime,1.3.4,MIT
-mime-db,1.27.0,MIT
-mime-db,1.29.0,MIT
-mime-types,2.1.15,MIT
mime-types,3.1,MIT
mime-types-data,3.2016.0521,MIT
mimemagic,0.3.0,MIT
-mimic-fn,1.1.0,MIT
-mimic-response,1.0.0,MIT
+mini_mime,0.1.4,MIT
mini_portile2,2.3.0,MIT
-minimalistic-assert,1.0.0,ISC
-minimatch,3.0.3,ISC
-minimatch,3.0.4,ISC
-minimist,0.0.8,MIT
-minimist,1.2.0,MIT
-mkdirp,0.5.1,MIT
-mmap2,2.2.7,ruby
-moment,2.17.1,MIT
-monaco-editor,0.10.0,MIT
-mousetrap,1.4.6,Apache 2.0
mousetrap-rails,1.4.6,"MIT,Apache"
-ms,0.7.1,MIT
-ms,0.7.2,MIT
-ms,2.0.0,MIT
multi_json,1.12.2,MIT
multi_xml,0.6.0,MIT
-multicast-dns,6.1.1,MIT
-multicast-dns-service-types,1.1.0,MIT
multipart-post,2.0.0,MIT
mustermann,1.0.0,MIT
mustermann-grape,1.0.0,MIT
-mute-stream,0.0.5,ISC
mysql2,0.4.5,MIT
-name-all-modules-plugin,1.0.1,MIT
-nan,2.6.2,MIT
-natural-compare,1.4.0,MIT
-negotiator,0.6.1,MIT
-nested-error-stacks,1.0.2,MIT
net-ldap,0.16.0,MIT
net-ssh,4.1.0,MIT
netrc,0.11.0,MIT
-node-dir,0.1.17,MIT
-node-forge,0.6.33,BSD
-node-libs-browser,1.1.1,MIT
-node-libs-browser,2.0.0,MIT
-node-pre-gyp,0.6.36,New BSD
-node-pre-gyp,0.6.37,New BSD
-nodemon,1.11.0,MIT
nokogiri,1.8.1,MIT
-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
-npm-run-path,2.0.2,MIT
-npmlog,4.1.0,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.1,MIT
-oauth-sign,0.8.2,Apache 2.0
oauth2,1.4.0,MIT
-object-assign,3.0.0,MIT
-object-assign,4.1.0,MIT
-object-assign,4.1.1,MIT
-object-component,0.0.3,unknown
-object.omit,2.0.1,MIT
-obuf,1.1.1,MIT
octokit,4.6.2,MIT
oj,2.17.5,MIT
omniauth,1.4.2,MIT
@@ -873,54 +192,10 @@ omniauth-saml,1.7.0,MIT
omniauth-shibboleth,1.2.1,MIT
omniauth-twitter,1.2.1,MIT
omniauth_crowd,2.2.3,MIT
-on-finished,2.3.0,MIT
-on-headers,1.0.1,MIT
-once,1.4.0,ISC
-onetime,1.1.0,MIT
-opener,1.4.3,(WTFPL OR MIT)
-opn,4.0.2,MIT
-optimist,0.6.1,MIT/X11
-optionator,0.8.2,MIT
-options,0.0.6,MIT
org-ruby,0.9.12,MIT
-original,1.0.0,MIT
orm_adapter,0.5.0,MIT
os,0.9.6,MIT
-os-browserify,0.2.1,MIT
-os-homedir,1.0.2,MIT
-os-locale,1.4.0,MIT
-os-locale,2.1.0,MIT
-os-tmpdir,1.0.2,MIT
-osenv,0.1.4,ISC
-p-cancelable,0.3.0,MIT
-p-finally,1.0.0,MIT
-p-limit,1.1.0,MIT
-p-locate,2.0.0,MIT
-p-map,1.1.1,MIT
-p-timeout,1.2.0,MIT
-package-json,1.2.0,MIT
-pako,0.2.9,MIT
-pako,1.0.5,(MIT AND Zlib)
paranoia,2.3.1,MIT
-parse-asn1,5.0.0,ISC
-parse-glob,3.0.4,MIT
-parse-json,2.2.0,MIT
-parsejson,0.0.3,MIT
-parseqs,0.0.5,MIT
-parseuri,0.0.5,MIT
-parseurl,1.3.1,MIT
-path-browserify,0.0.0,MIT
-path-exists,2.1.0,MIT
-path-exists,3.0.0,MIT
-path-is-absolute,1.0.1,MIT
-path-is-inside,1.0.2,(WTFPL OR MIT)
-path-key,2.0.1,MIT
-path-parse,1.0.5,MIT
-path-to-regexp,0.1.7,MIT
-path-type,1.1.0,MIT
-path-type,2.0.0,MIT
-pause-stream,0.0.11,"MIT,Apache2"
-pbkdf2,3.0.9,MIT
peek,1.0.1,MIT
peek-gc,0.0.2,MIT
peek-host,1.0.0,MIT
@@ -930,86 +205,14 @@ peek-pg,1.3.0,MIT
peek-rblineprof,0.2.0,MIT
peek-redis,1.2.0,MIT
peek-sidekiq,1.0.3,MIT
-performance-now,0.2.0,MIT
pg,0.18.4,"BSD,ruby,GPL"
-pify,2.3.0,MIT
-pify,3.0.0,MIT
-pikaday,1.6.1,MIT
-pinkie,2.0.4,MIT
-pinkie-promise,2.0.1,MIT
-pkg-dir,1.0.0,MIT
-pkg-dir,2.0.0,MIT
-pkg-up,1.0.0,MIT
-pluralize,1.2.1,MIT
po_to_json,1.0.1,MIT
-portfinder,1.0.13,MIT
posix-spawn,0.3.13,MIT
-postcss,5.2.16,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-load-config,1.2.0,MIT
-postcss-load-options,1.2.0,MIT
-postcss-load-plugins,2.3.0,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-modules-extract-imports,1.0.1,ISC
-postcss-modules-local-by-default,1.1.1,MIT
-postcss-modules-scope,1.0.2,ISC
-postcss-modules-values,1.2.2,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-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
-preserve,0.2.0,MIT
-prismjs,1.6.0,MIT
-private,0.1.7,MIT
-process,0.11.9,MIT
-process-nextick-args,1.0.7,MIT
-progress,1.1.8,MIT
-prometheus-client-mmap,0.7.0.beta18,Apache 2.0
-proto-list,1.2.4,ISC
-proxy-addr,1.1.5,MIT
-prr,0.0.0,MIT
-ps-tree,1.1.0,MIT
-pseudomap,1.0.2,ISC
-public-encrypt,4.0.0,MIT
+prometheus-client-mmap,0.7.0.beta43,Apache 2.0
public_suffix,3.0.0,MIT
-punycode,1.3.2,MIT
-punycode,1.4.1,MIT
pyu-ruby-sasl,0.0.3.3,MIT
-q,1.5.0,MIT
-qjobs,1.1.5,MIT
-qs,6.4.0,New BSD
-qs,6.5.0,New BSD
-query-string,4.3.2,MIT
-querystring,0.2.0,MIT
-querystring-es3,0.2.1,MIT
-querystringify,0.0.4,MIT
-querystringify,1.0.0,MIT
rack,1.6.8,MIT
rack-accept,0.4.5,MIT
rack-attack,4.4.1,MIT
@@ -1018,88 +221,35 @@ rack-oauth2,1.2.3,MIT
rack-protection,1.5.3,MIT
rack-proxy,0.6.0,MIT
rack-test,0.6.3,MIT
-rails,4.2.8,MIT
+rails,4.2.10,MIT
rails-deprecated_sanitizer,1.0.3,MIT
rails-dom-testing,1.0.8,MIT
rails-html-sanitizer,1.0.3,MIT
rails-i18n,4.0.9,MIT
-railties,4.2.8,MIT
+railties,4.2.10,MIT
rainbow,2.2.2,MIT
raindrops,0.18.0,LGPL-2.1+
-rake,12.1.0,MIT
-randomatic,1.1.6,MIT
-randombytes,2.0.3,MIT
-range-parser,1.2.0,MIT
-raphael,2.2.7,MIT
-raven-js,3.14.0,Simplified BSD
-raw-body,2.2.0,MIT
-raw-loader,0.5.1,MIT
+rake,12.3.0,MIT
rbnacl,4.0.2,MIT
rbnacl-libsodium,1.0.11,MIT
-rc,1.2.1,(BSD-2-Clause OR MIT OR Apache-2.0)
rdoc,4.2.2,ruby
re2,1.1.1,New BSD
-react-dev-utils,0.5.2,New BSD
-read-all-stream,3.1.0,MIT
-read-pkg,1.1.0,MIT
-read-pkg,2.0.0,MIT
-read-pkg-up,1.0.1,MIT
-read-pkg-up,2.0.0,MIT
-readable-stream,1.0.34,MIT
-readable-stream,2.0.6,MIT
-readable-stream,2.2.9,MIT
-readable-stream,2.3.3,MIT
-readdirp,2.1.0,MIT
-readline2,1.0.1,MIT
recaptcha,3.0.0,MIT
-rechoir,0.6.2,MIT
recursive-open-struct,1.0.0,MIT
-recursive-readdir,2.1.1,MIT
redcarpet,3.4.0,MIT
-redent,1.0.0,MIT
redis,3.3.3,MIT
-redis-actionpack,5.0.1,MIT
-redis-activesupport,5.0.1,MIT
+redis-actionpack,5.0.2,MIT
+redis-activesupport,5.0.4,MIT
redis-namespace,1.5.2,MIT
-redis-rack,1.6.0,MIT
-redis-rails,5.0.1,MIT
-redis-store,1.2.0,MIT
-reduce-css-calc,1.3.0,MIT
-reduce-function-call,1.0.2,MIT
-regenerate,1.3.2,MIT
-regenerator-runtime,0.10.1,MIT
-regenerator-transform,0.9.8,BSD
-regex-cache,0.4.3,MIT
-regexpu-core,1.0.0,MIT
-regexpu-core,2.0.0,MIT
-registry-url,3.1.0,MIT
-regjsgen,0.2.0,MIT
-regjsparser,0.1.5,BSD
-remove-trailing-separator,1.1.0,ISC
-repeat-element,1.1.2,MIT
-repeat-string,0.2.2,MIT
-repeat-string,1.6.1,MIT
-repeating,1.1.3,MIT
-repeating,2.0.1,MIT
+redis-rack,2.0.3,MIT
+redis-rails,5.0.2,MIT
+redis-store,1.4.1,MIT
representable,3.0.4,MIT
-request,2.81.0,Apache 2.0
request_store,1.3.1,MIT
-require-directory,2.1.1,MIT
-require-from-string,1.2.1,MIT
-require-main-filename,1.0.1,ISC
-require-uncached,1.0.3,MIT
-requires-port,1.0.0,MIT
-resolve,1.1.7,MIT
-resolve,1.2.0,MIT
-resolve-from,1.0.1,MIT
responders,2.3.0,MIT
rest-client,2.0.0,MIT
-restore-cursor,1.0.1,MIT
retriable,3.1.1,MIT
-right-align,0.1.3,MIT
-rimraf,2.6.1,ISC
rinku,2.0.0,ISC
-ripemd160,1.0.1,New BSD
rotp,2.1.2,MIT
rouge,2.2.1,MIT
rqrcode,0.7.0,MIT
@@ -1112,244 +262,51 @@ rubyntlm,0.6.2,MIT
rubypants,0.2.0,BSD
rufus-scheduler,3.4.0,MIT
rugged,0.26.0,MIT
-run-async,0.1.0,MIT
-rx-lite,3.1.2,Apache 2.0
-safe-buffer,5.0.1,MIT
-safe-buffer,5.1.1,MIT
safe_yaml,1.0.4,MIT
sanitize,2.1.0,MIT
sass,3.4.22,MIT
sass-rails,5.0.6,MIT
sawyer,0.8.1,MIT
-sax,1.2.2,ISC
securecompare,1.0.0,MIT
seed-fu,2.3.6,MIT
-select,1.1.2,MIT
-select-hose,2.0.0,MIT
-select2,3.5.2-browserify,unknown
select2-rails,3.5.9.3,MIT
-selfsigned,1.10.1,MIT
-semver,4.3.6,ISC
-semver,5.3.0,ISC
-semver-diff,2.1.0,MIT
-send,0.15.4,MIT
sentry-raven,2.5.3,Apache 2.0
-serve-index,1.9.0,MIT
-serve-static,1.12.4,MIT
-set-blocking,2.0.0,ISC
-set-immediate-shim,1.0.1,MIT
-setimmediate,1.0.5,MIT
-setprototypeof,1.0.3,ISC
settingslogic,2.0.9,MIT
sexp_processor,4.9.0,MIT
-sha.js,2.4.8,MIT
-shebang-command,1.2.0,MIT
-shebang-regex,1.0.0,MIT
-shelljs,0.7.8,New BSD
sidekiq,5.0.4,LGPL
sidekiq-cron,0.6.0,MIT
sidekiq-limit_fetch,3.4.0,MIT
-sigmund,1.0.1,ISC
-signal-exit,3.0.2,ISC
signet,0.7.3,Apache 2.0
slack-notifier,1.5.1,MIT
-slash,1.0.0,MIT
-slice-ansi,0.0.4,MIT
-slide,1.1.6,ISC
-sntp,1.0.9,BSD
-socket.io,1.7.3,MIT
-socket.io-adapter,0.5.0,MIT
-socket.io-client,1.7.3,MIT
-socket.io-parser,2.3.1,MIT
-sockjs,0.3.18,MIT
-sockjs-client,1.0.1,MIT
-sockjs-client,1.1.4,MIT
-sort-keys,1.1.2,MIT
-source-list-map,0.1.8,MIT
-source-list-map,2.0.0,MIT
-source-map,0.1.43,BSD
-source-map,0.2.0,BSD
-source-map,0.4.4,New BSD
-source-map,0.5.6,New BSD
-source-map-support,0.4.11,MIT
-spdx-correct,1.0.2,Apache 2.0
-spdx-expression-parse,1.0.4,(MIT AND CC-BY-3.0)
-spdx-license-ids,1.2.2,Unlicense
-spdy,3.4.7,MIT
-spdy-transport,2.0.20,MIT
-split,0.3.3,MIT
-sprintf-js,1.0.3,New BSD
sprockets,3.7.1,MIT
-sprockets-rails,3.2.0,MIT
-sql.js,0.4.0,MIT
-sshpk,1.13.0,MIT
+sprockets-rails,3.2.1,MIT
state_machines,0.4.0,MIT
state_machines-activemodel,0.4.0,MIT
state_machines-activerecord,0.4.0,MIT
-statuses,1.3.1,MIT
-stream-browserify,2.0.1,MIT
-stream-combiner,0.0.4,MIT
-stream-http,2.6.3,MIT
-stream-shift,1.0.0,MIT
-strict-uri-encode,1.1.0,MIT
-string-length,1.0.1,MIT
-string-width,1.0.2,MIT
-string-width,2.0.0,MIT
-string_decoder,0.10.31,MIT
-string_decoder,1.0.1,MIT
-string_decoder,1.0.3,MIT
stringex,2.7.1,MIT
-stringstream,0.0.5,MIT
-strip-ansi,3.0.1,MIT
-strip-bom,2.0.0,MIT
-strip-bom,3.0.0,MIT
-strip-eof,1.0.0,MIT
-strip-indent,1.0.1,MIT
-strip-json-comments,2.0.1,MIT
-supports-color,2.0.0,MIT
-supports-color,3.2.3,MIT
-supports-color,4.2.1,MIT
-svg4everybody,2.1.9,CC0-1.0
-svgo,0.7.2,MIT
sys-filesystem,1.1.6,Artistic 2.0
-table,3.8.3,New BSD
-tapable,0.1.10,MIT
-tapable,0.2.8,MIT
-tar,2.2.1,ISC
-tar-pack,3.4.0,Simplified BSD
temple,0.7.7,MIT
-test-exclude,4.0.0,ISC
text,1.3.1,MIT
-text-table,0.2.0,MIT
thor,0.19.4,MIT
thread_safe,0.3.6,Apache 2.0
-three,0.84.0,MIT
-three-orbit-controls,82.1.0,MIT
-three-stl-loader,1.0.4,MIT
-through,2.3.8,MIT
-thunky,0.1.0,unknown
tilt,2.0.6,MIT
-timeago.js,2.0.5,MIT
-timed-out,2.0.0,MIT
-timed-out,4.0.1,MIT
-timers-browserify,1.4.2,MIT
-timers-browserify,2.0.4,MIT
timfel-krb5-auth,0.8.3,LGPL
-tiny-emitter,1.1.0,MIT
-tmp,0.0.31,MIT
-to-array,0.1.4,MIT
-to-arraybuffer,1.0.1,MIT
-to-fast-properties,1.0.2,MIT
toml-rb,0.3.15,MIT
-touch,1.0.0,ISC
-tough-cookie,2.3.2,New BSD
-traverse,0.6.6,MIT
-trim-newlines,1.0.0,MIT
-trim-right,1.0.1,MIT
truncato,0.7.10,MIT
-tryit,1.0.3,MIT
-ts-loader,3.1.1,MIT
-tty-browserify,0.0.0,MIT
-tunnel-agent,0.6.0,Apache 2.0
-tweetnacl,0.14.5,Unlicense
-type-check,0.3.2,MIT
-type-is,1.6.15,MIT
-typedarray,0.0.6,MIT
-typescript,2.6.1,Apache 2.0
-tzinfo,1.2.3,MIT
+tzinfo,1.2.4,MIT
u2f,0.2.1,MIT
uber,0.1.0,MIT
uglifier,2.7.2,MIT
-uglify-js,2.8.29,Simplified BSD
-uglify-to-browserify,1.0.2,MIT
-uglifyjs-webpack-plugin,0.4.6,MIT
-uid-number,0.0.6,ISC
-ultron,1.0.2,MIT
-ultron,1.1.0,MIT
-unc-path-regex,0.1.2,MIT
-undefsafe,0.0.3,MIT / http://rem.mit-license.org
-underscore,1.8.3,MIT
unf,0.1.4,BSD
unf_ext,0.0.7.4,MIT
unicorn,5.1.0,ruby
unicorn-worker-killer,0.4.4,ruby
-uniq,1.0.1,MIT
-uniqid,4.1.1,MIT
-uniqs,2.0.0,MIT
-unpipe,1.0.0,MIT
-update-notifier,0.5.0,Simplified BSD
-url,0.11.0,MIT
-url-loader,0.5.8,MIT
-url-parse,1.0.5,MIT
-url-parse,1.1.7,MIT
-url-parse,1.1.9,MIT
-url-parse-lax,1.0.0,MIT
-url-to-options,1.0.1,MIT
url_safe_base64,0.2.2,MIT
-user-home,2.0.0,MIT
-useragent,2.2.1,MIT
-util,0.10.3,MIT
-util-deprecate,1.0.2,MIT
-utils-merge,1.0.0,MIT
-uuid,2.0.3,MIT
-uuid,3.0.1,MIT
-validate-npm-package-license,3.0.1,Apache 2.0
validates_hostname,1.0.6,MIT
-vary,1.1.1,MIT
-vendors,1.0.1,MIT
-verror,1.3.6,MIT
version_sorter,2.1.0,MIT
virtus,1.0.5,MIT
-visibilityjs,1.2.4,MIT
-vm-browserify,0.0.4,MIT
vmstat,2.3.0,MIT
-void-elements,2.0.1,MIT
-vue,2.5.2,MIT
-vue-hot-reload-api,2.0.11,MIT
-vue-loader,11.3.4,MIT
-vue-resource,1.3.4,MIT
-vue-style-loader,2.0.5,MIT
-vue-template-compiler,2.5.2,MIT
-vue-template-es2015-compiler,1.5.1,MIT
-vuex,3.0.0,MIT
warden,1.2.6,MIT
-watchpack,1.4.0,MIT
-wbuf,1.7.2,MIT
-webpack,3.5.5,MIT
-webpack-bundle-analyzer,2.8.2,MIT
-webpack-dev-middleware,1.11.0,MIT
-webpack-dev-server,2.7.1,MIT
webpack-rails,0.9.10,MIT
-webpack-sources,1.0.1,MIT
-webpack-stats-plugin,0.1.5,MIT
-websocket-driver,0.6.5,MIT
-websocket-extensions,0.1.1,MIT
-whet.extend,0.9.9,MIT
-which,1.2.12,ISC
-which-module,1.0.0,ISC
-which-module,2.0.0,ISC
-wide-align,1.1.2,ISC
wikicloth,0.8.1,MIT
-window-size,0.1.0,MIT
-wordwrap,0.0.2,MIT/X11
-wordwrap,0.0.3,MIT
-wordwrap,1.0.0,MIT
-wrap-ansi,2.1.0,MIT
-wrappy,1.0.2,ISC
-write,0.2.1,MIT
-write-file-atomic,1.3.4,ISC
-ws,1.1.2,MIT
-ws,2.3.1,MIT
-wtf-8,1.0.0,MIT
-xdg-basedir,2.0.0,MIT
xml-simple,1.1.5,ruby
-xmlhttprequest-ssl,1.5.3,MIT
-xtend,4.0.1,MIT
-y18n,3.2.1,ISC
-yallist,2.1.2,ISC
-yargs,3.10.0,MIT
-yargs,6.6.0,MIT
-yargs,8.0.2,MIT
-yargs-parser,4.2.1,ISC
-yargs-parser,7.0.0,ISC
-yeast,0.1.2,MIT